自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面向?qū)ο笤O(shè)計(jì)原則

開(kāi)發(fā) 后端
CaculateArea方法只會(huì)進(jìn)行簡(jiǎn)單的數(shù)學(xué)運(yùn)算,而Draw方法則調(diào)用GUI組件實(shí)現(xiàn)繪制矩形的功能. 顯然,這個(gè)類就包含了兩個(gè)不同的職責(zé)了. 那這樣又會(huì)帶來(lái)什么問(wèn)題呢?

一 Single Responsibility Principle——單一職責(zé)原則

核心思想: 一個(gè)類應(yīng)該只有一個(gè)引起它變化的原因.

假設(shè)存在這樣的設(shè)計(jì). Rectangle類具有兩個(gè)方法,一個(gè)方法是計(jì)算矩形的面積 , 另一個(gè)方法是把矩形繪制在屏幕上.

CaculateArea方法只會(huì)進(jìn)行簡(jiǎn)單的數(shù)學(xué)運(yùn)算,而Draw方法則調(diào)用GUI組件實(shí)現(xiàn)繪制矩形的功能. 顯然,這個(gè)類就包含了兩個(gè)不同的職責(zé)了. 那這樣又會(huì)帶來(lái)什么問(wèn)題呢? 考慮這樣一個(gè)場(chǎng)景:現(xiàn)在有一個(gè)幾何學(xué)應(yīng)用程序調(diào)用了這一個(gè)類,已便實(shí)現(xiàn)計(jì)算面積的功能,在這個(gè)程序中不需要用到繪制矩形的功能. 問(wèn)題一:部署幾何應(yīng)用程序需要把GUI組件一同部署,而且這個(gè)組件根本沒(méi)有使用到.問(wèn)題二:對(duì)Rectangle類的改變,比如Draw方法改用另外一套GUI組件,必須對(duì)幾何應(yīng)用程序進(jìn)行一次重新部署.

可見(jiàn),一個(gè)類如果承擔(dān)的職責(zé)過(guò)多,就等于把職責(zé)耦合在一起了,容易導(dǎo)致脆弱的設(shè)計(jì),帶來(lái)額外的麻煩. 在實(shí)際開(kāi)發(fā)中, 業(yè)務(wù)規(guī)則的處理和數(shù)據(jù)持久化一般是不同時(shí)存在同一個(gè)類中的,業(yè)務(wù)規(guī)則往往會(huì)頻繁地變化,而持久化的方式卻不會(huì)經(jīng)常性地變化.如果這兩個(gè)職責(zé)混合在同一個(gè)類中,業(yè)務(wù)規(guī)則頻繁變化導(dǎo)致類的修改,只調(diào)用持久化方法的類也必須跟著重新編譯,部署的次數(shù)常常會(huì)超過(guò)我們希望的次數(shù). 對(duì)業(yè)務(wù)規(guī)則和持久化任務(wù)的職責(zé)分離就是遵循單一職責(zé)原則的體現(xiàn).

對(duì)上述Recangle類可進(jìn)行這樣的修改:

二 Open Closed Principle——開(kāi)放封閉原則

核心思想:對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉.

"需求總是變化的." 擁抱變化似乎就是軟件開(kāi)發(fā)的真理之一. 經(jīng)常會(huì)有這樣令人沮喪的情景出現(xiàn):新的需求來(lái)了,對(duì)不起,我的代碼設(shè)計(jì)必須大幅度推倒重來(lái). 設(shè)計(jì)的壞味道讓我們深受其害,那么怎樣的設(shè)計(jì)才能面對(duì)需求的改變卻可以保持相對(duì)穩(wěn)定呢?

針對(duì)這樣的問(wèn)題,OCP給了我們?nèi)缦碌慕ㄗh:在發(fā)生變化的時(shí)候,不要修改類的源代碼,要通過(guò)添加新代碼來(lái)增強(qiáng)現(xiàn)有類的行為.

對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉,這兩個(gè)特征似乎就是相互矛盾的. 通常觀念來(lái)講,擴(kuò)展不就是修改源代碼嗎?怎么可能在不改動(dòng)源代碼的情況下去更改它的行為呢?

