摘要:[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等等手法來整理相依性。透過不斷的整理相依性,就能慢慢提高程式碼的品質。
參考資料 :
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。