面向对象的设计原则最终篇

mp.weixin.qq.com/s?src=11&am…

关于面向对象的设计原则我以前已经解释过四种了,分别是单一职责原则,开放关闭原则,里式替换原则,依赖倒置原则而接下来咱们要解释的就是最后的三种原则了,分别是接口隔离原则, 迪米特法则, 组合复用原则。编程


前言设计模式


在面向对象的软件设计中,只有尽可能下降各个模块之间的耦合度,才能提升代码的复用率,系统的可维护性、可扩展性才能提升。面向对象的软件设计中,有23种经典的设计模式,是一套前人代码设计经验的总结,若是把设计模式比做武功招式,那么设计原则就比如是内功心法。经常使用的设计原则有七个,下文将具体介绍。bash


设计原则简介ide


  • 单一职责原则:专一下降类的复杂度,实现类要职责单一;工具

  • 开放关闭原则:全部面向对象原则的核心,设计要对扩展开发,对修改关闭;ui

  • 里式替换原则:实现开放关闭原则的重要方式之一,设计不要破坏继承关系;spa

  • 依赖倒置原则:系统抽象化的具体实现,要求面向接口编程,是面向对象设计的主要实现机制之一;设计

  • 接口隔离原则:要求接口的方法尽可能少,接口尽可能细化;3d

  • 迪米特法则:下降系统的耦合度,使一个模块的修改尽可能少的影响其余模块,扩展会相对容易;code

  • 组合复用原则:在软件设计中,尽可能使用组合/聚合而不是继承达到代码复用的目的。


这些设计原则并不说咱们必定要遵循他们来进行设计,而是根据咱们的实际状况去怎么去选择使用他们,来让咱们的程序作的更加的完善。


接口隔离原则


定义


接口隔离原则常常略写为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复制代码


很显然,这不遵循接口设计原则,那么咱们怎么去设计遵循接口设计原则的代码呢?


public interface TestInterface1 {    public void method1();}interface TestInterface2{    public void method2();    public void method3();}interface TestInterface3 {    public void method4();    public void method5();}class Test1{    public void mm1(TestInterface1 i){        i.method1();    }    public void mm2(TestInterface2 i){        i.method2();    }    public void mm3(TestInterface2 i){        i.method3();    }}class Test2 implements TestInterface1,TestInterface2{    @Override    public void method1() {        System.out.println("类Test2实现接口TestInterface1的方法1");    }    @Override    public void method2() {        System.out.println("类Test2实现接口TestInterface2的方法2");    }    @Override    public void method3() {        System.out.println("类Test2实现接口TestInterface2的方法3");    }}class Test3{    public void mm1(TestInterface1 i){        i.method1();    }    public void mm2(TestInterface3 i){        i.method4();    }    public void mm3(TestInterface3 i){        i.method5();    }}class Test4 implements TestInterface1,TestInterface3{    @Override    public void method1() {        System.out.println("类Test4实现接口TestInterface1的方法1");    }    @Override    public void method4() {        System.out.println("类Test4实现接口TestInterface3的方法4");    }    @Override    public void method5() {        System.out.println("类Test4实现接口TestInterface3的方法5");    }}复制代码


接口隔离原则的含义是:创建单一接口,不要创建庞大臃肿的接口,尽可能细化接口,接口中的方法尽可能少。也就是说,咱们要为各个类创建专用的接口,而不要试图去创建一个很庞大的接口供全部依赖它的类去调用。


我写的这个例子中,将一个庞大的接口变动为3个专用的接口所采用的就是接口隔离原则。


因此其实接口隔离原则其实也算是“看人下菜碟”,它的意思就是要看客人是谁,在提供不一样档次的饭菜。


从接口隔离原则的角度出发的话,要根据客户不一样的需求,去指定不一样的服务,这就是接口隔离原则中推荐的方式。


迪米特法则


定义


迪米特法则(Law of Demeter)又叫做最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其余对象有尽量少的了解,不和陌生人说话。英文简写为: LoD。


其实他主要是为了解决一个咱们最多见的问题,就是类之间的关系,因此类与类之间的关系越密切,耦合度就越大,当一个类放生改变的时间,对另外一个类的影响也会越大。


而他最终的解决方案就是下降类和类之间的耦合度,这也是咱们所说的高内聚,低耦合。


咱们来经过简单的一个系统的代码来理解一下迪米特法则。


不知足迪米特法则的系统


这里的系统有三个类,分别是SomeOne,Friend和Stranger。其中SomeOne与Friend是朋友,而Friend和Stranger是朋友,系统结构图就像下面的。



从上面的类图中,咱们能够看到,Friend持有Stranger对象的引用,这就解释了为何Friend与Stranger是朋友,咱们给出一点代码来解释SomeOne和Friend的朋友关系。


public class Someone{    public void operation1(Friend friend){        Stranger stranger = friend.provide();        stranger.operation3();    }}复制代码


能够看出,SomeOne具备一个方法operation1(),这个方法接收friend为参数,显然,根据朋友的定义,Friend和Stranger是朋友关系,其中的Friend的provide()方法会提供本身所建立的Stranger实例,就像下面的代码


public class Friend{    private Stranger stranger = new Stranger();    public void operation2(){            }    public Stranger provide(){        return stranger;    }}复制代码


