單一職責原則(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:The Single Responsibility Principle by Robert C. Martin “Uncle Bob”
- The SOLID Principles, Explained with Motivational Posters
- Dangers of Violating SOLID Principles in C#
- An introduction to the SOLID principles of OO design
- 10 Golden Rules Of Good OOP
- 10 Object Oriented Design principles Java programmer should know
- SOLID Principles: Single Responsibility Principle
- Object Oriented Design Principles
本文《單一職責原則(Single Responsibility Principle)》由 Dennis Gao 翻譯改編自 Robert Martin 的文章《SRP: The Single Responsibility Principle》,未經作者本人同意禁止任何形式的轉載,任何自動或人為的爬蟲行為均為耍流氓。
文章列表
留言列表