在 OOP 中,用更少的方法拥有更多 类 而不是拥有更多方法的更少 类 是否一定有利?

In OOP, is it necessarily favorable to have more classes with fewer methods rather than fewer classes with more methods?

我一直在研究设计模式,并且遇到了访问者模式。这对我来说真的很奇怪;它的唯一目的似乎是防止层次结构中的原始 classes 需要更改,即使在向它们添加新职责时也是如此。这只需将这些职责委派给抽象访问者 class(或接口)的实现,并有效地双重调度以调用传递正确动态类型的正确访问者的操作函数。

我研究了各种其他问题,讨论何时以及为何使用访问者模式,我所遇到的只是在 OOP 中,预计 classes 的数量会增加比访问者模式专门研究的每个 class 的方法数量还要多。

然而,这是真的吗?对我来说,这听起来像是对 OOP 的过度简化。作为参考,这个答案来自here

我还阅读了另一个(松散)相关的问题 here,,其答案暗示您应该只创建更多 classes 并在有意义的地方分离职责。组织代码使得 "responsibility" 或 class 仅由单个操作定义,而不管所操作的类型如何(即访问者负责实现每个相关类型的操作),还是将职责定义为可以在单个类型上完成的一组操作(将这些职责委托给类型本身)更有意义?

如果目标仅仅是减少每个 class 中的代码行数,为什么这是一个合理的目标?单独的行数对封装、模块化、代码组织或一般良好的 OOP 实践没有任何直接影响,对吗?

编辑: 我现在明白了服务于 OOP 的模式并不存在,而且我现在明白在某些情况下访问者模式是 最佳 解决方案。话虽如此,当与 OOP 一起使用时,它是否会促进不太明智的 class 层次结构?

首先我要说我既不是模式方面的专家也不是 OOP 教条方面的专家。我只是一个日常使用模式和 OOP 的开发人员。

我倾向于同意几乎所有不那么教条的观点,仅仅是因为坚持教条迟早会让你陷入困境。但是,我同意这样一个事实,即 classes 将是增加的东西,而不是方法。这当然过于简单化了,说 class 可能不会增加方法是完全错误的。这种教条式方法的唯一有效情况是,如果您使用 class 建模的对象永远不会发生任何更新。通常情况下,class 定义一个对象,并且预期 class 代表最终对象,而不是会改变的东西。

我认为您可能误解了访问者模式和一般模式的作用以及我们有时使用它们的原因。

不是,因为某些 OOP 事物、某些抽象概念或某些隐藏的真相。恰恰相反。您应该以适当的面向对象的方式为您的问题建模,使用具有 行为 、隐藏状态等的对象。简而言之,就是 OO。您 应该出去寻找在您的代码中随机应用的模式,事实上这本身就是一种反模式:)

现在,有些人注意到,对于某些小问题,我们大多数人往往会产生类似的解决方案。这些很有趣,不是因为这些解决方案如此具有开创性,或者它们本身很有价值,而是因为它只是节省了我们一些时间和脑力,而不是一遍又一遍地解决这些问题。这些是模式。

回到你的问题:如果你不太明白访问者模式是做什么的或者为什么它这样做,或者how/why 会用的,不用担心。如果您遇到 需要 的问题,您会注意到的!在这种情况下,没有其他方法可以正常工作,您基本上 来使用它。在所有其他情况下,您不应该 使用它!

总结: 不,由于某些 OOP 的原因,我们不使用访问者模式。减少线路本身并不是一个合法的目标。是的,单独的行数不会影响封装、模块化等。

在设计时考虑到 SOLID object-oriented principles,面向对象的软件在长期 运行 中最易于维护。设计模式只是这些原则的命名、可重用应用。

访问者模式的目的不是为了防止层次结构中的原始 classes 需要更改 "even when a new responsibility is added to them." 而是将那些 classes 设置为不加修改地扩展,这是Open/Closed原则的完美范例。

但是,要回答您的具体问题:

  1. 正确。减少行数不是目标。事实上,我可以向您展示一个 class ,其中使用单一职责原则会增加代码行数。 (见下文。)

  2. "alongside OOP"没有关于访问者模式的内容。和不。它不会促进不太明智的 class 层次结构。它允许您在不修改 class 的情况下向 class 添加行为,这在长期 运行 中使您的软件更易于维护。

这是我承诺的示例(在 Ruby 中,末尾有注释):

class OrderTotalCaluculator
  attr_reader :order, :tax_calculators

  def initialize(order, tax_calculators = [])
    @order = order
    @tax_calculators = tax_calculators
  end

  def total
    items_subtotal = order.items.sum { |item| item.price * item.quantity }
    taxes_subtotal = tax_calculators.sum { |calculator| calculator.calculate(items_subtotal) }

    items_subtotal + taxes_subtotal
  end
end

class CaliforniaStateSalesTaxCalculator
  def self.calculate(amount)
    amount * 0.075
  end
end

class SanFranciscoCitySalesTaxCalculator
  def self.calculate(amount)
    amount * 0.01
  end
end

total = OrderTotalCalculator.new(
  order, 
  [
    CaliforniaStateSalesTaxCalculator, 
    SanFranciscoCitySalesTaxCalculator
  ]
).total

总的来说,这段实现策略模式的代码非常好。每个 class 都有一个单一的责任。另外,如果美国要征收全国销售税,OrderTotalCalculator 就不需要改变。

但是,如果我们仔细观察,OrderTotalCalculator#total 方法有不止一项职责。如果我们清理它,它会是这样的:

class OrderTotalCaluculator
  attr_reader :order, :tax_calculators

  def initialize(order, tax_calculators = [])
    @order = order
    @tax_calculators = tax_calculators
  end

  def total
    items_subtotal + taxes_subtotal
  end

  def items_subtotal
    @items_subtotal ||= order.items.sum { |item| item.price * item.quantity }
  end

  def taxes_subtotal
    tax_calculators.sum { |calculator| calculator.calculate(items_subtotal) }
  end
end

现在,从字面上看,每个方法都有一个单一的、定义明确、名称明确的职责。而且,class 实际上变大了。所以你去吧。

Ruby 译注:

  • @variable 创建一个名为 variable
  • 的实例变量
  • attr_reader :variable@variable 实例变量创建 getter 方法的语法糖
  • @variable ||= statement将语句的值记住在一个实例变量中,这样下次调用该方法时,就不会再执行该语句了