答案就是抽象(Interface 和 抽象基類).實(shí)現(xiàn)OCP的核心思想就是對(duì)抽象編程. 讓類依賴于固定的抽象,對(duì)修改就是封閉的; 而通過(guò)面向?qū)ο蟮睦^承和多態(tài)機(jī)制,通過(guò)覆寫(xiě)方法改變固有行為,實(shí)現(xiàn)新的擴(kuò)展方法,對(duì)于擴(kuò)展就是開(kāi)放的.

來(lái)看一個(gè)例子. 實(shí)現(xiàn)一個(gè)能夠根據(jù)客戶端的調(diào)用要求繪制圓形和長(zhǎng)方形的應(yīng)用程序. 初始設(shè)計(jì)如下:

  1. public class Draw  
  2. {  
  3.     public void DrawRectangle()  
  4.     {  
  5.         //繪制長(zhǎng)方形  
  6.     }  
  7.  
  8.     public void DrawCircle()  
  9.     {  
  10.         //繪制圓形  
  11.     }  
  12. }  
  13.  
  14. public enum Sharp  
  15. {  
  16.     /// <summary>  
  17.     /// 長(zhǎng)方形  
  18.     /// </summary>  
  19.     Rectangle ,  
  20.  
  21.     /// <summary>  
  22.     /// 圓形  
  23.     /// </summary>  
  24.     Circle ,  
  25. }  
  26.  
  27. public class DrawProcess  
  28. {  
  29.     private Draw _draw = new Draw();  
  30.  
  31.     public void Draw(Sharp sharp)  
  32.     {  
  33.         switch (sharp)  
  34.         {  
  35.             case Sharp.Rectangle:  
  36.                 _draw.DrawRectangle();  
  37.                 break;  
  38.             case Sharp.Circle:  
  39.                 _draw.DrawCircle();  
  40.                 break;  
  41.             default:  
  42.                 throw new Exception("調(diào)用出錯(cuò)!");  
  43.         }  
  44.     }  
  45. }  
  46.  
  47. //調(diào)用代碼  
  48. DrawProcess draw = new DrawProcess();  
  49. draw.Draw(Sharp.Circle); 

現(xiàn)在的代碼可以正確地運(yùn)行. 一切似乎都趨近于理想. 然而,需求的變更總是讓人防不勝防. 現(xiàn)在程序要求要實(shí)現(xiàn)可以繪制正方形. 在原本的代碼設(shè)計(jì)下,必須做如下的改動(dòng).

  1. //在Draw類中添加  
  2. public void DrawSquare()  
  3. {  
  4.      //繪制正方形  
  5. }  
  6.  
  7. //在枚舉Sharp中添加  
  8.  /// <summary>  
  9.  /// 正方形  
  10.  /// </summary>  
  11.  Square ,  
  12.  
  13. //在DrawProcess類的switch判斷中添加  
  14. case Sharp.Square:  
  15.      _draw.DrawSquare();  
  16.      break

需求的改動(dòng)產(chǎn)生了一系列相關(guān)模塊的改動(dòng),設(shè)計(jì)的壞味道悠然而生. 現(xiàn)在運(yùn)用OCP, 來(lái)看一下如何對(duì)代碼進(jìn)行一次重構(gòu).

  1. /// <summary>  
  2. /// 繪制接口  
  3. /// </summary>  
  4. public interface IDraw  
  5. {  
  6.     void Draw();  
  7. }  
  8.  
  9. public class Circle:IDraw  
  10. {  
  11.     public void Draw()  
  12.     {  
  13.         //繪制圓形  
  14.     }  
  15. }  
  16.  
  17. public class Rectangle:IDraw  
  18. {  
  19.     public void Draw()  
  20.     {  
  21.         //繪制長(zhǎng)方形  
  22.     }  
  23. }  
  24.  
  25. public class DrawProcess  
  26. {  
  27.     private IDraw _draw;  
  28.  
  29.     public IDraw Draw { set { _draw = value; } }  
  30.       
  31.     private DrawProcess() { }  
  32.  
  33.     public DrawProcess(IDraw draw)  
  34.     {  
  35.         _draw = draw;  
  36.     }  
  37.  
  38.     public void DrawSharp()  
  39.     {  
  40.         _draw.Draw();  
  41.     }  
  42. }   
  43.  
  44.  
  45. //調(diào)用代碼  
  46. IDraw circle = new Circle();  
  47. DrawProcess draw = new DrawProcess(circle);  
  48. draw.DrawSharp(); 

