[Object-oriented] 相依性

摘要:[Object-oriented] 相依性

前言 :

寫程式的時候都會聽到說,要降低程式之間的相依性。

 

程式之間的「相依性」,可以用下面簡單的範例來理解。FunctionA裡面使用了FunctionB,當FunctionB功能變更的時候,FunctionA就必須跟著做修改。這也就是說,「FunctionA相依FunctionB」。

 

 


static void FunctionA()
{
    FunctionB();
}

static void FunctionB()
{

}

以上面這個範例看起來,相依性不會是很大的問題,改就是了。但是當我們把問題放大,假設系統裡有1000個Function。Function之間互相相依。當要更改1個Function內容並且維持整個系統正確運作,就必須要去檢查其他999個Function是否需要跟著修改。這樣的修改會是一場災難,也是造成很多軟體系統,改這邊壞那邊的主要原因。所以減少相依性,是提高程式碼品質很重要的一個環節。

 

在「結構化導向程式設計」Function與Function之間的相依性,可以透過追蹤程式碼的方式來識別。但到了「物件導向程式設計」的環境下,雖然說也是可以使用追蹤程式碼的方式,來識別物件之間的相依性。但卻會受到繼承、介面等等物件導向特性的影響,讓識別物件之間相依性的這件工作變得非常複雜。

 

 

本篇使用UML類別圖裡的「關係」當作工具,闡述該如何透過UML類別圖裡的「關係」,來輔助開發人員識別物件導向程式的物件相依性。

 

 

說明 :

 

識別物件相依性一個簡單的方法,就是「當兩個類別A與B,分別存在不同的專案內。大幅修改了B之後,A如果需要跟著重新編譯,那就是A相依B」。我們依照這個原則,來檢視UML類別圖裡所定義的各種關係:

 

關聯關係(Association)

 


using Association.ProjectB;

namespace Association.ProjectA
{
    public class ClassA
    {
        private readonly ClassB _b;

        public ClassA(ClassB b)
        {
            _b = b;
        }

        public ClassB B { get { return _b; } }
    }
}

 


namespace Association.ProjectB
{
    public class ClassB
    {

    }
}

上圖與範例程式是UML類別圖裡定義的關聯關係,代表圖形與範例程式。依照原則去檢視程式碼可以看出,當變更ClassB的時候,ClassA需要跟著重新編譯。所以ClassA相依於ClassB。

 

依賴關係(Dependency)

 


using Dependency.ProjectB;

namespace Dependency.ProjectA
{
    public class ClassA
    {
        public void MethodXXX(ClassB b)
        {
            // TODO
        }
    }
}

 


namespace Dependency.ProjectB
{
    public class ClassB
    {

    }
}

上圖與範例程式是UML裡定義的依賴關係,代表圖形與範例程式。依照原則去檢視程式碼可以看出,當變更ClassB的時候,ClassA需要跟著重新編譯。所以ClassA相依於ClassB。

 

概括關係(Generalization)

 


using Generalization.ProjectB;

namespace Generalization.ProjectA
{
    public class ClassA : ClassB
    {
        public ClassA()
            : base()
        {

        }
    }
}

 


namespace Generalization.ProjectB
{
    public class ClassB
    {

    }
}

上圖與範例程式是UML裡定義的概括關係,代表圖形與範例程式。依照原則去檢視程式碼可以看出,當變更ClassB的時候,ClassA需要跟著重新編譯。所以ClassA相依於ClassB。

 

實現關係(Realization)

 


using Realization.ProjectB;

namespace Realization.ProjectA
{
    public class ClassA : IntefaceB
    {

    }
}

 


namespace Realization.ProjectB
{
    public interface IntefaceB
    {

    }
}

上圖與範例程式是UML裡定義的實現關係,代表圖形與範例程式。依照原則去檢視程式碼可以看出,當變更IntefaceB的時候,ClassA需要跟著重新編譯。所以ClassA相依於IntefaceB。

 

聚合關係(Aggregate)

 


using Aggregate.ProjectB;

namespace Aggregate.ProjectA
{
    public class ClassA
    {
        private readonly ItemB[] _itemBCollection;

        public ClassA()
        {
            _itemBCollection = new ItemB[2];
            _itemBCollection[0] = new ItemB();
            _itemBCollection[1] = new ItemB();
        }

        public ItemB[] ItemBCollection { get { return _itemBCollection; } }
    }
}

 


namespace Aggregate.ProjectB
{
    public class ItemB
    {

    }
}

上圖與範例程式是UML裡定義的聚合關係,代表圖形與範例程式。依照原則去檢視程式碼可以看出,當變更ItemB的時候,ClassA需要跟著重新編譯。所以ClassA相依於ItemB。
*聚合關係語意較為複雜,範例程式只是簡單示意。

 

組成關係(Composition)

 


using Composition.ProjectB;

namespace Composition.ProjectA
{
    public class ClassA
    {
        private readonly ItemB[] _itemBCollection;

        public ClassA(ItemB[] itemBCollection)
        {
            _itemBCollection = itemBCollection;
        }

        public ItemB[] ItemBCollection { get { return _itemBCollection; } }
    }
}

 


namespace Composition.ProjectB
{
    public class ItemB
    {

    }
}

上圖與範例程式是UML裡定義的組成關係,代表圖形與範例程式。依照原則去檢視程式碼可以看出,當變更ItemB的時候,ClassA需要跟著重新編譯。所以ClassA相依於ItemB。
*組成關係語意較為複雜,範例程式只是簡單示意。

 

後記 :

 

本篇的文章描述了,如何透過UML類別圖裡的「關係」,來輔助開發人員識別物件導向程式的物件相依性。當將物件依照職責分類成為獨立Package的時候,物件之間的相依性也需要考慮進去,避免在Package之間有相依性雜亂的問題。當發現相依性雜亂時,則可以透過IoC、Facade等等手法來整理相依性。透過不斷的整理相依性,就能慢慢提高程式碼的品質。

 

參考資料 :

 

維基百科 - 類別圖

期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。