面向对象七大设计原则 设计出高内聚低耦合可扩展程序
1.单一职责原则
一个接口或类者只负责一项职责。
单一职责适适用于方法,一个方法尽可能的只做一件事。
对于单一职责,建议一个接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化,
一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
好处:
1.可降低类的复杂度,一个类只负责一项职责,其逻辑肯定比负责多项职责简单的多
2.复杂度底,可读性自然提高。
3.可维护性高,风险低。如果接口的单一职责好,一个接口修改只对应相应的实现类有影响。对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
2.里氏替换原则
所有引用基类(父类)的地方,必须能使用其子类的对象替换 ,即:在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,
里氏代换原则是实现开闭原则的重要方式之一,在类中调用其他类时务必要使用基类对象(父类或者接口),在传递参数,定义成员变量、定义局部变量、确定方法返回类型时都可使用里氏代换原则。针对基类编程,在程序运行时再确定具体子类。
如果子类不能完整的实现父类中的方法,或者子类实现的父类方法,与父类方法含义不同,则建议断开父子继承关系,采用依赖、聚合、组合等关系代替继承。
里氏替换原则通俗讲:子类可以扩展父类功能,但不能改变父类原有的功能。1.子类可以实现父类的功能,但不能改变父类原有功能 2.当子类的方法重载父类的方法时,方法的前置条件(方法形参)要比父类方法的输入参数更宽松 3.当子类方法实现父类的抽象方法,方法的后置条件(方法返回值)要比父类更严格。
3.依赖倒置原则
模块之间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生。
接口或抽象类不依赖实现类(应先有接口后有实现类)。
实现类依赖接口或者抽象类。
依赖倒置原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。
为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法
依赖倒置原则即面向接口编程OOD(Object-Oriented Design):接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑。
依赖1:构造函数传递依赖对象
依赖2:Setter依赖注入
依赖3:接口声明依赖对象
4.接口隔离原则
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
一个接口只服务于一个子模块或业务逻辑
通过深入了解业务逻辑压缩接口中的public方法,达到“满身筋骨”
与单一职责原则区别,单一职责原则注重的是职责,而接口隔离原则注重对接口依赖的隔离。单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节,而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
接口尽量小,但是要有限度,接口细化可以提高程序设计的灵活性,但如果过小,会导致接口过多,使设计复杂化。
5.迪米特法则
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类影响也越大。
解决:一个软件实体应当尽可能少地与其他实体发生相互作用,迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系
一 个类只和朋友交流,不与陌生类交流,其朋友包括以下几类:
(1) 当前对象本身(this);
(2) 以参数形式传入到当前对象方法中的对象;
(3) 当前对象的成员对象;
(4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
(5) 当前对象所创建的对象。
出现在方法体内部的类不属于朋友,不要出现getA().getB().getC(),除非每个点后面都返回同一个对象,类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不要引入一个类中不存在的对象。
在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类(朋友)都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
如果与多个朋友交互,可以通过引入一个合理的第三者来降低现有对象之间的耦合度
迪米特法则的核心是类间解耦,只有弱耦合,类之间的复用率才可以提高,其要求的结果就是产生了大量的中转或跳转类,导致系统复杂性提高,同时也为维护带来了难度,所以在采用迪米特法则时要反复权衡,既要结构清晰,又要搞内聚低耦合。
一般一个类访问另一个类跳转不要超过2 次。
6.开闭原则
一个软件实体应当对扩展开放,对修改关闭。其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
实体:软件模块、抽象和类、方法。
在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑,只有这样的代码才可以复用,粒度越小,被复用的可能性就越大,复用可以减少代码量,避免相同的逻辑分散在多个角落。避免日后的维护人员为了修改一个微小的缺陷而在整个项目中到处查找相关代码。
开闭原则可以提高维护性,扩展一个类相对修改一个类要舒服很多。
抽象层应尽量保持稳定,一旦确定即不允许修改。接口或者抽象类一旦定义,就应该立即执行,不能有修改接口的思想。除非是彻底的大返工。
7.合成复用原则
尽量使用对象组合,而不是继承来达到复用的目的。
复用时要尽量使用组合/聚合关系(关联关系),少用继承。来使用一个已有的对象成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用功能的目的。
在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承,但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用(如类没有声明为不能被继承)。
由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为“黑箱”复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性地调用成员对象的操作;合成复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。
一般而言,如果两个类之间是“Has-A”的关系应使用组合或聚合,如果是“Is-A”关系可使用继承。"Is-A"是严格的分类学意义上的定义,意思是一个类是另一个类的"一种";而"Has-A"则不同,它表示某一个角色具有某一项责任。
名词解释:
关联关系:类之间的联系
public class B { private A a; ........ } public class A{ ........ }
聚合关系: 成员类是整体类的一部分,即成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在
public class B { private A a; public B(A a) { this.a = a; } public void setA(A a) { this.a = a; } } public class A{ ........ }
组合关系:也是整体与部分的关系,但是整体与部分不可以分开。
也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之间具有同生共死的关系。
public class B { private A a; public B() { a = new A(); } } public class A{ ........ }
下面是粘过来的
设计原则名称 | 定 义 | 使用频率 |
单一职责原则 (Single Responsibility Principle, SRP) | 一个类只负责一个功能领域中的相应职责 | ★★★★☆ |
开闭原则 (Open-Closed Principle, OCP) | 软件实体应对扩展开放,而对修改关闭 | ★★★★★ |
里氏代换原则 (Liskov Substitution Principle, LSP) | 所有引用基类对象的地方能够透明地使用其子类的对象 | ★★★★★ |
依赖倒转原则 (Dependence Inversion Principle, DIP) | 抽象不应该依赖于细节,细节应该依赖于抽象 | ★★★★★ |
接口隔离原则 (Interface Segregation Principle, ISP) | 使用多个专门的接口,而不使用单一的总接口 | ★★☆☆☆ |
合成复用原则 (Composite Reuse Principle, CRP) | 尽量使用对象组合,而不是继承来达到复用的目的 | ★★★★☆ |
迪米特法则 (Law of Demeter, LoD) | 一个软件实体应当尽可能少地与其他实体发生相互作用 | ★★★☆☆ |
【作者:刘伟 http://blog.csdn.net/lovelion】
参考书籍:《设计模式之禅》