策略模式 - DRY 方式来定义一些但不是所有子策略中使用的方法

Strategy pattern - DRY way to define methods used in some, but not all, child strategies

我正在我的 Ruby 项目中实施策略模式,但 运行 遇到了一个轻微的代码风格问题。

假设我有三个策略 class 继承自一个共同的基本策略 class。这三种策略中的两种将需要访问一种方法——第三种不使用它。所以我的直觉是把常用方法放在Base策略中:

class FoodStrategies::Base
  # Used by 2 of the 3 child strategy classes
  def add_hot_sauce
    puts "Food is now spicy"
  end

  def eat
    raise NotImplementedError
  end
end

class FoodStrategies::Taco < FoodStrategies::Base
  def eat
    add_hot_sauce
    puts "Spicy and delicious!"
  end
end

class FoodStrategies::Burrito < FoodStrategies::Base
  def eat
    add_hot_sauce
    puts "Spicy and tasty!"
  end
end

class FoodStrategies::Cereal < FoodStrategies::Base
  def eat
    puts "Yum"
  end
end

从技术上讲,这意味着 FoodStrategies::Cereal 也将具有 add_hot_sauce 方法,但不会使用它。这对我来说似乎是一种代码味道。所以我想知道是否有更合适的方法来保持事物干爽而不向不打算使用它们的策略提供方法。

我试着用谷歌搜索看看其他人在这种情况下做了什么,但令人惊讶的是我没有找到任何东西。我也有想法为这些策略添加另一层继承,并将方法放在新的继承层中:

class FoodStrategies::SpicyFood < FoodStrategies::Base
  def add_hot_sauce
    puts "Food is now spicy"
  end
end

class FoodStrategies::SpicyFood::Taco < FoodStrategies::Base
  # etc.
end

这将使我仅将所需的行为隔离到 SpicyFood 的子级,这意味着 TacoBurrito 将获得 add_hot_sauce 方法,但 Cereal 不会。然而,我看到的策略模式的例子并不建议或鼓励像这样进行多层继承,而是所有的例子都只使用了一层继承。

是否有任何共识或标准可接受的方法来解决这个问题?

继承不仅仅是调用超类中的方法class,您还可以覆盖:

class FoodStrategies::Cereal < FoodStrategies::Base
  def add_hot_sauce
    super
    raise FlagrantCulinaryError, "what is wrong with you"
  end
end

可以在麦片中加入辣酱,这样会使麦片变辣。所以 super 允许您处理这种 特殊情况 的情况。例如:

class Bob
  def eat food
    # Bob eats _everything_ with hot sauce!
    begin
      food.add_hot_sauce
    rescue FlagrantCulinaryError
      # and he doesn't care
    end
    food.eat
  end
end

在大多数情况下,您可能不会捕捉到异常,因此结果在功能上与根本未定义方法相同:错误。

如果很多 subclasses 都有这种异常行为,那么它就不再是异常了。那么你可能应该引入一个中间体 class 来分隔可以添加辣酱的食物。

如果您根本不想将未使用的方法公开给 children,您应该为各个行为使用模块

class FoodStrategies::Base
  def eat
    raise NotImplementedError
  end
end

module CanBeSpicy
  def add_hot_sauce
    puts "Food is now spicy"
  end
end

class FoodStrategies::Taco < FoodStrategies::Base
  include CanBeSpicy
  def eat
    add_hot_sauce
    puts "Spicy and delicious!"
  end
end

class FoodStrategies::Burrito < FoodStrategies::Base
  include CanBeSpicy
  def eat
    add_hot_sauce
    puts "Spicy and tasty!"
  end
end

class FoodStrategies::Cereal < FoodStrategies::Base
  def eat
    puts "Yum"
  end
end

如果你有一堆行为要添加,你可以传递多个像

include BehaviorA, BehaviorB

当然还有很多其他方法可以做到这一点,但我认为在这种情况下,尽管模块方法可能看起来更死记硬背,因为您必须手动为每个 class 指定行为列表,而不是在 parent 中执行此操作与更简单的方法无关。