假如現(xiàn)在需要有繪制正方形的功能,則只需添加一個(gè)類Square 即可.

  1. public class Square:IDraw  
  2. {  
  3.     public void Draw()  
  4.     {  
  5.         //繪制正方形  
  6.     }  

只需新增加一個(gè)類且對(duì)其他的任何模塊完全沒(méi)有影響,OCP出色地完成了任務(wù).

如果一開(kāi)始就采用第二種代碼設(shè)計(jì),在需求的暴雨來(lái)臨時(shí),你會(huì)欣喜地發(fā)現(xiàn)你已經(jīng)到家了, 躲過(guò)了被淋一身濕的悲劇. 所以在一開(kāi)始設(shè)計(jì)的時(shí)候,就要時(shí)刻地思考,根據(jù)對(duì)應(yīng)用領(lǐng)域的理解來(lái)判斷最有可能變化的種類,然后構(gòu)造抽象來(lái)隔離那些變化. 經(jīng)驗(yàn)在這個(gè)時(shí)候會(huì)顯得非常寶貴,可能會(huì)幫上你的大忙.

OCP很美好,然而絕對(duì)的對(duì)修改關(guān)閉是不可能的,都會(huì)有無(wú)法對(duì)之封閉的變化. 同時(shí)必須清楚認(rèn)識(shí)到遵循OCP的代價(jià)也是昂貴的,創(chuàng)建適當(dāng)?shù)某橄笫且ㄙM(fèi)開(kāi)發(fā)時(shí)間和精力的. 如果濫用抽象的話,無(wú)疑引入了更大的復(fù)雜性,增加維護(hù)難度.

三 Liskov Subsitution Principle——里氏替換原則

核心思想: 子類必須能夠替換掉它們的父類型.

考慮如下情況: 

  1. public class ProgrammerToy  
  2. {  
  3.     private int _state;  
  4.  
  5.     public  int State  
  6.     {  
  7.         get { return _state; }  
  8.     }  
  9.  
  10.     public virtual void SetState(int state)  
  11.     {  
  12.         _state = state;  
  13.     }  
  14. }  
  15.  
  16. public class CustomProgrammerToy:ProgrammerToy  
  17. {  
  18.     public override void SetState(int state)  
  19.     {  
  20.         //派生類缺乏完整訪問(wèn)能力,即無(wú)法訪問(wèn)父類的私有成員_state  
  21.         //因此該類型也許不能完成其父類型能夠滿足的契約  
  22.     }  
  23. }   
  24.  
  25.  
  26.  
  27.  
  28. //控制臺(tái)應(yīng)用程序代碼  
  29. class Program  
  30. {  
  31.     static void Main(string[] args)  
  32.     {  
  33.         ProgrammerToy toy = new CustomProgrammerToy();  
  34.         toy.SetState(5);  
  35.         Console.Write(toy.State.ToString());  
  36.     }  

從語(yǔ)法的角度來(lái)看, 代碼沒(méi)有任何問(wèn)題. 不過(guò)從行為的角度來(lái)看 , 二者卻存在不同. 在使用CustomProgrammerToy替換父類的時(shí)候, 輸出的是0而不是5, 與既定的目標(biāo)相差千里. 所以不是所有的子類都能安全地替換其父類使用. 

前面談到的開(kāi)發(fā)封閉原則和里氏替換原則存在著密切的關(guān)系. 實(shí)現(xiàn)OCP的核心是對(duì)抽象編程, 由于子類型的可替換性才使得使用父類類型的模塊在無(wú)需修改的情況下就可以擴(kuò)展, 所以違反了里氏替換原則也必定違反了開(kāi)放封閉原則.

慶幸的是, 里氏替換原則還是有規(guī)律可循的.父類盡可能使用接口或抽象類來(lái)實(shí)現(xiàn),同時(shí)必須從客戶的角度理解,按照客戶程序的預(yù)期來(lái)保證子類和父類在行為上的相容.

四 InterFace Segregation Principle——接口隔離原則

核心思想:使用多個(gè)小的專門的接口,而不要使用一個(gè)大的總接口.

直接來(lái)看一個(gè)例子: 假設(shè)有一個(gè)使用電腦的接口

            

程序員類實(shí)現(xiàn)接口IComputerUse, 玩游戲,編程,看電影, 多好的事情.

現(xiàn)在有一個(gè)游戲發(fā)燒友,他也要使用電腦, 為了重用代碼 , 實(shí)現(xiàn)OCP, 他也實(shí)現(xiàn)接口IComputerUse

 

看出什么問(wèn)題了嗎? GamePlayer PlayGame無(wú)可厚非,WatchMovies小消遣, 但要編程干什么?

這就是胖接口帶來(lái)的弊端,會(huì)導(dǎo)致實(shí)現(xiàn)的類必須完全實(shí)現(xiàn)接口的所有方法, 而有些方法對(duì)客戶來(lái)說(shuō)是無(wú)任何用處的,在設(shè)計(jì)上這是一種"浪費(fèi)". 同時(shí),如果對(duì)胖接口進(jìn)行修改, 比如程序員要使用電腦配置為服務(wù)器, 在IComputerUse上添加Server方法, 同樣GamePlayer也要修改(這種修改對(duì)GamePlayer是毫無(wú)作用的),是不是就引入了額外的麻煩?

所以應(yīng)該避免出現(xiàn)胖接口,要使接口實(shí)現(xiàn)高內(nèi)聚(高內(nèi)聚是指一個(gè)模塊中各個(gè)部分都是為完成一項(xiàng)具體功能而協(xié)同工作,緊密聯(lián)系,不可分割). 當(dāng)出現(xiàn)了胖接口,就要考慮重構(gòu).優(yōu)先推薦的方法是使用多重繼承分離,即實(shí)現(xiàn)小接口.

將IComputerUse拆分為IComputerBeFun和IComputerProgram, Progammer類則同時(shí)實(shí)現(xiàn)IComputerBeFun和IComputerProgram接口,現(xiàn)在就各取所需了.

與OCP類似, 接口也并非拆分地越小越好, 因?yàn)樘嗟慕涌跁?huì)影響程序的可讀性和維護(hù)性,帶來(lái)難以琢磨的麻煩. 所以設(shè)計(jì)接口的時(shí)刻要著重考慮高內(nèi)聚性, 如果接口中的方法都?xì)w屬于同一個(gè)邏輯劃分而協(xié)同工作,那么這個(gè)接口就不應(yīng)該再拆分.

五 Dependency Inversion Principle——依賴倒置原則

核心思想: 高層模塊不應(yīng)該依賴底層模塊,兩者都應(yīng)該依賴抽象。抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。

