假设您需要构建一个管理检查的应用程序。每张支票都包含有关金额,日期,收款人以及可能存在或不存在的额外付款日期的数据。此外,每项支票必须与属于某个银行的经常账户相关。 现在,我们的应用程序应允许在这些条件下进行支票打印:
应用管理的每个银行都有不同的支票布局(即每个字段都有不同的x,y位置)。
如果支付日期存在,支票布局会略有变化,即使使用相同的相关银行对象也是如此。但是,从银行到银行,这些变化可能不一样(例如,银行A可能会改变日期字段的位置,而银行B会更改收款人字段的位置)
有了这些限制,很难想出一个简单的继承模式,因为没有一致的行为来分析不同类型的检查。一种可能的解决方案是避免继承并为每个支票 - 银行组合创建一个类:
这些类中的每一个都实现了print()方法,该方法从Bank对象获取字段位置并构建检查布局。到目前为止一直很好,但这种方法给我一种奇怪的感觉,因为没有代码重用的余地。我想知道我是否误解了这个问题,也许还有更好的方法。由于这根本不是一个新的问题领域,我相信这是一次重新发明的努力。任何见解都将受到赞赏。
答案 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()
调用它们。
如果您的渲染过于复杂,此设计可能无法扩展。在这种情况下,我会在有意义的子部分(例如HeaderView
,AmountView
等)中分割您的视图,以便渲染检查变得基本上呈现其不同的子部分。在这种情况下,ChequeView
可能基本上以Composite结尾。最后,如果您达到这种情况并将ChequeView
设置为复杂任务,则可能需要使用Builder。
根据OP评论进行编辑
构建器主要用于最终对象的实例化是一个复杂的过程(例如,为了获得一致的整体,子部件之间需要同步很多东西)。通常有一个构建器类和不同的客户端,它们可以发送消息(可能以不同的顺序和不同的参数)来创建各种最终对象。因此,虽然不是禁止的,但是每种类型的对象都不需要构建一个构建器。
如果您正在寻找代表特定实例创建的类,您可能需要检查Factory模式系列(可能Abstract Factory与您的想法非常相似)。
HTH