在面向對象中饺著,類是基本單位箫攀,各種設計都是圍繞著類來進行的∮姿ィ可以說匠童,類與類之間的關系,構成了設計模式的大部分內(nèi)容塑顺。
經(jīng)典的設計模式有23
種汤求,學習設計模式推薦GOF經(jīng)典以及《敏捷軟件開發(fā)——原則、方法與實踐》
1.單一職責原則
背景故事:亞當.斯密 對制針業(yè)做過一個分工生產(chǎn)效率的例子(《國富論》第一章)严拒。每個工人專學一種專門的業(yè)務扬绪,相比每個人獨立完成整個工作流程,達到數(shù)千倍的效率裤唠。在面向對象設計中挤牛,分工理論就是單一職責原則(Single Pesponsibility Prineiple, SRP)
。 就一個類而言种蘸,應該只有一個引起它變化的原因墓赴。此原則容易理解卻不容易做到,主要是怎樣設計類與類的方法界定問題航瞭。
單一職責的兩個含義:
1. 避免相同的職責分散到不同的類中
2. 避免一個類承擔太多職責
為什么要遵循SRP:
- 可以減少類之間的耦合:當需求變化時诫硕,只修改一個類,從而隔離了變化刊侯。
- 提高類的復用性
單一職責使得組件可以方便的拆卸和組裝
SPR實際開發(fā)中的應用:用工廠模式來實現(xiàn)不同數(shù)據(jù)庫操作類章办。
工廠模式(Factory) 允許你在代碼執(zhí)行時實例化對象。之所以被稱為工廠模式,是因為它負責“生產(chǎn)”對象藕届。
下面案例是使用工廠模式實現(xiàn)一個數(shù)據(jù)庫操作類挪蹭,主要是講解工廠模式。
Db_Adapter.php
//先定義一個適配器接口
interface Db_Adapter
{
/**
* 數(shù)據(jù)庫連接
* @param $config 數(shù)據(jù)庫配置
* @return resource
*/
public function connection($config);
/**
* 執(zhí)行數(shù)據(jù)庫查詢
* @param string $query 數(shù)據(jù)庫查詢SQL字符串
* @param mixed $handle 連接對象
* @return resource
*/
public function query($query, $handle);
}
Db_Adapter_Mysql.php
// 定義mysql 操作類
require_once 'Db_Adapter.php';
class Db_Adapter_Mysql implements Db_Apdater
{
private $_dbLink; //數(shù)據(jù)庫連接標識
/**
* 數(shù)據(jù)庫連接函數(shù)
*
* @param $config 數(shù)據(jù)庫配置
* @throws Db_Exception
* @return resource
*/
public function connection($config)
{
if ($this->_dbLink = @mysql_connect($config->host . (empty($config->port) ? '' : ':' . $config->port), $config->user, $config->password, true)) {
if (@mysql_select_db($config->database, $this->_dbLink)) {
if ($config->charset) {
mysql_query("SET NAMES '{$config->charset}'", $this->_dbLink);
}
return $this->_dbLink;
}
}
/** 數(shù)據(jù)庫異常 */
throw new Db_Exception(@mysql_error($this->_dbLink));
}
/**
* 執(zhí)行數(shù)據(jù)庫查詢
*
* @param string $query 數(shù)據(jù)庫查詢SQL 字符串
* @param mixed $handle 連接對象
* @return resource
public function query($query, $handle)
{
if ($resource = @mysql_query($query, $handle) {
return $resource;
}
}
}
Db_Adapter_sqlite.php
// 定義sqlite 操作類
require_once 'Db_Adapter.php';
class Db_Adapter_sqlite implements Db_Adapter
{
private $_dbLink; // 數(shù)據(jù)庫連接標識
public function connection($config)
{
if ($this->_dbLink = sqlite_open($config->file, 0666, $error)) {
return $this->_dbLink;
}
/** 數(shù)據(jù)庫異常 */
throw new Db_Exception($error);
}
/**
* 執(zhí)行數(shù)據(jù)庫查詢
*
* @param string $query 數(shù)據(jù)庫查詢 SQL 字符串
* @param mixed $handle 連接對象
* @return resource
*/
public function query($query, $handle)
{
if ($resource = @sqlite_query($query, $handle)) {
return $resource;
}
}
}
sqlFactory.php
// 定義工廠類
class sqlFactory
{
public static function factory($type)
{
if (incluede_once '/Db_Adapter_' . $type . '.php') {
$classname = 'Db_Adapter_' . $type;
return new $classname;
} else {
throw new Exception ('Driver not found');
}
}
}
test.php
// 調(diào)空上面的庫
require 'sqlFactory.php';
$dn = sqlFactory::factory('Mysql');
$db = sqlFactory::factory('sqlite');
print_r($dn);
print_r($db);
在上面的例子中休偶,sqlFactory類是一個工廠類梁厉,用戶可以通過調(diào)用sqlFactory類中的factory方法和它的參數(shù)來動態(tài)的初始化不同類型的數(shù)據(jù)庫的操作類,Db_Adapter.php定義了數(shù)據(jù)庫操作類的規(guī)范踏兜,這樣讓所有數(shù)據(jù)庫的操作完全一樣词顾,如果更換了數(shù)據(jù)庫,只需要用工廠類生成不同的對象就可以實現(xiàn)不同數(shù)據(jù)庫的操作庇麦,而不需要去修改數(shù)據(jù)庫操作的業(yè)務代碼计技。
SRP是最簡單的原則之一,也是最難做好的原則之一山橄。
一些應該遵循的做法:
-
根據(jù)業(yè)務流程垮媒,把業(yè)務對象提煉出來。
如果業(yè)務流程鏈路太復雜航棱,就把這個業(yè)務對象分離為多個單一業(yè)務對象睡雇。當業(yè)務鏈標準化后,對業(yè)務對象的內(nèi)部情況做進一部處理饮醇。把第一次標準化視為最高層抽象它抱,第二次視為次高層抽象,以此類推朴艰,直到“恰如其分”的設計層次观蓄。 -
職責的分類需要注意。
有業(yè)務職責祠墅,還要脫離業(yè)務的抽象職責侮穿,從認識業(yè)務到抽象算法是一個層層遞進的過程。就好比命令模式中的顧客毁嗦,服務員和廚師的職責亲茅,作為老板的你需要規(guī)劃好各自的職責范圍,既要防止越俎代庖狗准,也要防止互相推諉克锣。
2.接口隔離原則
設計應用程序的時候,如果一個模塊包含多個子模塊腔长,那么我們應該小心對該模塊做出抽象袭祟。
接口隔離原則(Interface Segregation Principle, ISP)
: 客戶端不應該被強迫實現(xiàn)不會使用的接口,應該把胖接口中的方法分組饼酿,然后用多個接口代替它榕酒,每個接口服務于一個子模塊胚膊。簡單的說故俐,就是使用多個專門的接口比使用單個接口要好得多想鹰。
ISP主要觀點:
- 一個類對另外一個類的依賴性應當是建立在最小的接口上。
- ISP 可以達到不強迫客戶依賴于他們不使用的方法药版,接口的實現(xiàn)類應該只呈現(xiàn)為單一職責的角色(遵守SRP原則)辑舷。
- ISP 還可降低客戶端之間的相互影響——當某個客戶程序要求提供新的職責(需求變更)而迫使接口發(fā)生改變時,影響到其他客戶程序的可能性會是最小的槽片。
- 客戶端程序不應該依賴它不需要的接口方法何缓。
ISP強調(diào)的是接口對客戶端的承諾越少越好,并且要做到專一还栓。
接口污染
過于臃腫的接口設計是對接口的污染碌廓。接口污染就是為接口添加不必要的職責,如果開發(fā)人員在接口中增加一個新功能的主要目的只是減少接口實現(xiàn)類的數(shù)目剩盒,則此設計導致接口被不斷的“污染” 并 “變胖”谷婆。
上面的結構就是一個標準的胖接口,客戶A需要服務A辽聊、客戶B需要服務B纪挎、客戶C需要服務C,當客戶A的需求變化后跟匆,也會影響到客戶B和C异袄。
使用接口隔離后如下:
使用接口隔離原則后,雖然接口數(shù)量變多玛臂,但是當客戶A的需求變更后烤蜕,就不會再影響到客戶B和C。
考慮下如何實現(xiàn)禽類動物的接口迹冤?他們都有翅膀讽营,都是卵生動物,都有羽毛叁巨,但是鳥會飛斑匪,鴨子不會飛。
可以把它們共性設計為一個基礎類锋勺,針對部分特有的屬性蚀瘸,可以另外定義一個接口,讓具有這些特殊屬性的同時實現(xiàn)基礎接口和特殊接口庶橱。
對于接口污染贮勃,可以考慮以下兩種處理方式:
- 利用委托分離接口
- 利用多繼承分離接口
3.開放 - 封閉原則
- Open(Open for extension) 模塊的行為必須是開放的、支持擴展的苏章,而不是僵化的寂嘉。
- Closed (Closed for modification) 在模塊的功能進行擴展時奏瞬,不應該影響或大規(guī)模影響已有的程序模塊。
一個模塊在擴展性方面應該是開放的泉孩,在更改性方面應該是封閉的硼端。
在生活中,開放 - 封閉原則最簡單的例子就是電腦寓搬,你可以很容易對電腦功能進行擴展灼捂,并且不對現(xiàn)有功能或者只是少量影響現(xiàn)有功能售碳。
該原則的核心是想是對抽象編程至耻,而不是具體編程渔期,因為抽象相對穩(wěn)定。讓類依賴于固定的抽象唾琼,這樣的修改就是封閉的兄春,通過面向對象的繼承和多態(tài)機制,可以實現(xiàn)對抽象體的繼承锡溯,通過覆寫其方法改變固有行為赶舆,實現(xiàn)新的擴展方法,所以對于擴展就是開放的趾唱。
- 在設計方面充分應用 抽象 和 封裝 的思想涌乳。
- 在系統(tǒng)功能編程實現(xiàn)方面應用面向接口的編程。
4.替換原則
87年 Liskov
女士在OOPSLA
大會上發(fā)表《Data Abstraction and Hierarchy》中提出甜癞,主要闡述有關繼承的一些原則夕晓,也稱為里氏替換原則。02年Robert C.Martin出版的《Agile Software Development Principles Pattems and Practices》中對里氏替換原則進行簡化為子類必須能夠替換成它們的基類
悠咱。
里氏替換原則(Liskov Substiution Principle, LSP)
定義以及主要思想:子類型必須能夠替換掉它們的父類型蒸辆、并出現(xiàn)在父類能夠出現(xiàn)的任何地方。
LSP解決如果正確進行繼承設計和合理地應用繼承機制:
- 如何正確地進行繼承方面的設計?
- 最佳的繼承層次如何獲得?
- 怎樣避免所設計的類層次陷入不符合OCP原則的狀況?
如何遵守替換原則:
- 父類的方法都要在子類中實現(xiàn)或者重寫, 派生類只實現(xiàn)其抽象類中聲明的方法, 而不應當給出多余的方法定義或實現(xiàn)析既。
- 在客戶端程序中只應該出現(xiàn)父類對象躬贡,而不是直接使用子類對象, 這樣可以實現(xiàn)運行期綁定(多態(tài)綁定)。
php對LSP的支持不如其它語言, 因為其沒有向上轉型等概念, 只能通過一些其它手段來實現(xiàn)眼坏。
5.依賴倒置原則
將依賴關系倒置為依賴接口:
- 上層模塊不應該依賴下層模塊, 它們共同依賴于一個抽象拂玻。
- 抽象不能依賴于具體, 具體應該要依賴于抽象
employee.php
<?php
interface employee
{
public function working();
}
class teacher implements employee
{
public function working()
{
echo 'teaching...';
}
}
class cooder implements employee
{
public function working()
{
echo 'coding...';
}
}
class workA
{
public function work()
{
$teacher = new teacher();
$teacher->working();
}
}
class workB
{
private $e;
public function set(employee $e)
{
$this->e = $e;
}
public function work()
{
$this->e->working();
}
}
$worka = new workA();
$worka->work();
$workb = new workB();
$workb->set(new teacher());
$workb->work();
classA中,work方法依賴teacher實現(xiàn); classB中宰译,通過工廠模式實現(xiàn)了一定的解耦檐蚜,但是這還是硬編碼,進一步擴展可以將依賴寫入配置文件中沿侈,專門由一個程序檢測配置是否正確以及加載配置中所依賴的實現(xiàn)闯第,這個檢測程序,就叫IOC容器缀拭。
IOC(Inversion of Control) 是依賴倒置原則(Dependence Inversion Principle, DIP)的同義詞咳短。
- DI: 依賴注入
- DS: 依賴查找
以上幾個概念都是IOC的兩種實現(xiàn)填帽。
依賴倒置的核心原則是解耦,如果脫離這個最原始的原則咙好,那就是本末倒置篡腌。
如何滿足DIP:
- 每個較高層次類都為它所需要的服務提出一個接口聲明, 較低層次實現(xiàn)這個接口
- 每個高層類都通過該抽象接口使用服務