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

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

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

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是朋友,系统结构图就像下面的。

65.png

从上面的类图中,我们可以看到,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的朋友。


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


使用迪米特法则进行改造

66.jpg

从上面的图中我们可以看出,与改造之前相比,在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”则不同,他表示某一个角色具有某一项责任。


我们看一个图解

67.jpg


人被继承到“雇员”,“经理”,“学生”等子类,而实际上,“雇员”,“经理”,“学生”分别描述一种角色,而“人”可以同时有几种不同的角色,比如,一个“人”即使“经理”,就必然是“雇员”,而有可能这个“人”还是一个“学生”。如果说使用继承来说,那么如果这个人是“学生”,那么它一定不能再是经理,这个大家可以思考一下为什么,很简单,这显然就是不合理的。


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

68.jpg

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


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


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


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


2. 永远不会出现需要将子类换成另外一个类的子类的情况。如果不能肯定将来是否会变成另外一个子类的话,就不要使用继承。 


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


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


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


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

相关文章
|
4月前
|
双11
访问者模式问题之在软件工程中,根据特性和场景决定是采用面向对象的抽象还是访问者的抽象,如何实现
访问者模式问题之在软件工程中,根据特性和场景决定是采用面向对象的抽象还是访问者的抽象,如何实现
面向对象七大设计原则,看了必会(代码详细版)(中)
面向对象七大设计原则,看了必会(代码详细版)(中)
|
设计模式 前端开发 Java
【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
213 0
|
关系型数据库
面向对象七大设计原则,看了必会(代码详细版)(上)
面向对象七大设计原则,看了必会(代码详细版)(上)
面向对象七大设计原则,看了必会(代码详细版)(下)
面向对象七大设计原则,看了必会(代码详细版)(下)
|
设计模式 Java 关系型数据库
面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
129 1
面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
|
设计模式 算法 Java
六大原则之外的设计原则|设计原则
在前面的几篇设计原则文章中,我们分别讲述了经典的六大设计原则。但是事实上,我们在开发中还有几个重要的设计原则,在这篇文章中,一并给大家讲述。
|
设计模式 Java
如何理解代码中的抽象|设计模式基础
下面,我们可以通过问题的形式来加深我们对抽象这一概念的理解。
|
设计模式 Java
面向对象的设计原则最终篇(一)
关于面向对象的设计原则我之前已经解释过四种了,分别是单一职责原则,开放关闭原则,里式替换原则,依赖倒置原则而接下来我们要解释的就是最后的三种原则了,分别是接口隔离原则, 迪米特法则, 组合复用原则。
面向对象的设计原则最终篇(一)
|
设计模式
【设计模式】软件设计七大原则 ( 里氏替换原则 | 定义 | 定义扩展 | 引申 | 意义 | 优点 )
【设计模式】软件设计七大原则 ( 里氏替换原则 | 定义 | 定义扩展 | 引申 | 意义 | 优点 )
214 0