设计模式 - 原则及实例讲解
关于设计模式#
设计模式是一些典型问题的优秀实践,是自下而上的,设计模式来源于实践。
在编程过程中,相同的问题不断出现,程序员一次次的解决,久而久之,总结出了特定问题的解决方式,并将其看作是优秀的实践。这些优秀实践就是设计模式。从这里可以看出,设计模式是针对场景而言的,并不是使用越多越好,因为设计模式不是科学理论,而是注重实效的。即使是同一个设计模式,在不同的上下文中实现也可能不同。
通常,设计模式由以下几部分组成
- 命名。命名直接体现出了问题和解决方案,必须兼顾简洁性和描述性。
- 问题。模式是为了解决问题而存在的,因此问题及问题发生的环境是设计模式的基础。
- 解决方案。模式的解决方案并非真正的解决方案,更像是一个半成品,编码人员需要根据具体的场景来完成具体的实现。
- 效果。设计模式会带来某种结果,除了收益外,还需要考虑风险。
原则#
对于人类来说,存在一些普适原则,适用于不同的人,例如「积极主动」、「要事第一」等。而对于软件开发而言,同样也有普适原则的存在,设计模式之所以称之为优秀实践,就是因为它们的设计符合一些软件开发的通用原则:
- 组合优于继承
- 保持组件的松耦合
- 针对接口编程,而非针对实现编程。
- 封装变化。
案例分析#
需求
课程 (Lesson) 分为 固定收费课程 (FixedPriceLesson) 与 计时收费课程 (TimedPriceLesson),前者一次收费 30,后者一小时收费 5 块。
设计
实现
<?php
abstract class Lesson
{
/**
* 课程时长
*
* @var int
*/
protected $duration;
/**
* Lesson constructor.
*
* @param int $duration
*/
public function __construct(int $duration)
{
$this->duration = $duration;
}
/**
* 收费
*
* @return int
*/
abstract function cost(): int;
/**
* 收费类型
*
* @return string
*/
abstract function chargeType() : string;
}
class FixedPriceLesson extends Lesson
{
public function cost(): int
{
return 30;
}
public function chargeType(): string
{
return '固定收费';
}
}
class TimedPriceLesson extends Lesson
{
public function cost(): int
{
return $this->duration * 5;
}
public function chargeType(): string
{
return '计时收费';
}
}
当我们要对课程类型进一步扩展时,就会变得很麻烦。例如,要将课程要分成讲座(Lecture)和 研讨会(Seminar),每种课程又包含了固定收费和即时收费。如果按照继承的思想来设计的话,就会涉及到大量的改动,并且后期的扩展性很差
<?php
abstract class Lesson {}
class Lecture extends Lesson {}
class FixedPriceLesson extends Lecture {}
class TimedPriceLesson extends Lecture {}
我们从设计模式的几个原则来分析下这个例子
- 违反了「组合优于继承」原则 - 所有的功能都通过继承来实现,导致
Lesson
类承担过多的职责; - 违反了「保持组件的松耦合」原则 -
Lesson
类与收费这两者之间是紧密耦合的,这样的话,一个组件的变化必然涉及到另外一个组件的变化,当我们引入新的课程时,就同时需要涉及到Lesson
和收费的变动,导致代码复杂度增加; - 违反了「针对接口编程,而非针对实现编程」原则。在该例子中,我们始终是针对具体的实现去编程;
- 没有「封装变化」。收费策略是不断变化的,在该例子中,我们没有将变化的要素封装起来。
重构#
按照设计原则,将变化的因素封装起来,这里变化的因素就是「收费」这一行为,同时,针对接口编程,所以需要定义一个抽象的收费策略接口。
设计
定义收费策略接口
<?php
abstract class CostStrategy
{
abstract public function cost(Lesson $lesson): int;
abstract public function chargeType(): string;
}
不同的收费策略具体的实现
<?php
class FixedCostStrategy extends CostStrategy
{
public function cost(Lesson $lesson): int
{
return 30;
}
public function chargeType(): string
{
return "固定收费";
}
}
class TimedCostStrategy extends CostStrategy
{
public function cost(Lesson $lesson): int
{
return ($lesson->getDuration() * 5);
}
public function chargeType(): string
{
return "即时收费";
}
}
父类 Lesson
只需要传入抽象的收费策略接口即可,保证了职责的单一性
<?php
abstract class Lesson
{
private $duration;
private $costStrategy;
public function __construct(int $duration, CostStrategy $strategy)
{
$this->duration = $duration;
$this->costStrategy = $strategy;
}
public function cost(): int
{
return $this->costStrategy->cost($this);
}
public function chargeType(): string
{
return $this->costStrategy->chargeType();
}
public function getDuration(): int
{
return $this->duration;
}
}
class Lecture extends Lesson {}
class Seminar extends Lesson {}
测试
<?php
$lessons1 = new Seminar(4, new TimedCostStrategy());
$lessons2 = new Lecture(4, new FixedCostStrategy());
$lesson1->cost();
$lesson1->chargeType();
$lesson2->cost();
$lesson2->chargeType();
消息通知#
最后,在现有的基础上增加消息通知功能,即课程注册成功后,发送通知。对于该功能,可以进行简单的分析
- 通知可以分很多种,邮件、短信等等,因此,需要将这一不断变化的行为进行封装;
- 需要有一个通知管理类,用于注册课程和发送通知;
设计
消息通知
<?php
abstract class Notifier
{
public static function getNotifier(): Notifier
{
if (rand(1, 2) === 1) { // 方便测试,使用了随机返回,实际上可以根据配置文件来决定返回哪种通知
return new MailNotifier();
} else {
return new TextNotifier();
}
}
abstract public function inform($message);
}
class MailNotifier extends Notifier
{
public function inform($message)
{
print "邮件通知: {$message}\n";
}
}
class TextNotifier extends Notifier
{
public function inform($message)
{
print "短信通知: {$message}\n";
}
}
通知管理
<?php
/**
* 客户端用于注册课程,并发送通知
*/
class RegistrationMgr
{
public function register(Lesson $lesson)
{
$notifier = Notifier::getNotifier();
$notifier->inform("新课程花费 ({$lesson->cost()})");
}
}
测试
<?php
$mgr = new RegistrationMgr();
$mgr->register($lessons1);
$mgr->register($lessons2);
参考#
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: