遵守软件开发的原则,可以让我们在‘软件构建活动’中,写出扩展性、复用性极强的代码。
面向对象的基本原则(solid)是五个,但是在经常被提到的除了这五个之外还有 迪米特法则
和合成复用原则
等, 所以在常见的文章中有表示写六大或七大原则的; 除此之外我还将给出一些其它相关书籍和互联网上出现的原则。
Single-Responsibility Principle, 一个类
(或者大到模块
,小到方法
), 最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则的引申,将职责定义为引起变化的原因,以提高内聚性减少引起变化的原因。
数据职责
】和【行为职责
】,数据职责通过其属性来体现,而行为职责通过其方法来体现。SpringMVC 中Entity,DAO,Service,Controller, Util等的分离。
Open - ClosedPrinciple ,OCP, 对扩展开放,对修改关闭(设计模式的核心原则)。意思是,在一个系统或者模块中,对于扩展是开放的,对于修改是关闭的,一个好的系统是在不修改源代码的情况下,可以扩展你的功能,而 实现开闭原则的关键就是抽象化
。
当软件实体因需求要变化时, 尽量通过扩展已有软件实体,可以提供新的行为,以满足对软件的新的需求;而不是修改已有的代码,使变化中的软件有一定的适应性和灵活性 。已有软件模块,特别是最重要的抽象层模块不能再修改,这使变化中的软件系统有一定的稳定性和延续性。
实现开闭原则的关键就是抽象化 : 在"开-闭"原则中,不允许修改的是抽象的类或者接口,允许扩展的是具体的实现类
。 抽象类和接口在"开-闭"原则中扮演着极其重要的角色…即要预知可能变化的需求.又预见所有可能已知的扩展…所以在这里"抽象化"是关键!
设计模式中模板方法模式和观察者模式都是开闭原则的极好体现.
Liskov Substitution Principle ,LSP: 任何基类可以出现的地方,子类也可以出现,这一思想表现为对继承机制
的约束规范。只有子类能够替换其基类时, 才能够保证系统在运行期内识别子类, 这是保证继承复用的基础。
基类和子类的关系
,只有这种关系存在时,里氏代换原则才存在。正方形是长方形是理解里氏代换原则的经典例子。因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象
。(Interface Segregation Principle,ISL): 客户端不应该依赖那些它不需要的接口。(这个法则与迪米特法则是相通的)。
另一种定义方法: 一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
注意,在该定义中的接口指的是所定义的
方法
。例如外面调用某个类的public方法。这个方法对外就是接口。
使用多个专门的接口
,而不使用单一的总接口。每一个接口应该承担一种相对独立的角色
,不多不少,不干不该干的事,该干的事都要干。Dependency-Inversion Principle 要依赖抽象, 而不要依赖具体的实现。
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:
1)抽象不应当依赖于细节;细节应当依赖于抽象;
2)要针对接口编程,不针对实现编程。
在代码中使用抽象类
,而将具体类放在配置文件中。类之间的耦合: 零耦合关系,具体耦合关系,抽象耦合关系。理解这个依赖倒置,首先我们需要明白依赖在面向对象设计的概念:
依赖关系(Dependency): 是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系。(假设A类的变化引起了B类的变化,则说名B类依赖于A类。)大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。在UML中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方。
例子: 某系统提供一个数据转换模块,可以将来自不同数据源的数据转换成多种格式,如可以转换来自数据库的数据(DatabaseSource)、也可以转换来自文本文件的数据(TextSource),转换后的格式可以是XML文件(XMLTransformer)、也可以是XLS文件(XLSTransformer)等。
由于需求的变化,该系统可能需要增加新的数据源或者新的文件格式,每增加一个新的类型的数据源或者新的类型的文件格式,客户类MainClass都需要修改源代码,以便使用新的类,但违背了开闭原则。现使用依赖倒转原则对其进行重构。
/**
* 依赖注入是依赖AbstractSource抽象注入的,而不是具体
* DatabaseSource
*
*/
abstract class AbstractStransformer {
private AbstractSource source;
/**
* 构造注入(Constructor Injection): 通过构造函数注入实例变量。
*/
public void AbstractStransformer(AbstractSource source){
this.source = source;
}
/**
* 设值注入(Setter Injection): 通过Setter方法注入实例变量。
* @param source : the sourceto set
*/
public void setSource(AbstractSource source) {
this.source = source;
}
/**
* 接口注入(Interface Injection): 通过接口方法注入实例变量。
* @param source
*/
public void transform(AbstractSource source ) {
source.getSource();
System.out.println("Stransforming ...");
}
}
(Composite/Aggregate ReusePrinciple ,CARP): 要尽量使用对象组合,而不是继承关系达到软件复用的目的。