软件设计中的原则(GRASP和OO的其他几大原则)——一种高层次的设计总则
这里说的几个软件模式是属于原则层次一级的,比GoF等软件设计模式高一层。遵循这些原则可以使我们设计出来的软件有更好的可复用性和可维护性,同样GoF等软件设计模式也是遵循这一原则的。
下边的条列只是简单的介绍,以便忘记了偶尔过来游览一下,详细的介绍请参阅:《Java模式》、《UML和模式应用-面向对象分析与设计导论》
GRASP模式
GRASP是GeneralResponsibilityAssignmentSoftwarePattern(通用指责分配软件模式)的缩写。
1)专家模式(Expert)
解决方案:将职责分配给具有履行职责所需要的信息的类
通俗点就是:该干嘛干嘛去,别管别人的闲事或者我的职责就是搞这个,别的事不管。
举个简单的例子,如果有一个类是专门处理字符串相关的类,那么这个类只能有字符串处理相关的方法,而不要将日期处理的方法加进来。也就是提高软件高内聚一种原则。
2)创建者(Creator)
解决方案:将创建一个类A的实例的职责指派给类B的实例,如果下列条件满足的话:
a)B聚合了A对象
b)B包含了A对象
c)B纪录了A对象的实例
d)B要经常使用A对象
e)当A的实例被创建时,B具有要传递给A的初始化数据(也就是说B是创建A的实例这项任务的信息专家)
f)B是A对象的创建者
如果以上条件中不止一条成立的话,那么最好让B聚集或包含A
通俗点就是:我要用你所以我来创建你,请不要让别人创建你
这个模式是支持低耦合度原则的一个体现
3)高聚合度或高内聚(HighCohesion)
解决方案:分配一个职责的时候要保持类的高聚合度
聚合度或内聚度(cohesion)是一个类中的各个职责之间相关程度和集中程度的度量。一个具有高度相关职责的类并且这个类所能完成的工作量不是特别巨大,那么他就是具有高聚合度。
4)低耦合度或低耦合(LowCoupling)
解决方案:在分配一个职责时要使保持低耦合度。
耦合度(coupling)是一个类与其它类关联、知道其他类的信息或者依赖其他类的强弱程度的度量。一个具有低(弱)耦合度的类不依赖于太多的其他类。
5)控制者(Controller)
解决方案:将处理系统事件消息的职责分派给代表下列事物的类:
a)代表整个“系统”的类(虚包控制者)
b)代表整个企业或组织的类(虚包控制者)
c)代表真实世界中参与职责(角色控制者)的主动对象类(例,一个人的角色)
d)代表一个用况中所有事件的人工处理者类,通常用“<用例名>处理者”的方式命名(用例控制者)
这是一个控制者角色职责分配的原则,就是哪些控制应该分派给哪个角色。
6)多态
当相关的可选择的方法或行为随着类型变化时,将行为的职责-使用多态的操作-分配给那些行为变化的类型
也就是说尽量对抽象层编程,用多态的方法来判断具体应该使用那个类,而不是用ifinstanceof来判断该类是什么接来执行什么。
7)纯虚构
一个纯虚构意味着虚构某些事物,而不是到了迫不得已我们才这样做。
例,我们的Sale类的数据要存入数据库,但是他必须和数据库接口相连接,如果将接口连接放入Sale类中势必增加该类的耦合度,所以我们可以虚构一个类来处理与数据库接口连接的问题。这个类就是我们虚构出来的一个事物。
8)中介者
将职责分配给一个中间对象以便在其他构件或服务之间仲裁,这样这些构件或服务没有被直接耦合。这个中间对象(intermediary)在其他构件或服务间创建一个中介者(Indirection)。这个中间对象也就事7)中的纯虚构。
9)不要和陌生人讲话
分配职责给一个客户端的直接对象以使它与一个间接对象进行协作,这样客户端无需知道这个间接对象。
这个模式-也被叫做(Demeter)准则。
通俗点就是:只与你直接的朋友们通信
不要跟“陌生人”说话
每个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
2.其他设计原则
1)“开-闭”原则(Open-ClosedPrinciple,或者OCP)
一个软件实体应当对扩展开放,对修改关闭。
意思就是在设计一个模块的时候,应当使这个模块在不被修改的前提下被扩展。换言之,应当可以在不修改代码的情况下改变这个模块的行为。
2)里氏代换原则(LiskovSubstitutionPrinciple,或者LSP)
这个就是尽量用多态的方法编程,也就是GRASP模式中的多态。
3)依赖倒转原则(DependencyInversionPrinciple,或者DIP)
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体
就是说我们尽量在抽象层进行控制编程,要针对接口编程,不要针对实现编程。
4)接口隔离原则(InterfaceSegregationPrinciple,或者ISP)
使用多个专门的接口比使用单一的总接口要好。也就是,从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小的接口上的。
5)组合/聚合复用原则(Composition/AggregationPrinciple,或者CARP)
又叫合成复用原则。原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象通过向这些对象的委派达到复用已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承
6)变与不变的分离
更扩展一步,就是将不同变化的组件进行隔离.最简单的例子就是javabean中的存取器。它隔离了不变的接口和变化的内部属性。这方面体现最好的个人觉得就是eclipse,通过变化的插件,eclipse可以用来实现任何功能。
oo原则另说6则:
1)Open-ClosePrinciple(OCP),开-闭原则,讲的是设计要对扩展有好的支持,而对修改要严格限制。这是最重要也是最为抽象的原则,基本上我们所说的ReusableSoftware既是基于此原则而开发的。其他的原则也是对它的实现提供了路径。
2)LiskovSubstituitionPrinciple(LSP),里氏代换原则,很严格的原则,规则是“子类必须能够替换基类,否则不应当设计为其子类。”也就是说,子类只能去扩展基类,而不是隐藏或覆盖基类.
3)DependenceInversionPrinciple(DIP),依赖倒换原则,“设计要依赖于抽象而不是具体化”。换句话说就是设计的时候我们要用抽象来思考,而不是一上来就开始划分我需要哪些哪些类,因为这些是具体。这样做有什么好处呢?人的思维本身实际上就是很抽象的,我们分析问题的时候不是一下子就考虑到细节,而是很抽象的将整个问题都构思出来,所以面向抽象设计是符合人的思维的。另外这个原则会很好的支持OCP,面向抽象的设计使我们能够不必太多依赖于实现,这样扩展就成为了可能,这个原则也是另一篇文章《DesignbyContract》的基石。
4)InterfaceSegregationPrinciple(ISP),“将大的接口打散成多个小接口”,这样做的好处很明显,我不知道有没有必要再继续描述了,为了节省篇幅,实际上我对这些原则只是做了一个小总结,如果有需要更深入了解的话推荐看《Java与模式》,MSMVP的一本巨作!^_^
5)Composition/AggregationReusePrinciple(CARP),设计者首先应当考虑复合/聚合,而不是继承(因为它很直观,第一印象就是“哦,这个就是OO啊”)。这个就是所谓的“FavorCompositionoverInheritance”,在实践中复合/聚合会带来比继承更大的利益,所以要优先考虑。
所知最少原则
6)LawofDemeterorLeastKnowlegdePrinciple(LoDorLKP),迪米特法则或最少知识原则,这个原则首次在Demeter系统中得到正式运用,所以定义为迪米特法则。它讲的是“一个对象应当尽可能少的去了解其他对象”。也就是又一个关于如何松耦合(Loosely-Coupled)的法则。
oo原则再5则说之SRP单一职责原则:
OO五大原则(1.SRP单一职责原则)
2010-10-0923:13
一点说明:OO的五大原则是指SRP、OCP、LSP、DIP、ISP。这五个原则是书中所提到的。除此之外,书中还提到一些高层次的原则用于组织高层的设计元素,这些放到下次再写。当然,OO设计的原则可能不止这五个,希望大家多提宝贵意见,多多交流。
在学习和使用OO设计的时候,我们应该明白:OO的出现使得软件工程师们能够用更接近真实世界的方法描述软件系统。然而,软件毕竟是建立在抽象层次上的东西,再怎么接近真实,也不能替代真实或被真实替代。
OO设计的五大原则之间并不是相互孤立的。彼此间存在着一定关联,一个可以是另一个原则的加强或是基础。违反其中的某一个,可能同时违反了其余的原则。因此应该把这些原则融会贯通,牢记在心!
1.SRP(SingleResponsibilityPrinciple单一职责原则)
单一职责很容易理解,也很容易实现。所谓单一职责,就是一个设计元素只做一件事。什么是“只做一件事”?简单说就是少管闲事。现实中就是如此,如果要你专心做一件事情,任何人都有信心可以做得很出色。但如果,你整天被乱七八糟的事所累,还有心思和精力把每件事都作好么?
fig-1.JPG
“单一职责”就是要在设计中为每种职责设计一个类,彼此保持正交,互不干涉。这个雕塑(二重奏)就是正交的一个例子,钢琴家和小提琴家各自演奏自己的乐谱,而结果就是一个和谐的交响乐。当然,真实世界中,演奏小提琴和弹钢琴的必须是两个人,但是在软件中,我们往往会把两者甚至更多搅和到一起,很多时候只是为了方便或是最初设计的时候没有想到。
这样的例子在设计中很常见,书中就给了一个很好的例子:调制解调器。这是一个调制解调器最基本的功能。但是这个类事实上完成了两个职责:连接的建立和中断、数据的发送和接收。显然,这违反了SRP。这样做会有潜在的问题:当仅需要改变数据连接方式时,必须修改Modem类,而修改Modem类的结果就是使得任何依赖Modem类的元素都需要重新编译,不管它是不是用到了数据连接功能。解决的办法,书中也已经给出:重构Modem类,从中抽出两个接口,一个专门负责连接、另一个专门负责数据发送。依赖Modem类的元素也要做相应的细化,根据职责的不同分别依赖不同的接口。最后由ModemImplementation类实现这两个接口。
fig-2.JPG
从这个例子中,我们不难发现,违反SRP通常是由于过于“真实”地设计了一个类所造成的。因此,解决办法是往更高一层进行抽象化提取,将对某个具体类的依赖改变为对一组接口或抽象类的依赖。当然,这个抽象化的提取应该根据需要设计,而不是盲目提取。比如刚才这个Modem的例子中,如果有必要,还可以把DataChannel抽象为DataSender和DataReceiver两个接口。