Menu
Coddy logo textTech

Паттерн «Стратегия»

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 45 из 57.

Паттерн стратегия помещает одну часть алгоритма в отдельный объект и позволяет основному объекту делегировать ему выполнение этой части. Замена стратегии изменяет поведение, не изменяя основной класс:

class Standard
  def cost(kg)
    5 + kg * 1.5
  end
end

class Express
  def cost(kg)
    15 + kg * 3
  end
end

class Shipment
  attr_writer :strategy

  def initialize(kg, strategy)
    @kg = kg
    @strategy = strategy
  end

  def cost
    @strategy.cost(@kg)
  end
end

s = Shipment.new(2, Standard.new)
puts s.cost
s.strategy = Express.new
puts s.cost

Вывод:

8.0
21

Стратегии используют только одно имя метода, поэтому работает любой объект с cost: снова утиная типизация. Добавление нового способа доставки означает добавление одного класса, а не ещё одной ветви внутри Shipment:

class Pickup
  def cost(_kg)
    0
  end
end

shipment.strategy = Pickup.new     # Shipment не изменён

В Ruby стратегией часто является лямбда. Всё, что реагирует на call, можно сохранить и вызвать — будь то лямбда, объект метода или объект собственного класса:

class TextFormatter
  def initialize(strategy)
    @strategy = strategy
  end

  def format_all(words)
    words.map { |w| @strategy.call(w) }.join(" ")
  end
end

class Censor
  def call(word)
    word.size > 3 ? "*" * word.size : word
  end
end

words = %w[ruby is great]
puts TextFormatter.new(->(w) { w.upcase }).format_all(words)
puts TextFormatter.new(:capitalize.to_proc).format_all(words)
puts TextFormatter.new(Censor.new).format_all(words)

Результат:

RUBY IS GREAT
Ruby Is Great
**** is *****

Выбирайте стратегию вместо подклассов, когда один объект должен изменять своё поведение во время работы программы или когда вариация представляет собой один шаг. Подкласс фиксирует поведение на весь срок существования объекта:

# подкласси: фиксируется при создании
ExpressShipment.new(2)

# стратегия: можно изменить позже
shipment = Shipment.new(2, Standard.new)
shipment.strategy = Express.new
challenge icon

Задание

Легко

Завершите стратегии доставки и ShippingCalculator. Standard#cost(kg) равен 5 плюс 1.50 за кг, Express#cost(kg) равен 15 плюс 3 за кг, а Pickup#cost(kg) равен 0. Калькулятор создаётся со стратегией, её можно изменить с помощью strategy=, а quote(kg) возвращает 2.5 kg by Express: 22.50, используя имя класса стратегии.

Предоставленный код начинает работу со Standard и считывает команды use express, use pickup, use standard или quote 2.5, выводя каждую оценку стоимости.

Ваш код должен находиться в файлах standard.rb, express.rb, pickup.rb и shipping_calculator.rb. В main.rb содержится предоставленный код ввода/вывода, и его нельзя изменять.

Попробуйте сами

require_relative 'standard'
require_relative 'express'
require_relative 'pickup'
require_relative 'shipping_calculator'

# Предоставленный код ввода/вывода: оставьте его как есть
input = []
while (line = gets)
  input << line.chomp
end
strategies = { "standard" => Standard, "express" => Express, "pickup" => Pickup }
calc = ShippingCalculator.new(Standard.new)
input.each do |line|
  cmd, arg = line.split
  if cmd == "use"
    calc.strategy = strategies[arg].new
  else
    puts calc.quote(arg.to_f)
  end
end
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Ruby