-
单一职责原则(Single Responsibility Principle,SRP)
一个类只负责一个功能领域中的相应职责,或者说,对于一个特定的类,只有一个能引起它变化的原因。
单一职责的原则是实现高内聚,低耦合。当然,如果一个类中实现了太多的功能,那么当其中一个功能变化时,可能会使其他功能受到影响。以汽车厂生产汽车为例
public class CAR {
public Engine createEngine(){
// 生产发动机
}
public Tyre createTyre(){
// 生产轮胎
}
public Glass createGlass(){
// 生产汽车玻璃
}
public Car createCar(){
// 组装汽车
}
}
从上述代码看出,汽车厂不仅承担了生产发动机,轮胎玻璃的任务,还担任了组装汽车的角色,这样的话生产效率是十分低下的,作为汽车厂,就要做好自己组装汽车的任务,而对于其他零部件,可以直接从其它生产商那里获取,按照单一职责原则,这个类需要重写
public class CAR {
public Car createCar(){
Engine mEngine = getEngine();
Tyre mTyre = getTyre();
Glass mGlass = getGlass();
}
}
由上述代码可见,汽车厂只承担了组装汽车的责任,而对于其他零部件不再属于自己的责任范畴。对于这个类而言,这样改写,不仅代码逻辑清晰,而且降低了各方法间的耦合度,提高了类的复用度,这就是单一职责的优点。
2 . 开闭原则(Open-Closed Principle,OCP)
开闭原则的意思就是,一个软件实体应当对拓展开放,对修改关闭,也就是说软件实体应尽量不修改原有代码的情况下对其进行拓展。随着时间的推移,任何一个软件都会有新的需求,这个时候我们就应该在保证原有功能不被改变的基础上进行添加新的功能,这也是开闭原则存在的意义。
3 . 里氏代换原则(Liskov Substitution Principle ,LSP)
所有引用基类的地方必须能够透明的使用其子类对象,这也就意味着我们在定义父类时应该将其设计为抽象类或者接口,让子类继承父类或者实现父类接口,并实现在父类中声明的方法。举个例子,比如一个汽车厂生产汽车,我们将汽车声明为一个接口,让不同型号的汽车继承它并实现其方法。
public interface Car {
public void createCar();
}
public class Audi implements Car{
@Override
public void createCar() {
System.out.println("Audi");
}
}
public class BWM implements Car{
@Override
public void createCar() {
System.out.println("BWM");
}
}
这个时候,如果我们需要生产一辆汽车,那么我们可以使用以下语句
Car car = new Audi();
这个时候如果我们需要将 Audi 换成 BWM,只需要重新赋值给car即可
car = new Audi();
而如果刚才定义car 时,使用 Audi car = new Audi() , 那么现在就不能这样赋值了,而且也不可能有 Audi mAudi = new Car() 这种情况存在。基类可以替换成它的子类,而子类不能够替换成它的基类,这就是里氏替换原则的特点。
4 . 依赖倒转原则
抽象不应该依赖于细节,细节应当依赖于抽象,也就是说,要针对接口编程,而不是针对实现编程。依赖倒转原则要求我们在编写程序时,传递参数和方法返回类型声明都最好定义为基类,这样的话,系统将具有很好的灵活性。还是以刚才的汽车厂为例,不过这个时候需要简单的修改一些代码,让这一原则更突出的显现出来。
public interface Car {
public Car createCar();
}
public class Audi implements Car{
@Override
public Car createCar() {
return new Audi();
}
}
public class BWM implements Car{
@Override
public Car createCar() {
return new BWM();
}
}
可见,我们将方法的返回类型设置为了Car , 这个时候如果汽车厂想要增添一种新的车型也是十分方便的,只需要New 出一个类继承自 Car 即可,还可以再添加一个 Repair(Car car) 方法,也就是修理汽车的方法,这个时候不论是什么型号的车,只要是继承自Car 这个接口的,那么就可以传进来而不出现任何错误。想像一下,如果这个时候参数类型是一个具体车的类型,Audi 或是 BWM ,那么就要针对每一个车型都要有一个Repair() 方法,那么实现起来是非常繁琐的,这就是依赖倒转原则带给我们的便利。
5 . 接口隔离原则(Interface Segregation Principle,ISP)
使用多个专用的接口,而不是用单一的总接口。为什么这么说呢,有的时候一个接口中定义了太多的方法,那么它的实现类将会十分庞大,而且这些方法中说不定还有这个类并不需要的方法,这就生成了大量的无用的代码。遇到这种情况,我们就应该将接口再分离,将它分成更小的接口,这么做不仅可以提高系统的灵活性,而且在逻辑上也会更加清晰。
举个例子
public interface Person {
public void sleep();
public void eat();
public void wear();
public void study();
public void interst();
}
如图所示,一个人有吃,睡,穿,学习,兴趣等相关方法,这个时候会发现,睡觉吃饭和穿衣服是人的身体上的活动,而学习和兴趣则是人的思想方面,可见这个接口是可以再次分离的。如果一个病人去医院看病,那么医生看重的是它的吃睡等身体上的问题,而对于学习和兴趣是不受关注的,但是我们在实现这个接口时,同样要实现这两个方法,这是不合理的,这对系统内存资源是一种严重的浪费。所以可以这样定义
public interface Body {
public void sleep();
public void eat();
public void wear();
}
public interface Mind {
public void study();
public void interest();
}
这样我们分离了接口的职责,使接口更符合单一职责原则,灵活性得到了显著提高。
6 . 合成复用原则(Composite Reuse Principle,CRP)
尽量使用对象组合,而不是继承来达到继承的目的。在程序设计中,如果我们想要重用之前的设计和实现,有两种方法,一个是组合,一个是继承。但是根据合成复用原则,我们应该优先考虑组合,因为组合可以提高系统的灵活度,降低类与类之间的耦合度。其次才是考虑继承,有效使用继承会有助于问题的求解,而滥用继承会使系统难于构建和维护。使用继承进行复用的主要问题是会破坏系统的封装性,因为基类的内部实现细节是对子类可见的,如果基类改变,那么子类也不得不改变,所以使得系统灵活性非常低,选择使用时应当慎重。其实简单地说,如果两个类是Has 关系,那么应该使用组合,如果是Is关系当使用继承。
举个例子来说,一只鹦鹉有身高,体重属性,而且拥有speak() 方法,当我们此时想要写一个Person 类,这个时候我们发现和鹦鹉相同,人也有身高体重属性,也有speak() 方法,那么为了省事,我们干脆让Person 类继承自鹦鹉类算了。这个时候就出现了滥用继承的问题,不能因为两个类中拥有相同的方法,就草率的互相继承,Person 类继承鹦鹉类,不从其他方面,单从逻辑上,这就是不合理的。
还有,关于汽车组装的问题,一个汽车拥有轮胎,发动机等零件,那么我们能使用 Car 去继承 Engine 和 Wheel 类吗?显然是不可以的,因为Car 和Engine, Wheel 是从属关系,用组合才是个完美的选择。
7 . 迪米特法则 ( Law of Demeter,LoD)
一个软件实体应当尽可能少的与其他实体发生相互作用,实际上,迪米特法则强调的是软件实体间的通信法则,它限制了软件实体间通信的深度和宽度,这样可以降低系统间的耦合度,是类与类之间保持松散的耦合关系。
好比我们在学校上学,我们需要书本,课桌,老师,如果没有学校这个介质,那么我们需要自己去买课本,买课桌,联系教师,这些任务都要由我们个人去解决,去沟通。但是有了学校之后,我们就不必再去操心这些过程的实现细节,我们只需要交给学校一定的学费,就可以轻松的获得这些成果。这个过程和迪米特法则的要求是雷同的,也即一些繁琐的交互细节交由第三方去处理,我们想要与其他类进行交互只需要通过第三方就可以实现,这样做的方法是一旦交互环节需要做出相应改变,只需要修改这个第三方类,而不是去修改这两个需要交互的类,在一定程度上,这提高了类的复用性。
网友评论