复杂的继承场景

时间:2013-02-19 05:20:18

标签: oop design-patterns

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

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

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

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

  • class ChequeNoPaymentDateForBankA
  • class ChequeWithPaymentDateForBankA
  • class ChequeNoPaymentDateForBankB
  • class ChequeWithPaymentDateForBankB等

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

2 个答案:

答案 0 :(得分: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方法和/或将这些数据传递给类(Bank或​​PaymentSchedule子类)的构造函数并存储它在田野里。

答案 1 :(得分:0)

我首先将域模型(支票,银行等)与视图(打印支票的方式)分开。这是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评论进行编辑

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

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

HTH