面向對象的SOLID原則白話篇


面向對象的SOLID原則

簡介

縮寫 全稱 中文
S The Single Responsibility Principle 單一責任原則
O The Open Closed Principle 開放封閉原則
L Liskov Substitution Principle 里氏替換原則
I The Interface Segregation Principle 接口分離原則
D The Dependency Inversion Principle 依賴倒置原則

單一職責原則

一個類只應承擔一種責任。換句話說,讓一個類只做一件事。如果需要承擔更多的工作,那么分解這個類。

舉例

訂單和賬單上都有流水號、業務時間等字段。如果只用一個類表達,賦予其雙重職責,后果:

  1. 特有屬性和共有屬性相互摻雜,難以理解;
  2. 修改一個場景可能會影響另一個場景。

正確的做法是拆成兩個獨立的類。

開放封閉原則

實體應該對擴展是開放的,對修改是封閉的。即,可擴展(extension),不可修改(modification)。

舉例

一個商戶接入了多個付款方式,支付寶和微信支付,如果將調用支付API的類寫成:

public class PayHandler {

	public Result<T> pay(Param param) {
		if(param.getType() == "ALIPAY") {
			// 支付寶付款調用
			...
		} else if(param.getType() == "WeChatPay") {
		   // 微信支付付款調用
		   ...
		}
	}
}

那么每次新加一種支付方式,或者修改原有的其中一種支付方式,都要修改PayHandler這個類,可能會影響現有代碼。

比較好的做法是將不同的行為(支付方式)抽象,如下:

public class PayHandler {

	private Map<String, PayProcessor> processors;

	public Result<T> pay(Param param) {
		PayProcessor payProcessor = processors.get(param.getType());
		// 異常處理略
		return payProcessor.handle(param);
	}
}

interface PayProcessor {
	Result<T> handle(Param param);
}

public class AlipayProcessor implements PayProcessor {
	...
}

public class WeChatPayProcessor implements PayProcessor {
	...
}

這樣,新增支付方式只需要新增類,如果使用的是spring等容器,在xml配置對應key-value關系即可;修改已有的支付方式只需要修改對應的類。最大化地避免了對已有實體的修改。

里式替換原則

一個對象在其出現的任何地方,都可以用子類實例做替換,並且不會導致程序的錯誤。換句話說,當子類可以在任意地方替換基類且軟件功能不受影響時,這種繼承關系的建模才是合理的。

舉例

經典的例子: 正方形不是長方形的子類。原因是正方形多了一個屬性“長 == 寬”。這時,對正方形類設置不同的長和寬,計算面積的結果是最后設置那項的平方,而不是長*寬,從而發生了與長方形不一致的行為。如果程序依賴了長方形的面積計算方式,並使用正方形替換了長方形,實際表現與預期不符。

擴展

不能用繼承關系(is-a),但可以用委派關系(has-a)表達。上例中,可以使用正方形類包裝一個長方形類。或者,將正方形和長方形作進一步抽象,使用共有的抽象類。

逸聞

“里氏”指的是芭芭拉·利斯科夫(Barbara Liskov,1939年-),是美國第一個計算機科學女博士,圖靈獎、馮諾依曼獎得主,參與設計並實現了OOP語言CLU,而CLU語言對現代主流語言C++/Java/Python/Ruby/C#都有深遠影響。其項目中提煉出來的數據抽象思想,已成為軟件工程中最重要的精髓之一。(來源: 互動百科

接口分離原則

客戶(client)不應被強迫依賴它不使用的方法。即,一個類實現的接口中,包含了它不需要的方法。將接口拆分成更小和更具體的接口,有助於解耦,從而更容易重構、更改。

舉例

仍以商家接入移動支付API的場景舉例,支付寶支持收費和退費;微信接口只支持收費。

interface PayChannel {
	void charge();
	void refund();
}

class AlipayChannel implements PayChannel {
	public void charge() {
		...
	}
	
	public void refund() {
		...
	}
}

class WeChatChannel implements payChannel {
	public void charge() {
		...
	}
	
	public void refund() {
		// 沒有任何代碼
	}
}

第二種支付渠道,根本沒有退款的功能,但是由於實現了PayChannel,又不得不將refund()實現成了空方法。那么,在調用中,這個方法是可以調用的,實際上什么都沒有做!

改進

將PayChannel拆成各包含一個方法的兩個接口PayableChannel和RefundableChannel。

依賴倒置原則

  1. 高層次的模塊不應依賴低層次的模塊,他們都應該依賴於抽象。
  2. 抽象不應依賴於具體實現,具體實現應依賴抽象。

實際上,依賴倒置是實現開閉原則的方法。

舉例

開閉原則的場景仍然可以說明這個問題。以下換一種表現形式。

public class PayHandler {

	public Result<T> pay(Param param) {
		if(param.getType() == "ALIPAY") {
			AlipayProcessor processor = new AlipayProcessor();
			processor.hander(param);
			...
		} else if(param.getType() == "WeChatPay") {
		  	WeChatPayProcessor processor = new WeChatPayProcessor();
			processor.hander(param);
		   ...
		}
	}
}

public class AlipayProcessor { ... }

public class WeChatPayProcessor { ... }

這種實現方式,PayHandler的功能(高層次模塊)依賴了兩個支付Processor(低層次模塊)的實現。

擴展:IOC和DI

控制反轉(IOC)和依賴注入(DI)是Spring中最重要的核心概念之一,而兩者實際上是一體兩面的。

  • 依賴注入
    • 一個類依賴另一個類的功能,那么就通過注入,如構造器、setter方法等,將這個類的實例引入。
    • 側重於實現。
  • 控制反轉
    • 創建實例的控制權由一個實例的代碼剝離到IOC容器控制,如xml配置中。
    • 側重於原理。
    • 反轉了什么:原先是由類本身去創建另一個類,控制反轉后變成了被動等待這個類的注入。

后記

網絡上很多文章中關於SOLID的介紹,語句都不通順,徒增理解難度。如果對基本釋義仍不能領會,可以參考 英文WIKI


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM