【问题标题】:Complex inheritance scenario复杂的继承场景
【发布时间】:2013-02-20 02:26:38
【问题描述】:

假设您需要构建一个管理支票的应用程序。每张支票都包含有关金额、日期、收款人和可能存在或不存在的附加付款日期的数据。此外,每张支票必须与属于某家银行的活期账户相关。 现在,我们的应用程序应该允许在这些条件下打印支票:

  • 应用管理的每家银行都有不同的支票布局(即每个字段都有不同的 x、y 位置)。

  • 如果存在付款日期,即使使用相同的相关银行对象,支票布局也会略有变化。但是,从银行到银行,这些更改可能不一样(例如,银行 A 可能会更改日期字段的位置,而银行 B 会更改收款人字段的位置)

有了这些限制,就很难想出一个简单的继承模式,因为没有一致的行为可以跨不同类型的检查进行分解。一种可能的解决方案是避免继承并为每个支票-银行组合创建一个类:

  • ChequeNoPaymentDateForBankA 类
  • 类 ChequeWithPaymentDateForBankA
  • 类 ChequeNoPaymentDateForBankB
  • 类 ChequeWithPaymentDateForBankB 等

这些类中的每一个都实现了 print() 方法,该方法从 Bank 对象中获取字段位置并构建支票布局。到目前为止一切都很好,但是这种方法让我有一种奇怪的感觉,因为没有代码重用的空间。我想知道我是否误解了这个问题,也许有更好的方法。由于这根本不是一个新的问题领域,我相信这是一个重新发明轮子的努力。任何见解将不胜感激。

【问题讨论】:

  • 如果它们都有相同的组件但坐标不同,你可以考虑在同一个对象中使用不同的构造函数。
  • 可能是部分使用了打印的en.wikipedia.org/wiki/Visitor_pattern

标签: oop design-patterns


【解决方案1】:

通常在这些情况下,我会从继承转向委托。也就是说,我没有将公共代码放在超类中(正如您所说,这是有问题的,因为有两个维度),而是将公共代码放在一个字段中(每个维度一个字段)并委托给该字段。

假设您说的是 Java:

public interface Bank {
   public void print();
}

public class BankA implements Bank {
   public void print() { ... }
}

public class BankB implements Bank {
   public void print() { ... }
}


public interface PaymentSchedule {
   public void print();
}

public class WithPaymentDate implements PaymentSchedule {
   public void print() { ... }    
}

public class NoPaymentDate implements PaymentSchedule {
   public void print() { ... }    
}

public class Cheque {
  private final Bank bank;
  private final PaymentSchedule schedule;


  public Cheque(Bank b, PaymentSchedule s) {
     bank = b;
     schedule = s;
  }


  public void print() {
     bank.print();
     schedule.print();
  }
}

这就是解决方案的一般结构。

根据您的 print() 算法的具体细节,您可能需要将更多数据传递到 print 方法和/或将此数据传递到类(银行或 PaymentSchedule 子类)的构造函数并存储它在领域。

【讨论】:

  • 这是一个很好的起点,但问题是很难分解出通用代码并将其放入 Bank 类中,因为银行打印的是什么(我的意思是位置和字段本身)变化取决于付款日期的存在。
【解决方案2】:

我首先将域模型(支票、银行等)与视图(支票的打印方式)分开。这是MVC 模式背后的基本思想,其目标之一是允许以不同方式显示相同的域模型(这似乎是您的情况)。所以,我会先创建领域类,比如:

class Cheque 
{
protected $bank;
protected $money;
...
}

class Bank {...}

请注意,这些类是 MVC 三元组的“M”,并实现您的域模型的逻辑,而不是与渲染过程相关的行为。下一步是实现用于呈现支票的 View 类。采用哪种方法在很大程度上取决于您的渲染有多复杂,但我会首先使用一个 ChequeView 类来渲染公共部分,并将可以更改的特定部分委托给其他子视图(在这种情况下是日期) :

abstract class ChequeView
{
protected $cheque;
protected $dateView;

public function __construct($cheque)
{
  $this->cheque = $cheque;
  $this->dateView = //Whatever process you use to decide if the payment date is shown or not
}

public function render()
{
  $this->coreRender();
  $this->dateView->render();
}

abstract protected function coreRender();
}

class BankACheckView extends ChequeView
{
protected function coreRender() {...}
}

class BankBCheckView extends ChequeView
{
protected function coreRender() {...}
}

abstract class DateView
{
abstract function render()
}

class ShowDateView extends DateView
{
function render() {...}
}

class NullDateView extends DateView
{
function render() {...}
}

而且,如果有代码可以跨子类重用,您当然可以将它们放入ChequeView 并让coreRender() 调用它们。

如果您的渲染变得过于复杂,此设计可能无法缩放。在这种情况下,我会将您的视图拆分为有意义的子部分(例如HeaderViewAmountView 等),以便渲染支票基本上变成渲染其不同的子部分。在这种情况下,ChequeView 可能会以Composite 的身份结束。最后,如果您遇到这种情况并且设置ChequeView 是一项复杂的任务,您可能需要使用Builder

基于 OP cmets 编辑

Builder 主要用于最终对象的实例化是一个复杂的过程(例如,为了获得一致的整体,子部分之间需要同步很多东西)。通常有一个构建器类和不同的客户端,它们发送消息(可能以不同的顺序和不同的参数)以创建各种最终对象。因此,虽然没有被禁止,但通常不会为您要构建的每种类型的对象配备一个构建器。

如果您正在寻找代表创建特定实例的类,您可能需要检查Factory 系列模式(可能Abstract Factory 与您的想法非常相似)。

HTH

【讨论】:

  • 我认为您明白了,将检查字段拆分为单独的渲染组件是我的选择之一,但我从未考虑过 Builder 模式。正如您所说,在这种情况下,扩展至关重要,所以我将 Builder 作为要走的路。
  • 我想要应用构建器模式,我必须为每个支票-银行组合创建一个构建器类,类似于我最初的建议。在这种情况下,ChequeWithPaymentDateForBankA 将变为 ChequeWithPaymentDateForBankABuilder 以创建渲染对象。另外,我需要一组 PrintingItem 实例,它们将携带 x、y 位置和一个文本字段。最后,渲染对象将有一个 print() 方法,该方法简单地遍历每个 PrintingItem 并打印它们。
  • 是的,你是对的。在我的评论中说 builder 的地方应该是 concrete builder。只有一个构建器类定义了print() 抽象方法(这里也许一个接口就足够了)和几个具体的构建器,每个支票-银行组合一个。主管类将负责根据来自检查模型对象的一些信息为作业选择正确的具体构建器。
  • 让一个具体构建器扩展另一个具体构建器而不是直接继承抽象构建器类是否“合法”?这样,我可以通过仅包含需要从一个具体构建器更改为另一个的字段来重用更多代码。
  • 模式是捕获常见问题的解决方案的设计指南,您可以根据自己的需要调整它们。所以,原则上,它似乎没问题。然而,我会尝试一种不同的(恕我直言更优雅)的方法:让构建器成为一个单独的类,负责将各个部分放在一起,并有一个单独的工厂层次结构保存每个 Cheque 图形布局的细节。这样,您就有了明确的职责分离(构建过程与实例配置)。 HTH
猜你喜欢
  • 2014-01-27
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多