解耦
如果类之间有非常强的依赖性,那么这样的系统就很难维护,因为系统里的一个改动会引起一连串的相关改动。
- 问题
重用性是面向对象设计的主要目的,而紧耦合便是它的敌人。当我们看到系统中一个组件的改变迫使系统其他许多地方也发生改变的时候,就可以诊断为紧耦合了。
- 降低耦合
在你自己的项目中,你会看到很多这种需要分离组件的情况。例如,课程系统应包含注册组件,从而向系统添加新课程。添加新课程后,应该通知管理员,这是注册程序的一部分。
下面的这些代码对使用通知程序的系统隐藏了通知程序的实现细节:
class RegistrationMgr
{
function register(Lesson $lesson)
{
//处理该课程
//通知某人
$notifier = Notifier::getNotifier();
$notifier->inform("new lesson:cost({$lesson->cost()})");
}
}
abstract class Notifier
{
static function getNotifier()
{
//根据配置或其他逻辑获得具体的类
if (rand(1, 2) == 1) {
return new MailNotifier();
} else {
return new TextNotifier();
}
}
abstract function inform($message);
}
class MailNotifier extends Notifier
{
function inform($message)
{
print "MAIL notification: {$message}\n";
}
}
class TextNotifier extends Notifier
{
function inform($message)
{
print "TEXT notification:{$message}\n";
}
}
$lesson1 = new Seminar(4, new TimedCostStrategy());
$lesson2 = new Lecture(4, new FixedCostStrategy());
$mgr = new RegistrationMgr();
$mgr->register($lesson1);
$mgr->register($lesson2);
针对接口编程,而不是针对实现编程
我们了解到可以把不同的实现隐藏在父类所定义的共同接口下,然后客户端代码需要一个父类的对象而不是一个子类的对象,从而使客户端代码可以不用关心它实际得到的是哪个具体实现。从客户端代码来看,类方法参数为抽象或通用类型通常是不错的主意。如果参数对对象类型要求过于严格,就会限制代码在运行时的灵活性。
当然,如何使用参数类型提示来调整参数对象的“通用性”是需要仔细权衡的。选中过于通用,则会降低方法的安全性。而如果是某个子类型的特有功能,那么方法接受另一个子类类型则可能会有风险。
变化的概念
一旦做出设计决定,解释它就很容易。但是你如何决定从哪里开始设计呢?
“把变化的概念封装起来”。在我们的示例中,“变化的概念”就是费用计算。我们发现为这个变化直接创建子类是不合适的,于是使用了条件语句。通过把变化的元素放入同一个类中,我们强调了封装的适用性。
《设计模式》建议积极搜寻类中变化的元素,并评估它们是否适合用新类型来封装。根据一定条件,变化的元素可被提取出来形成子类,而这些元素共同拥有一个抽象父类。而这个新类型能被其他类使用。这么做有以下好处:
- 专注于职责
- 通过组合提高灵活性
- 使继承层级体系更紧凑和集中
- 减少重复
那么如何发现变化的元素呢?误用继承便是一个标志,当然适合封装“变化元素”的另一个标志便是出现了条件表达式。
- 父子关系
模式的一个问题便是不必要或不恰当地使用模式。
“极限编程”提供了几个可以使用的相关原则。第一个是“你还不需要它”,第二个是“用最简单的方式来完成任务”。
模式
以《设计模式》为起点,将模式分为以下几种:
- 用于生成对象的模式
关注对象的实例化。 - 用于组织对象和类的模式
帮助我们组织对象的组成关系。 - 企业模式
着眼于一些描述典型因特网编程问题和解决方案的模式。 - 数据库模式
数据库存取数据及对象-数据库映射的相关模式。