關於設計模式
設計模式是一些典型問題的優秀實踐,是自下而上的,設計模式來源於實踐。
在程式設計過程中,相同的問題不斷出現,程式設計師一次次的解決,久而久之,總結出了特定問題的解決方式,並將其看作是優秀的實踐。這些優秀實踐就是設計模式。從這裡可以看出,設計模式是針對場景而言的,並不是使用越多越好,因為設計模式不是科學理論,而是注重實效的。即使是同一個設計模式,在不同的上下文中實現也可能不同。
通常,設計模式由以下幾部分組成
- 命名。命名直接體現出了問題和解決方案,必須兼顧簡潔性和描述性。
- 問題。模式是為了解決問題而存在的,因此問題及問題發生的環境是設計模式的基礎。
- 解決方案。模式的解決方案並非真正的解決方案,更像是一個半成品,編碼人員需要根據具體的場景來完成具體的實現。
- 效果。設計模式會帶來某種結果,除了收益外,還需要考慮風險。
原則
對於人類來說,存在一些普適原則,適用於不同的人,例如「積極主動」、「要事第一」等。而對於軟體開發而言,同樣也有普適原則的存在,設計模式之所以稱之為優秀實踐,就是因為它們的設計符合一些軟體開發的通用原則:
- 組合優於繼承
- 保持元件的松耦合
- 針對介面程式設計,而非針對實現程式設計。
- 封裝變化。
案例分析
需求
課程 (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 協議》,轉載必須註明作者和本文連結