設計模式概述


最近開始學習設計模式相關的知識,記錄一下。

設計模式是什么

學習一個東西,肯定要先知道這個東西是什么,用來解決什么問題。從網上找了一份設計模式的定義:

設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結,使用設計模式是為了可重用代碼、讓代碼更容易被他人理解並且保證代碼可靠性

據說最開始軟件工程中模式的概念還是借鑒於建築學,由大名鼎鼎的“四人幫”(GoF)引入了軟件工程領域。其實軟件模式除了設計模式還包括架構模式、分析模式、過程模式等,其他的有機會再了解。

簡單來說,設計模式就是程序員在軟件開發中發現有些類似的程序結構會不斷地出現,反復地被使用,然后就有人把這些成熟的代碼設計經驗總結了成一套解決方案,提供給大家學習、交流和使用。

學習設計模式有什么好處

  1. 提高代碼的可重用性,避免一些重復的工作,減少大段大段的復制粘貼。
  2. 提高開發速度。有時候我們會發現思考了好久想出來的設計方案其實早已經在設計模式里面了。
  3. 當項目的規模漸漸變大,如果系統沒有足夠的靈活性和可擴展性,開發和維護會變得苦不堪言。而設計模式的正確使用可以大大減輕這個問題。
  4. 懂得設計模式對於學習那些大牛寫的開源工具、庫和框架是很有幫助的,我覺得這一點很重要。研究開源框架時,找找里面的設計模式。

學習設計模式應該注意的地方

  1. 要搞清楚每個設計模式要解決的問題是什么,使用的場景條件是什么,是如何解決的,優缺點是什么。
  2. 最好能運用這個設計模式解決一個問題。
  3. 不能為了使用設計模式而使用設計模式,過多的設計模式也會使系統變得臃腫不堪。這個說着簡單,其實很難量化,只能多學習大牛的源碼,加上自己去體會。
  4. 並不所有的編程語言都需要用到這些設計模式,有些設計模式完全是為了彌補某些語言的缺陷而出現的,可以參考知乎上的一個回答

設計模式的七個原則

單一職責原則(Single Responsibility Principle, SRP):一個類只負責一個功能領域中的相應職責,或者說就一個類而言,應該只有一個引起它變化的原因。

一個類應該只負責一個職責。負責的職責越多,類就會越復雜。當各種職責耦合在一個類中時,這個類被多次復用的可能性就會大大減小。而且當我們需要修改其中一個職責的代碼時,很可能會影響到別的職責。因此最好將各個職責分離,放在不同的類中。

其實這個原則在我們的工作中是很常見的,比如下面這個例子:將用戶照片上傳到某圖床,返回一個url,然后將這個url和用戶其他信息一起保存到數據庫,最后把所有數據顯示在頁面。

初始方案是一個UserService類實現,其中uploadPhoto()方法上傳照片,saveUser()方法保存信息,displayUser()方法返回所有數據到頁面。

現在這個類一看就不符合單一職責原則,我想只要有點經驗的程序員都會向下面這么使用吧。

一個類只負責做一類事,這就是單一職責原則。

開閉原則(Open-Closed Principle, OCP):對擴展開放,對修改關閉。即盡量在不修改原有代碼的情況下進行擴展。

ChartDisplay類想調用Barchart和PieChart的display方法就得通過if語句根據type來判斷應該執行哪個實現的display方法。

public class ChartDisplay {
public void display(String type) {
if (type.equals("pie")) {
PieChart chart = new PieChart();
chart.display();
} else if (type.equals("bar")) {
BarChart chart = new BarChart();
chart.display();
}
}
}

重構后如果需要添加新的chart類就不需要修改ChartDisplay類,只需要新增一個AbstractChart實現類就好。

public class ChartDisplay {
private AbstractChart chart;
public void setChart(AbstractChart chart) {
this.chart = chart;
}
public void display() {
chart.display();
}
}

