文章出處

單一職責原則(SRP:The Single Responsibility Principle)

一個類應該有且只有一個變化的原因。

There should never be more than one reason for a class to change.

為什么將不同的職責分離到單獨的類中是如此的重要呢?

因為每一個職責都是一個變化的中心。當需求變化時,這個變化將通過更改職責相關的類來體現。

如果一個類擁有多于一個的職責,則這些職責就耦合到在了一起,那么就會有多于一個原因來導致這個類的變化。對于某一職責的更改可能會損害類滿足其他耦合職責的能力。這樣職責的耦合會導致設計的脆弱,以至于當職責發生更改時產生無法預期的破壞。

例如,考慮下圖中的設計。類圖中顯示 Rectangle 類包含兩個方法,一個方法(Draw)負責在顯示屏幕上繪制矩形,另一個方法(Area)負責計算矩形圖形面積。

有兩個不同的應用程序均使用了 Rectangle 類。一個應用為計算幾何程序,它使用了 Rectangle 中的數學幾何模型,但不會在顯示屏幕上繪制矩形。另一個應用是一個圖形界面程序(GUI),它可能會做一些計算幾何方面的工作,但主要功能是在屏幕上繪制矩形。

 1   public class Rectangle
 2   {
 3     public int Height { get; set; }
 4     public int Width { get; set; }
 5 
 6     public double Area()
 7     {
 8       return Width * Height;
 9     }
10 
11     public void Draw(Form form)
12     {
13       SolidBrush brush = new SolidBrush(Color.Red);
14       Graphics formGraphics = form.CreateGraphics();
15       formGraphics.FillRectangle(brush,
16         new System.Drawing.Rectangle(
17           new Point(0, 0), new Size(Width, Height)));
18     }
19   }

這個設計侵犯了 SRP 原則。Rectangle 類包含了兩個職責。第一個職責是提供矩形幾何計算的數學模型,第二個職責是在 GUI 上渲染矩形。

對 SRP 原則的侵犯會導致諸多難以解決的問題:

首先,我們必須在計算幾何應用中包含對 GUI 庫的引用。這導致應用程序無謂的消耗了鏈接時間、編譯時間、內存空間和存儲空間等。

再者,如果因為某些原因對 GraphicalApplication 的一個更改導致 Rectangle 類也相應做了更改,這將強制我們對 ComputationalGeometryApplication 進行重新編譯、重新測試和重新部署等。如果我們忘了做這些事情,那么應用程序可能以無法預期的方式而崩潰。

 1   public class ComputationalGeometryApplication
 2   {
 3     public double CalculateArea(Rectangle rectangle)
 4     {
 5       double area = rectangle.Area();
 6       return area;
 7     }
 8   }
 9 
10   public class GraphicalApplication
11   {
12     public Form form { get; set; }
13 
14     public void DrawOnScreen(Rectangle rectangle)
15     {
16       rectangle.Draw(form);
17     }
18   }

一個較好的設計是將這兩個職責完全地隔離到不同的類當中,如下圖所示。這個設計將 Rectangle 中關于幾何計算的職責移到了 GeometricRectangle 類中,而 Rectangle 類中僅保留矩形渲染職責。

 1   public class GeometricRectangle
 2   {
 3     public int Height { get; set; }
 4     public int Width { get; set; }
 5 
 6     public double Area()
 7     {
 8       return Width * Height;
 9     }
10   }
11 
12   public class Rectangle
13   {
14     public void Draw(Form form, GeometricRectangle geometric)
15     {
16       SolidBrush brush = new SolidBrush(Color.Red);
17       Graphics formGraphics = form.CreateGraphics();
18       formGraphics.FillRectangle(brush,
19         new System.Drawing.Rectangle(
20           new Point(0, 0),
21           new Size(geometric.Width, geometric.Height)));
22     }
23   }

然后,如果我們再對 Rectangle 中渲染職責進行更改時將不會再影響到 ComputationalGeometryApplication 了。

 1   public class ComputationalGeometryApplication
 2   {
 3     public double CalculateArea(GeometricRectangle geometric)
 4     {
 5       double area = geometric.Area();
 6       return area;
 7     }
 8   }
 9 
10   public class GraphicalApplication
11   {
12     public Form form { get; set; }
13 
14     public void DrawOnScreen(Rectangle rectangleDraw, GeometricRectangle rectangleShape)
15     {
16       rectangleDraw.Draw(form, rectangleShape);
17     }
18   }

那么,職責(Responsibility)到底是什么?

單一職責原則(SRP:Single Responsibility Principle)的概念中,我們將職責(Responsibility)定義為 "一個變化的原因(a reason for change)"。如果你能想出多于一種動機來更改一個類,則這個類就包含多于一個職責。

職責的耦合有時很難被發現,因為我們習慣于將多個職責一起來考慮。例如,我們考慮下面定義的 Camera 接口,可能會認為這個接口看起來是非常合理的。接口中聲明的 4 個方法從屬于一個 Camera 接口定義。

1   public interface Camera
2   {
3     void Connect(string host);
4     void Disconnect();
5     void Send(byte[] data);
6     byte[] Receive();
7   }

然而,它確實耦合了 2 個職責。第一個職責是連接管理,第二個職責是數據通信。Connect 和 Disconnect 方法負責管理 Camera 與管理端 Host 的連接,而 Send 和 Receive 方法則負責收發通信數據。

這兩個職責應該被分離嗎?答案基本上是肯定的。這兩組方法基本上沒有任何交集,它們都可以依據不同的原因而變化。進一步說,它們將在應用程序中完全不同的位置被調用,而那些不同的位置將同樣會因不同的原因而變化。

因此,下圖中的設計可能會好一些。它將這兩個職責分別隔離到不同的接口定義中,這至少使應用程序從兩個職責中解耦。

然而,我們注意到這兩個職責又重新被耦合進了一個 CameraImplementation 類中。這可能不是我們想要的,但卻有可能是必須的。通常有很多原因會強制我們將一些職責耦合在一起。盡管如此,我們使得應用程序的其他部分得益于這個接口的隔離。

CameraImplementation 類在我們看來是一個組合出來的但確實包含一些缺點的類。但需要注意到的是,所有其他需要使用 CameraImplementation 類的地方已經可以被接口進行隔離,我們僅需依賴所定義的單一職責的接口。而 CameraImplementation 僅在被實例化的位置才會出現。我們將丑陋的代碼限制在一定的范圍內,而不會泄露或污染應用程序的其他部分。

總結

單一職責原則(SRP:Single Responsibility Principle)可表述為 "一個類應該有且只有一個變化的原因(There should never be more than one reason for a class to change.)"。單一職責原則是一個非常簡單的原則,但通常也是最難做的正確的一個原則。職責的聯合是在實踐中經常碰到的事情,從這些各不相同的職責中發現并隔離職責就是軟件設計的真諦所在。我們所討論的其他設計原則最終也會回歸到這個原則上來。

 

面向對象設計的原則

 SRP

 單一職責原則

 Single Responsibility Principle

 OCP

 開放封閉原則

 Open Closed Principle

 LSP

 里氏替換原則

 Liskov Substitution Principle

 ISP

 接口分離原則

 Interface Segregation Principle

 DIP

 依賴倒置原則

 Dependency Inversion Principle

 LKP

 最少知識原則

 Least Knowledge Principle

參考資料

 

本文《單一職責原則(Single Responsibility Principle)》由 Dennis Gao 翻譯改編自 Robert Martin 的文章《SRP: The Single Responsibility Principle》,未經作者本人同意禁止任何形式的轉載,任何自動或人為的爬蟲行為均為耍流氓。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()