Menu
Coddy logo textTech

Padrão Strategy

Parte da seção Programação orientada a objetos do Journey de Ruby da Coddy. Lição 45 de 57.

O padrão de estratégia coloca uma parte de um algoritmo em seu próprio objeto e permite que o objeto principal delegue a ele. Trocar a estratégia altera o comportamento sem alterar a classe principal:

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

Saída:

8.0
21

As estratégias compartilham apenas um nome de método, portanto qualquer objeto com cost funciona: duck typing novamente. Adicionar uma nova forma de envio significa adicionar uma classe, não outro ramo dentro de Shipment:

class Pickup
  def cost(_kg)
    0
  end
end

shipment.strategy = Pickup.new     # Shipment permanece inalterado

Em Ruby, uma estratégia geralmente é uma lambda. Qualquer coisa que responda a call pode ser armazenada e invocada, seja uma lambda, um objeto de método ou um objeto da sua própria classe:

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)

Saída:

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

Escolha a estratégia em vez de subclasses quando um objeto precisar mudar seu comportamento enquanto o programa estiver em execução, ou quando a variação for uma única etapa. Uma subclasse fixa o comportamento durante toda a vida do objeto:

# subclassing: fixo na criação
ExpressShipment.new(2)

# strategy: pode mudar depois
shipment = Shipment.new(2, Standard.new)
shipment.strategy = Express.new
challenge icon

Desafio

Fácil

Complete as estratégias de envio e ShippingCalculator. Standard#cost(kg) é 5 mais 1.50 por kg, Express#cost(kg) é 15 mais 3 por kg, e Pickup#cost(kg) é 0. O calculador é criado com uma estratégia, pode alterá-la com strategy=, e quote(kg) retorna 2.5 kg by Express: 22.50, usando o nome da classe da estratégia.

O código fornecido começa com Standard e lê os comandos use express, use pickup, use standard ou quote 2.5, imprimindo cada cotação.

Seu código deve ficar em standard.rb, express.rb, pickup.rb e shipping_calculator.rb. main.rb contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

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

# Código de entrada/saída fornecido: mantenha-o como está
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 iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação orientada a objetos

Pratique por conta própria: Compilador de Ruby online