開閉原則的關鍵就是抽象。將具體業務放在實現類,添加業務類型的時候,就可以通過添加實現類來擴展,而不用修改原來的類。

里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對象。

這個原則的意思就是一個子類應該可以替換掉它的父類,而且程序不會產生異常。

因為一般來說,父類都是對外開放的接口,不能隨便修改。我們使用子類重寫父類的方法的時候應該按照父類的規定來寫,不要違背父類的意思。比如ArrayList實現了List接口,他們都有size方法,size方法定義的就是返回List中元素的個數。我們自己寫一個List實現類的時候就不要返回別的東西,不然子類就不能替代父類的位置,就不滿足這個原則。

還有子類最好不要重寫父類已經實現的方法,而是增加自己持有的新方法。

class A {
public int func1(int a, int b) {
return a - b;
}
}
class B extends A {
public int func1(int a, int b) {
return a + b;
}
public int func2(int a, int b) {
return func1(a, b) + 100;
}
}

很明顯,B類的func1的做法是錯誤的。如果我在一個程序中使用了A類的func1方法,然后把A類替換成B類,這時程序就會出現問題。

依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴於細節,細節應當依賴於抽象。要針對接口編程,而不是針對實現編程。

要針對接口編程,而不是針對實現編程。這句很好理解。因為接口是穩定的,實現是容易變化的,所以我們要針對接口編程,這樣實現的變化才不會對客戶端產生影響。

依賴倒轉原則要求我們在程序代碼中傳遞參數時或在關聯關系中,盡量引用的抽象層類,即使用接口和抽象類進行變量類型聲明、參數類型聲明、方法返回類型聲明,以及數據類型的轉換等,而不要用具體類來做這些事情。

依賴倒轉原則的例子和開閉原則的例子差不多。很多情況下開閉原則、里氏代換原則和依賴倒轉原則會同時出現,看起來也比較類似。

開閉原則是目標,里氏代換原則是基礎,依賴倒轉原則是手段,它們相輔相成,相互補充,目標一致,只是分析問題時所站角度不同而已。

接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口去提供所有功能。

接口隔離原則要求每個接口有自己專門的工作,不要把所有的功能一股腦的塞到一個接口里面。

一個接口的功能過多就會導致該接口使用起來很不靈活,因為如果客戶端只需要使用其中的一個方法就得把所有的方法實現。所以,最好確保每一個接口只扮演一個角色,做好一份工作。

當然,在使用接口隔離原則的時候,我們也不能矯枉過正,把接口分得太細,導致接口泛濫。

合成復用原則(Composite Reuse Principle, CRP):復用時要盡量使用組合/聚合關系(關聯關系),少用繼承。

使用合成復用原則的原因就是,繼承比組合/聚合關系(關聯關系)的耦合性強。

現在有一個UserDAO類需要連接MySQL數據庫進行數據操作,有一個DBUtil類中有獲取MySQL數據庫連接的方法getConnection。如果使用繼承方案,使用UserDAO繼承DBUtil得到獲取數據庫連接的方法getConnection,那么當需要新增一種連接Oracle數據庫的方式時,就必須修改UserDAO或者DBUtil類的源碼。

這是違反開閉原則的。因此應該使用關聯復用來代替繼承復用。

將DBUtil變為UserDAO類中的屬性,采用依賴注入的方式把DBUtil對象注入UserDAO對象中。這樣UserDAO和DBUtil之間的關系由繼承關系變為關聯關系。如果需要對DBUtil的功能進行擴展,添加其子類的實現就可以,比如OracleDBUtil。

迪米特法則(Law of Demeter, LoD):一個軟件實體應當盡可能少地與其他實體發生相互作用。

