前言
在面向对象的软件设计中,只有尽量降低各个模块之间的耦合度,才能提高代码的复用率,系统的可维护性、可扩展性才能提高。面向对象的软件设计中,有23种经典的设计模式,是一套前人代码设计经验的总结,如果把设计模式比作武功招式,那么设计原则就好比是内功心法。常用的设计原则有七个,下文将具体介绍。
设计原则简介
- 单一职责原则:专注降低类的复杂度,实现类要职责单一;
- 开放关闭原则:所有面向对象原则的核心,设计要对扩展开发,对修改关闭;
- 里式替换原则:实现开放关闭原则的重要方式之一,设计不要破坏继承关系;
- 依赖倒置原则:系统抽象化的具体实现,要求面向接口编程,是面向对象设计的主要实现机制之一;
- 接口隔离原则:要求接口的方法尽量少,接口尽量细化;
- 迪米特法则:降低系统的耦合度,使一个模块的修改尽量少的影响其他模块,扩展会相对容易;
- 组合复用原则:在软件设计中,尽量使用组合/聚合而不是继承达到代码复用的目的。
这些设计原则并不说我们一定要遵循他们来进行设计,而是根据我们的实际情况去怎么去选择使用他们,来让我们的程序做的更加的完善。
接口隔离原则
定义:
接口隔离原则经常略写为ISP,讲的是使用多个专门的接口比使用单一的接口要好。
换句话来说从一个客户类的角度来说,一个类对另外一个类的依赖性应当是建立在最小的接口上的。
那么到底该怎么去理解这个接口隔离原则呢?
我觉得可以从三个方面去理解这个事情。
1. 角色的合理划分
将“接口”理解为一个类所提供的所有的方法的特征集合,也就是一种在逻辑上才存在的概念,这样的话,接口的划分其实就是直接在类型上的划分。
其实可以这么想,一个接口就相当于剧本中的一个角色,而这个角色在表演的过程中,决定由哪一个演员来进行表演就相当于是接口的实现,因此,一个接口代表的应当是一个角色而不是多个角色,如果系统涉及到多个角色的话,那么每一个角色都应当由一个特定的接口代表才对。
而为了避免我们产山混淆的想法,这时候我们就可以把接口隔离原则理解成角色隔离原则。
2. 定制服务
将接口理解成我们开发中狭义的JAVA接口的话,这样子,接口隔离原则讲的就是为同一个角色提供宽窄不同的接口,来应对不同的客户端内容,我画一个简单的图示,大家就完全能明白了。
上面这个办法其实就可以称之为定制服务,在上面的图中有一个角色service以及三个不同的客户端,这三个Client需要的服务是不一样的,所以我给他分成了是三个接口,也就是Service1,Service2和Service3,显而易见,每一个JAVA接口,都仅仅是将Cilent需要的行为暴露给Client,而没有将不需要的方法暴露出去。
其实了解设计模式的很容易就想到这是适配器模式的一个应用场景,我不细聊适配器模式,设计模式我们在知识星球中会进行讲解。
3. 接口污染
这句话的意思就是过于臃肿的接口就是对接口的污染。
由于每一个接口都代表一个角色,实现一个接口对象,在他的整个生命周期中,都扮演着这个角色,因此将角色分清就是系统设计的一个重要的工作。因此一个符合逻辑的判断,不应该是将几个不同的角色都交给一个接口,而是应该交给不同的接口来进行处理。
准确而恰当的划分角色以及角色所对应的接口,就是我们面向对象设计中的一个重要的组成部分,如果将没有关系或者关系不大的接口整合到一起去的话,那就是对角色和接口的污染。
我们来写代码来举个例子论证一下:
public interface TestInterface { public void method1(); public void method2(); public void method3(); public void method4(); public void method5(); } class Test1 { public void mm1(TestInterface i) { i.method1(); } public void mm2(TestInterface i) { i.method2(); } public void mm3(TestInterface i) { i.method3(); } } class Test2 implements TestInterface{ @Override public void method1() { System.out.println("类Test2实现接口TestInterface的方法1"); } @Override public void method2() { System.out.println("类Test2实现接口TestInterface的方法2"); } @Override public void method3() { System.out.println("类Test2实现接口TestInterface的方法3"); } @Override public void method4() {} @Override public void method5() {} } class Test3{ public void mm1(TestInterface i) { i.method1(); } public void mm2(TestInterface i) { i.method4(); } public void mm3(TestInterface i) { i.method5(); } } class Test4 implements TestInterface{ @Override public void method1() { System.out.println("类Test4实现接口TestInterface的方法1"); } @Override public void method2() { } @Override public void method3() { } @Override public void method4() { System.out.println("类Test4实现接口TestInterface的方法4"); } @Override public void method5() { System.out.println("类Test4实现接口TestInterface的方法5"); } } 然后我们看一下调用方式 public class Client { public static void main(String[] args) { Test1 test1 = new Test1(); test1.mm1(new Test2()); test1.mm2(new Test2()); test1.mm3(new Test2()); Test3 test3 = new Test3(); test3.mm1(new Test4()); test3.mm2(new Test4()); test3.mm3(new Test4()); } }
执行结果如下:
类Test2实现接口TestInterface的方法1 类Test2实现接口TestInterface的方法2 类Test2实现接口TestInterface的方法3 类Test4实现接口TestInterface的方法1 类Test4实现接口TestInterface的方法4 类Test4实现接口TestInterface的方法5