敏捷式軟體開發 (Agile Software Development):原則、模式與實踐——第9章 OCP:開放-封閉原則

來源:互聯網
上載者:User

標籤:

第9章 OCP:開放-封閉原則

  軟體實體(類、模組、函數等)應該是可以擴充的,但是不可修改。


9.1 OCP概述

  遵循開放-封閉原則設計出的模組具有兩個主要特徵:

  (1)對於擴充是開放的(open for extension)。這意味著模組的行為是可以擴充的。當應用的需求改變時,我們可以對模組進行擴充,使其具有滿足那些改變的新行為。

  (2)對於修改是封閉的(closed for modification)。對模組進行擴充時,不必改動模組的原始碼或者二進位代碼。模組的二進位可執行版本,無論是可連結的庫、DLL或者.EXE檔案,都無需改動。

  在C#或者其他任何OOPL(物件導向程式設計語言)中,可以建立出固定卻能夠描述一組任意個可能行為的抽象體。這個抽象體就是抽象基類。而這一組任意個可能的行為則表現為可能的衍生類別。

  模組可能對抽象體進行操作。由於模組依賴於一個固定的抽象體,所以它對於更改可以是封閉的。同時,通過從這個抽象體派生,可以擴充此模組的行為。

  
9.2 Shape應用程式

9.2.1 違反OCP

  查看如下代碼:

//--shape.h---------------------------------------    enum ShapeType    {        circle,        square    };    struct Shape    {        ShapeType itsType;    };//--circle.h---------------------------------------    struct Circle    {        ShapeType itsType;        double itsRadius;        Point itsCenter;    };    void DrawCircle(struct Circle*);//--square.h---------------------------------------    struct Square    {        ShapeType itsType;        double itsSide;        Point itsTopLeft;    };    void DrawSquare(struct Square*);//--drawAllShapes.cc-------------------------------    typedef struct Shape *private ShapePointer ;    void DrawAllShapes(ShapePointer list[]private ,private int n )    {        int i;        for (i = 0; i < n; i++)        {          struct Shape* s = list[i];            switch (s->itsType)            {                case square:                    DrawSquare((struct Square* )s);                    break;                case circle:                    DrawCircle((struct Circle* )s);                    break;            }        }    }

 

  DrawAllShapes函數不符合OCP,因為它對於新的形狀類型的添加不是封閉的。如果希望這個函數能夠繪製包含三角形的列表,就必須變更這個函數。事實上,每增加一種新的形狀類型,都必須要更改這個函數。

  
9.2.2 遵循OCP  

  查看如下Square/Circle問題的OOD解決方案

    public interface Shape    {        void Draw();    }    public class Square : Shape    {        public void Draw()        {            //draw a square        }    }    public class Circle : Shape    {        public void Draw()        {            //draw a circle        }    }    public void DrawAllShapes(IList shapes)    {        foreach (Shape shape in shapes)            shape.Draw();    }

 


9.2.3 預測變化和“貼切的”結構

  一般而言,無論模組是多麼的“封閉”,都會存在一些無法對之封閉的變化。沒有對於所有的情況都貼切的模型。

  既然不能完全封閉,那麼就必須有策略的對待這個問題。也就是說,設計人員必須對於他設計的模組應該對哪種變化封裝做出選擇。他必須先猜測出最有可能發生變化的類,然後構造抽象來隔離那些變化。

  這需要設計人員具有一些從經驗中獲得的預測能力。有經驗的設計人員希望自己對使用者和應用領域很瞭解,能夠以此來判斷各種變化的可能性。然後,它可以讓設計對於最有可能發生的變化遵循OCP原則。

  這一點不容易做到。並且在大多數情況下,他們都會猜測錯誤。

  遵循OCP的代價也是昂貴的。建立適當的抽象是要花費開發時間和精力的。同時,那些抽象也增加了軟體設計的複雜性。

  最終,我們會一直等到變化發生時才採取行動!


9.2.4 放置吊鉤

  在上世紀,我們會在我們認為可能發生變化的地方“放置吊鉤”(hook)。我們覺得這樣會使軟體靈活一些。

  然而,我們放置的吊鉤常常是錯誤的。更糟的是,即使不使用這些吊鉤,也必須要去支援和維護它們,從而就有了不必要的複雜性的臭味。通常,我們更願意一直等到卻是需要那些抽象時再把它放置進去。


9.2.5 使用抽象獲得顯式封閉

  封閉是建立在抽象的基礎上的。因此,為了讓DrawAllShapes對於繪製順序的變化是封閉的。我們需要一種“順序抽象體”。這個抽象體定義了一個抽象介面,通過這個介面可以表示任何可能的排序策略。

  一個排序策略意味著,給定兩個對象,可以推匯出先繪製哪一個。C#提供了這樣的抽象。IComparable是一個介面,它只提供一個方法:CompareTo。這個方法以一個對象作為輸入參數,當接受訊息的對象小於、等於、大於參數數對象時,該方法分別返回-1,0,1 。

如果希望Circle先於Square繪製,查看如下代碼:

    public interface Shape : IComparable    {        void Draw();    }    public class Square : Shape    {        public void Draw()        {            //draw a square        }        public int CompareTo(object obj)        {            if (obj is Circle)            {                return 1;            }            else            {                return 0;            }        }    }    public class Circle : Shape    {        public void Draw()        {            //draw a circle        }        public int CompareTo(object obj)        {            if (obj is Square)            {                return -1;            }            else            {                return 0;            }        }    }    public void DrawAllShapes(ArrayList shapes)    {        shapes.Sort();        foreach (Shape shape in shapes)            shape.Draw();    }

 

對於這樣的代碼:

        public int CompareTo(object obj)        {            if (obj is Square)            {                return -1;            }            else            {                return 0;            }        }

 

顯然不符合OCP。每次建立一個新的Shape類的衍生類別時,所有的CompareTo()函數都需要改動。


9.2.6 使用“資料驅動”的方法擷取封閉性  
  如果我們不要使Shape類的各個衍生類別之間互不知曉,可以使用表格驅動的方法。表格驅動的形狀排序機制:

/// <summary>/// This comparer will search the priorities/// hashtable for a shape‘s type. The priorities/// table defines the odering of shapes. Shapes/// that are not found precede shapes that are found./// </summary>public class ShapeComparer : IComparer{    private static Hashtable priorities = new Hashtable();    static ShapeComparer()    {        priorities.Add(typeof(Circle), 1);        priorities.Add(typeof(Square), 2);    }    private int PriorityFor(Type type)    {        if(priorities.Contains(type))            return (int)priorities[type];        else            return 0;    }    public int Compare(object o1, object o2)    {        int priority1 = PriorityFor(o1.GetType());        int priority2 = PriorityFor(o2.GetType());        return priority1.CompareTo(priority2);    }}

 

修改DrawAllShapes方法:

public void DrawAllShapes(ArrayList shapes){    shapes.Sort(new ShapeComparer());    foreach(Shape shape in shapes)        shape.Draw();}

 


9.3 結論

  在許多方面,OCP都是物件導向設計的核心所在。遵循這個原則可以帶來物件導向技術所聲稱的巨大好處:靈活性、可重用性以及靈活性。然而,並不是說使用一種物件導向的語言就是遵循了這個原則。對於應用程式中的每個部分都肆意地進行抽象同樣不是一個好主意。正確的做法是,開發人員僅僅對程式中出現頻繁變化的那些部分作出抽象。拒絕不成熟的抽象和抽象本身一樣重要。

 

 

摘自:《敏捷式軟體開發 (Agile Software Development):原則、模式與實踐(C#版)》Robert C.Martin    Micah Martin 著

轉載請註明出處:

JesseLZJ
出處:http://jesselzj.cnblogs.com

敏捷式軟體開發 (Agile Software Development):原則、模式與實踐——第9章 OCP:開放-封閉原則

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.