我覺得這個原則有兩個意思:1、一個對象應該對其他對象保持最少的了解,對於被依賴的類而言,意思就是向外公開的public方法應該盡可能的少;2、不要和“陌生人”說話、只與你的直接朋友通信。直接朋友通常表現為關聯,聚合和組成關系,兩個對象之間聯系很緊密,通常以成員變量,方法的參數和返回值的形式出現。如果兩個對象之間不需要直接通信,那這兩個對象就不應該有直接的相互作用,如果其中的一個對象需要調用另一個對象的某一個方法的話,可以通過第三者轉發這個調用。簡單來說就是通過引入一個合理的第三者來降低現有對象之間的耦合度。

第一條簡單點說就是把應該設為private的屬性和方法設置為private

public class Operation {

public void openDoor() {
System.out.println("把冰箱門打開");
}

public void putIn() {
System.out.println("把大象放進冰箱");
}

public void closeDoor() {
System.out.println("把冰箱關閉");
}

public void operate() {
openDoor();
putIn();
closeDoor();
}
}

public class Person {
private Operation operation;

public void setOperation(Operation operation) {
this.operation = operation;
}

public void operate() {
// 要把大象放入冰箱可以執行以下流程
operation.openDoor();
operation.putIn();
operation.closeDoor();

// 或者
operation.operate();
}
}

上面的Operation類暴露的方法太多,會讓使用者產生迷惑。

應該改成這樣:

public class Operation {

private void openDoor() {
System.out.println("把冰箱門打開");
}

private void putIn() {
System.out.println("把大象放進冰箱");
}

private void closeDoor() {
System.out.println("把冰箱關閉");
}

public void operate() {
openDoor();
putIn();
closeDoor();
}
}

對於Person來說,它只關心把大象放進冰箱的整體操作,不關心分了幾步。所以這個Operation類只需要暴露一個操作方法operate()。

第二條有點像代理的意思。比方說,現在有個工人Operator是專門做把大象放進冰箱這個工作的(這工作真奇葩~)。我們普通人就不需要自己做這件事情,我們只要跟這個工人溝通,讓他去做這件是就可以了,畢竟他更專業。普通人擅長與人溝通,工人擅長做這個工作。這就叫只依賴應該依賴的對象。

public class Operation {

private void openDoor() {
System.out.println("把冰箱門打開");
}

private void putIn() {
System.out.println("把大象放進冰箱");
}

private void closeDoor() {
System.out.println("把冰箱關閉");
}

public void operate() {
openDoor();
putIn();
closeDoor();
}
}

public class Operator {
private Operation operation;

public void operate(){
operation.operate();
}
}

public class Person {
private Operator operator;

public void operate() {
// 要把大象放入冰箱可以執行以下流程
operator.operate();
}
}

這里通過引入一個專門用於操作的中間類(Operator)來降低操作和人的耦合度。

設計模式的類型

23個GoF設計模式一共可以分為三種類型:創建型模式(Creational Patterns)、結構型模式(Structural Patterns)、行為型模式(Behavioral Patterns。

創建型模式:不使用 new 運算符直接實例化,對象隱藏創建邏輯的方式。

  • 工廠模式(Factory Pattern)
  • 抽象工廠模式(Abstract Factory Pattern)
  • 單例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

結構型模式:這些設計模式着重於類和對象的組合。

  • 適配器模式(Adapter Pattern)
  • 橋接模式(Bridge Pattern)
  • 過濾器模式(Filter、Criteria Pattern)
  • 組合模式(Composite Pattern)
  • 裝飾器模式(Decorator Pattern)
  • 外觀模式(Facade Pattern)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy Pattern)

行為型模式:這些設計模式着重於對象之間的通信。

  • 責任鏈模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 解釋器模式(Interpreter Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 備忘錄模式(Memento Pattern)
  • 觀察者模式(Observer Pattern)
  • 狀態模式(State Pattern)
  • 空對象模式(Null Object Pattern)
  • 策略模式(Strategy Pattern)
  • 模板模式(Template Pattern)
  • 訪問者模式(Visitor Pattern)

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: