`
webdev2014
  • 浏览: 680398 次
文章分类
社区版块
存档分类
最新评论

面向对象的设计原则

 
阅读更多

面向对象设计原则与面性对象的三大特性是相辅相成的。是设计模式的灵魂所在。主要就是运用面向对象三大特性(抽象、继承、多态)与抽象,达到增强程序灵活性、松耦合与“代码复用最大化”的目的。

开放封闭原则

指软件实体(类、模块、函数等等)应该可以扩展,但是不能修改,即对于扩展是开放的,对于更改是关闭的,也就是说面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。

由于系统的需求不是一成不变的,开放封闭原则使得系统面对需求的改变可以保持相对稳定,从而是系统在后期易于拓展和维护,使得系统可以不断推出新的版本,变得越来越强大。

实现该原则的途径是通过抽象来隔离变化,通过继承、多态等来减少抽象类与子类的耦合。

开放封闭原则是面向对象设计的核心所在,是一切设计所要达到的最理想的结果。遵循这个原则可以充分发挥出面向对象技术的优势,即可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中频繁变化的那部分做出抽象,但对于程序中的每部分都刻意地进行抽象同样是不可取的。拒绝不成熟的抽象和抽象本身一样重要。

例如,下图中增加一个新的运算类

依赖倒转原则

抽象不应该依赖细节;细节应该依赖抽象。因为抽象是比较稳定的,而细节(具体需求)是经常需要变化的。说白了就是要针对接口编程而不要针对实现编程。比如PC中的主板、CPU、内存、硬盘都是在针对接口设计的。如果针对实现设计,内存集成到某个主板上,就会出现换内存必须换主板的尴尬。感觉,独立的器件(网卡、显卡等等)与集成的器件与主板之间的关系很像是面向对象与面向过程的关系。细细品味能感觉出面向对象技术的巨大优势。

依赖倒转其实就是彼此之间保持距离,相互独立,谁也不依赖谁,只知道彼此间约定的接口即可。很像三层架构中各层之间的关系。是面向对象的封装、(继承)抽象特性很好的体现。它的目的是得到高内聚、低耦合的目标程序,最终得到符合开闭原则的目标程序。

依赖倒转可以说是面向对象设计的标志,用哪种语言来编写程序并不重要,如果编写时考虑的都是如何针对抽象编程而不是正对细节编程,即程序中所有的依赖关系都是终止于抽象类或接口,那就是面向对象的设计,反之那就是过程化的设计。

里氏代换原则

任何基类可以出现的地方,子类一定可以出现;即一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象与子类对象的区别。里氏代换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。

里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。可以说正是有了里氏代换原则,才使开闭原则成为可能,因为由于子类型的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。

举例解释:

我们就看那个著名的长方形和正方形的例子。对于长方形的类,如果它的长宽相等,那么它就是一个正方形,因此,长方形类的对象中有一些正方形的对象。对于一个正方形的类,它的方法有个setSide和getSide,它不是长方形的子类,和长方形也不会符合里氏代换原则。

eg:

长方形类:

public class Rectangle{

...

setWidth(int width){

this.width=width;

}

setHeight(int height){

this.height=height

}

}

正方形类:

public class Square{

...

setWidth(int width){

this.width=width;

this. height=width;

}

setHeight(int height){

this.setWidth(height);

}

}

例子中改变边长的函数:

public void resize(Rectangle r){

while(r.getHeight()<=r.getWidth){

r.setHeight(r.getWidth+1);

}

}

那么,如果让正方形当做是长方形的子类,会出现什么情况呢?我们让正方形从长方形继承,然后在它的内部设置width等于height,这样,只要width或者height被赋值,那么width和height会被同时赋值,这样就保证了正方形类中,width和height总是相等的.现在我们假设有个客户类,其中有个方法,规则是这样的,测试传入的长方形的宽度是否大于高度,如果满足就停止下来,否则就增加宽度的值。现在我们来看,如果传入的是基类长方形,这个运行的很好。根据里氏代换原则,我们把基类替换成它的子类,结果应该也是一样的,但是因为正方形类的width和height会同时赋值,条件总是满足,这个方法没有结束的时候,也就是说,替换成子类后,程序的行为发生了变化,它不满足LSP。

那么我们用第一种方案进行重构,我们构造一个抽象的四边形类,把长方形和正方形共同的行为放到这个四边形类里面,让长方形和正方形都是它的子类,问题就OK了。对于长方形和正方形,取width和height是它们共同的行为,但是给width和height赋值,两者行为不同,因此,这个抽象的四边形的类只有取值方法,没有赋值方法。上面的例子中那个方法只会适用于不同的子类,里氏代换原则也就不会被破坏。

在进行设计的时候,我们尽量从抽象类继承,而不是从具体类继承。如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口。当然这个只是一个一般性的指导原则,使用的时候还要具体情况具体分析。

迪米特法则

又叫作最少知识原则,就是说一个对象应当对其他对象有尽可能少的了解,尽量减少关联,不和陌生人说话。

实质是强调了类结构设计上的松耦合,目的是为了利于代码复用。原意:如果两个类不必直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

每一个类在结构设计上都应尽量降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要让别的类的字段或行为就不要公开(封装思想)。因为类之间耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及,即信息的隐藏促进了软件的复用。

单一职责原则

原意:就一个类而言,应该仅有一个引起它变化的原因。目的:松耦合。

如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责的能力。多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离(分层设计思想)。这种耦合会导致脆弱的设计,当变化发生时设计会遭受到很大的破坏。软件设计的过程就是发现职责(功能)并把那些职责相互分离的过程。如果能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离。在编程时我们要在类的职责分离上多思考,做到单一职责,这样你的代码才是真正的易维护、易扩展、易复用、灵活多样的。

合成(组合)聚合复用原则

原意:尽量使用合成/聚合,尽量不要使用类继承。

聚合表示一种弱的拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样,部分不能脱离整体而独自存在。例如:大雁与(自己的)翅膀是组合关系;而雁群与大雁则是聚合关系。

此原则的好处是,优先使用对象的合成/聚合将有助于你保持每个类被封装,并集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。

继承是面向对象的三大特性之一,但很多情况用继承会带来麻烦。对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其它更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

举例:有手机名牌M和手机品牌N,它们都有自己的手机游戏和手机通讯录。

(1)采用继承的类图结构可以有两种方式

方式一:

方式二:

如果增加手机品牌或增加软件种类的话此两种方式 都会更改很多,会变得越来越庞大。

(2)运用合成聚合复用原则,应对相同的需求变化就可以细细品味到其优势的所在

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics