0

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

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

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

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

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

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

4

2 回答 2

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() 算法的确切细节,您可能需要将更多数据传递到打印方法和/或将此数据传递到类(银行或 PaymentSchedule 子类的)的构造函数并将其存储在字段中。

于 2013-02-19T05:47:27.357 回答
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() {...}
}

而且,如果有代码可以跨子类重用,您当然可以将它们考虑在内ChequeViewcoreRender()调用它们。

In case your rendering turns to be too complex, this design may not scale. In that case I would go for splitting your view in meaningful subparts (e.g. HeaderView, AmountView, etc) so that rendering a cheque becomes basically rendering its different sub-parts. In this case the ChequeView may end basically working as a Composite. Finally, if you reach this case and setting up the ChequeView turns out to be a complex task you may want to use a Builder.

Edit based on the OP comments

The Builder is mostly used when the instantiation of the final object is a complex process (e.g. there are many things to sync between the sub-parts in order to get a consistent whole). There is generally one builder class and different clients, that send messages (potentially in different orders and with different arguments) to create a variety of final objects. So, while not prohibited, it is not usual to have one builder per type of object that you want to build.

If you are looking for a class that represents the creation of a particular instance you may want to check the Factory family of patterns (maybe the Abstract Factory resembles closely to what you had in mind).

HTH

于 2013-02-19T12:00:57.370 回答