这其实就很显然了,SomeOne的方法operation1()并不知足迪米特法则,为何会这么说呢?由于这个方法引用Stranger对象,而Stranger对象不是SomeOne的朋友。


咱们下面使用迪米特法则来进行改造一下这个关系和代码。


使用迪米特法则进行改造



从上面的图中咱们能够看出,与改造以前相比,在SomeOne与Stranger之间的联系已经没有了,SomeOne不须要知道Stranger的存在就能够作一样的事情,咱们看一下SomeOne的代码,


public class Someone{    public void operation1(Friend friend){        friend.forward();    }}复制代码


从源代码中咱们能够看出,SomeOne经过调用本身的朋友Friend对象的forward()方法作到了原来须要调用Stranger对象才可以作到的事情,那么咱们再来看一下forward()方法是作什么呢?


public class Frend{    private Stranger stranger = new Stranger();        public void operation2(){        System.out.printIn("In Friend.operation2()");    }    public void forward(){        stranger.operation3();    }}复制代码


原来Friend类的forward()方法所作的就是之前SomeOne要作的事情,使用Stranger的operation3()方法,而这种forward()方法叫作转发方法,


因为使用了调用转发,使得调用的具体的细节被隐藏在Friend内部,从而使SomeOne与Stranger之间的直接联系被省略掉了,这样一来,系统内部的耦合度下降了,在系统的某一个类须要修改时,仅仅会影响这个类的“朋友们”,而不会直接影响到其余的部分。


以上就是我对迪米特法则的一些理解,有兴趣的人也能够去深刻研究一下,未来对理解设计模式会有很好的看法的。


组合复用原则


定义


组合复用原则常常又叫作合成复用原则。该原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象经过向这些对象的委派达到复用已有功能的目的。


而在咱们的代码中尽量使用组合而不是用继承是什么缘由呢?

缘由以下


  • 第一,继承复用破坏包装,它把父类的实现细节直接暴露给了子类,这违背了信息隐藏的原则;

  • 第二:若是父类发生了改变,那么子类也要发生相应的改变,这就直接致使了类与类之间的高耦合,不利于类的扩展、复用、维护等,也带来了系统僵硬和脆弱的设计。而用合成和聚合的时候新对象和已有对象的交互每每是经过接口或者抽象类进行的,就能够很好的避免上面的不足,并且这也可让每个新的类专一于实现本身的任务,符合单一职责原则。


其实这个组合复用原则最好的理解就是咱们各类系统中的后台系统里面的权利和角色的分配,我相信不少公司的项目中都会有,我来阐述一下这个问题。


“Has-A”和“Is-A”


“Is-A”是严格的分类学意义上的定义,意思是一个类是另一个类的“一种”。而“Has-A”则不一样,他表示某一个角色具备某一项责任。


咱们看一个图解



人被继承到“雇员”,“经理”,“学生”等子类,而实际上,“雇员”,“经理”,“学生”分别描述一种角色,而“人”能够同时有几种不一样的角色,好比,一个“人”即便“经理”,就必然是“雇员”,而有可能这个“人”仍是一个“学生”。若是说使用继承来讲,那么若是这我的是“学生”,那么它必定不能再是经理,这个你们能够思考一下为何,很简单,这显然就是不合理的。


图中的这种就是把“角色”的等级结构和“人”的等级结构混淆了,把“Has-A”角色误解成为了“Is-A”角色,而下面这幅图就成功的解释了这一点



而在这个图中,就不存在以前混淆的问题了,每一个人均可以拥有一个以上的“角色”了。


组合/聚合复用原则使用总结:


合成和聚合均是关联的特殊状况。聚合用来表示“拥有”关系或者总体与部分的关系;而合成则用来表示一种强得多的“拥有”关系。在一个合成关系里面,部分和总体的生命周期是同样的。一个合成的新的对象彻底拥有对其组成部分的支配权,包括它们的建立和销毁等。使用程序语言的术语来讲,组合而成的新对象对组成部分的内存分配、内存释放有绝对的责任。要正确的选择合成/复用和继承,必须透彻地理解里氏替换原则和Coad法则。(Coad法则由Peter Coad提出,总结了一些何时使用继承做为复用工具的条件。Coad法则:只有当如下Coad条件所有被知足时,才应当使用继承关系)


1. 子类是基类的一个特殊种类,而不是基类的一个角色。区分“Has-A”和“Is-A”。只有“Is-A”关系才符合继承关系,“Has-A”关系应当用聚合来描述。


2. 永远不会出现须要将子类换成另一个类的子类的状况。若是不能确定未来是否会变成另一个子类的话,就不要使用继承。


3. 子类具备扩展基类的责任,而不是具备置换掉(override)或注销掉(Nullify)基类的责任。若是一个子类须要大量的置换掉基类的行为,那么这个类就不该该是这个基类的子类。


4. 只有在分类学角度上有意义时,才可使用继承。不要从工具类继承。


以上就是我最后介绍的关于设计模式以前的设计原则的全部了,三篇文章,你学会了么?


我是懿,一个正在被打击还在努力前进的码农。欢迎你们关注咱们的公众号,加入咱们的知识星球,咱们在知识星球中等着你的加入。

相关文章
相关标签/搜索