當(dāng)一個(gè)類A存在指向另一個(gè)具體類B的引用的時(shí)候,類A就依賴于類B了。如:

  1. /// <summary>  
  2. /// 商品類  
  3. /// </summary>  
  4. public class Product  
  5. {  
  6.     public int Id { get; set; }  
  7. }  
  8.  
  9. /// <summary>  
  10. /// 商品持久化類  
  11. /// </summary>  
  12. public class ProductRepository  
  13. {  
  14.     public IList<Product> FindAll()  
  15.     {  
  16.         //假設(shè)從SQL Server數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)  
  17.         return null;  
  18.     }  
  19. }  
  20.  
  21. /// <summary>  
  22. /// 商品服務(wù)類  
  23. /// </summary>  
  24. public class ProductService  
  25. {  
  26.     private ProductRepository _productRepository;  
  27.  
  28.     public IList<Product> GetProducts()  
  29.     {  
  30.         _productRepository = new ProductRepository();  
  31.  
  32.         return _productRepository.FindAll();  
  33.     }  

(在前面單一職責(zé)原則中有提到,業(yè)務(wù)邏輯處理和對(duì)象持久化分屬兩個(gè)職責(zé),所以應(yīng)該拆分為兩個(gè)類。)高層模塊ProductService類中引用了底層模塊具體類ProductRepository,所以ProductService類就直接依賴于ProductRepository了。那么這樣的依賴會(huì)帶來(lái)什么問(wèn)題呢?

"需求總是那么不期而至"。原本ProductRepository是從SQL Server數(shù)據(jù)庫(kù)中讀存數(shù)據(jù),現(xiàn)在要求從MySQL數(shù)據(jù)庫(kù)中讀存數(shù)據(jù)。由于高層模塊依賴于底層模塊,現(xiàn)在底層模塊ProductRepository發(fā)生了更改,高層模塊ProductService也需要跟著一起修改,回顧之前談到的設(shè)計(jì)原則,這是不是就違反了OCP呢?OCP的核心思想是對(duì)抽象編程,DIP的思想是依賴于抽象,這也讓我們更清楚地認(rèn)識(shí)到,面向?qū)ο笤O(shè)計(jì)的時(shí)候,要綜合所有的設(shè)計(jì)原則考慮。DIP給出了解決方案:在依賴之間定義一個(gè)接口,使得高層模塊調(diào)用接口,而底層模塊實(shí)現(xiàn)接口,以此來(lái)控制耦合關(guān)系。(在上面OCP的例子中,也是使用了這一個(gè)方法。)所以可以對(duì)代碼做如下的重構(gòu):

  1. View Code   
  2.  
  3. /// <summary>  
  4. /// 商品持久化接口  
  5. /// </summary>  
  6. public interface IProductRepository  
  7. {  
  8.     List<Product> FindAll();  
  9. }  
  10.  
  11. /// <summary>  
  12. /// 商品持久化類  
  13. /// </summary>  
  14. public class ProductRepository:IProductRepository  
  15. {  
  16.     public IList<Product> FindAll()  
  17.     {  
  18.         //假設(shè)從SQL Server數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)  
  19.         return null;  
  20.     }  
  21. }  
  22.  
  23. /// <summary>  
  24. /// 商品服務(wù)類  
  25. /// </summary>  
  26. public class ProductService  
  27. {  
  28.     private IProductRepository _productRepository;  
  29.  
  30.     private ProductService() { }  
  31.  
  32.     //使用構(gòu)造函數(shù)依賴注入  
  33.     public ProductService(IProductRepository productRepository)  
  34.     {  
  35.         _productRepository = productRepository;  
  36.     }  
  37.  
  38.     public IList<Product> GetProducts()  
  39.     {  
  40.         return _productRepository.FindAll();  
  41.     }  

現(xiàn)在已對(duì)變化進(jìn)行了抽象隔離,再根據(jù)OCP,我相信實(shí)現(xiàn)從MySQL數(shù)據(jù)庫(kù)中讀存數(shù)據(jù)的需求已經(jīng)可以被輕松地解決掉了。

責(zé)任編輯:張偉 來(lái)源: 博客園
相關(guān)推薦

2012-06-07 10:11:01

面向?qū)ο?/a>設(shè)計(jì)原則Java

2024-05-10 09:28:57

Python面向?qū)ο?/a>代碼

2009-09-27 14:12:12

面向?qū)ο笤O(shè)計(jì)單一職責(zé)

2012-05-08 10:14:45

設(shè)計(jì)原則

2009-01-16 08:52:26

面向?qū)ο?/a>OOP編程

2009-06-30 15:29:00

Java面向?qū)ο?/a>

2011-07-12 17:53:21

PHP

2022-09-28 07:31:03

SOLID對(duì)象設(shè)計(jì)

2015-03-16 11:14:26

Java程序員面向?qū)ο?/a>程序員

2012-03-13 09:24:30

Java

2015-10-29 09:30:38

程序員面向?qū)ο?/a>設(shè)計(jì)

2022-04-01 10:27:04

面向?qū)ο?/a>串口協(xié)議代碼

2020-06-09 07:00:00

面向?qū)ο?/a>編程編程原則

2018-05-03 15:54:19

2011-03-04 13:29:13

海量服務(wù)設(shè)計(jì)原則

2023-01-10 09:38:09

面向對(duì)象系統(tǒng)

2019-09-18 18:56:34

JavascriptOOP前端

2021-11-23 20:41:05

對(duì)象軟件設(shè)計(jì)

2013-06-07 11:31:36

面向?qū)ο?/a>設(shè)計(jì)模式

2012-12-25 10:51:39

IBMdW
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)