前言 :
写程序的时候都会听到说,要降低程序之间的相依性。
程序之间的「相依性」,可以用下面简单的范例来理解。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等等手法来整理相依性。透过不断的整理相依性,就能慢慢提高程序代码的品质。
参考数据 :