William的总结(十二):设计模式五大原则

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_21918021/article/details/88094485

一、开放封闭原则(OCP,Open Closed Principle)

1、开放封闭原则概述

开放封闭原则(OCP,Open Closed Principle)是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化、降低耦合,而开放封闭原则正是对这一目标的最直接体现。其他的设计原则,很多时候是为实现这一目标服务的,例如以Liskov替换原则实现最佳的、正确的继承层次,就能保证不会违反开放封闭原则。

关于开放封闭原则,其核心的思想是:软件实体应该是可扩展,而不可修改的。也就是说,一个软件实体应当对扩展是开放的,而对修改是封闭的。

因此,开放封闭原则主要体现在两个方面:对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

在设计一个模块时,应当使得这个模块可以在不被修改的前提下被扩展。也就是说,应当可以在不必修改源代码的情况下修改这个模块的行为。

设计的目的便在于面对需求的改变而保持系统的相对稳定,从而使得系统可以很容易的从一个版本升级到另一个版本。

“需求总是变化”、“世界上没有一个软件是不变的”,这些言论是对软件需求最经典的表白。从中透射出一个关键的意思就是,对于软件设计者来说,必须在不需要对原有的系统进行修改的情况下,实现灵活的系统扩展。而如何能做到这一点呢?

只有依赖于抽象。实现开放封闭的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定

  • 让类依赖于固定的抽象,所以对修改就是封闭的;
  • 而通过面向对象的继承和对多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。

这是实施开放封闭原则的基本思路,同时这种机制是建立在两个基本的设计原则的基础上,这就是Liskov替换原则和合成/聚合复用原则。

对于违反这一原则的类,必须进行重构来改善,常用于实现的设计模式主要有Template Method模式和Strategy模式。而封装变化,是实现这一原则的重要手段,将经常发生变化的状态封装为一个类。

2、怎样做到开放封闭原则

实际上,绝对封闭的系统是不存在的。无论模块是怎么封闭,到最后,总还是有一些无法封闭的变化。而我们的思路就是:既然不能做到完全封闭,那我们就应该对那些变化封闭,那些变化隔离做出选择。我们做出选择,然后将那些无法封闭的变化抽象出来,进行隔离,允许扩展,尽可能的减少系统的开发。当系统变化来临时,我们要及时的做出反应。

我们并不害怕改变的到来。当变化到来时,我们首先需要做的不是修改代码,而是尽可能的将变化抽象出来进行隔离,然后进行扩展。面对需求的变化,对程序的修改应该是尽可能通过添加代码来实现,而不是通过修改代码来实现。

扫描二维码关注公众号,回复: 5617416 查看本文章

实际上,变化或者可能的变化来的越早,抽象就越容易,相对的,代码的维护也就越容易;而当项目接近于完成而来的需求变化,则会使抽象变得很困难——这个困难,并不是抽象本身的困难,抽象本身并没有困难,困难在于系统的架构已经完成,修改牵扯的方面太多而使得抽象工作变得很困难。

我们举个银行业务的例子

银行有四项业务,存款,取款,转账和买基金

如果用普通的方式来写

/*
 * 银行业务员
 */
public class BankWorker {
    //负责存款业务
    public void saving() {
        System.out.println("进行存款操作");
    }
    
    //负责取款业务
    public void drawing() {
        System.out.println("进行取款操作");
    }
    
    //负责转账
    public void zhuanzhang() {
        System.out.println("进行转账操作");
    }
    
    //负责基金的申购
    public void jijin() {
        System.out.println("进行基金申购操作");
    }
}

这样等同于一个业务员负责了所有的业务,站在银行窗口焦急等待的用户,在长长的队伍面前显得无奈。所以,将这种无奈迁怒到银行的头上是理所当然的,因为银行业务的管理显然有不当之处。银行的业务人员面对蜂拥而至的客户需求,在排队等待的人们并非只有一种需求,有人存款、有人转账,

也有人申购基金,繁忙的业务员来回在不同的需求中穿梭,手忙脚乱的寻找各种处理单据,电脑系统的功能模块也在不同的需求要求下来回切换,这就是一个发生在银行窗口内外的无奈场景。而我每次面对统一排队的叫号系统时,都为前面长长的等待人群而叫苦,从梳理银行业务员的职责来看,在管理上

他们负责的业务过于繁多,将其对应为软件设计来实现,这种拙劣的设计就上面例子中的方式。

这样,如果要修改的话,如果银行新添加了一项业务,这个业务员就得新增这项业务,这样扩展行就很差,而且,只要新增业务就要修改源码。

所以下面我们用符合开放封闭原则的方式来编写代码

把不同的业务分配给不同的业务员,所以先编写抽象业务员父类

/*
 * 银行业务员接口,是所有银行业务员的抽象父类
 */
public interface BankWorker {
    public void operation();
}

然后,在编写各个负责各个业务的业务员

/*
 * 负责存款业务的业务员
 */
public class SavingBankWorker implements BankWorker {
    public void operation() {
        System.out.println("进行存款操作");
    }    
}
/*
 * 负责取款业务的业务员
 */
public class WithdrawalsBankWorker  implements BankWorker{

    public void operation() {
        System.out.println("进行取款操作");
    }
    
}
/*
 * 负责转账业务的业务员
 */
public class ZhuanZhangBankWorker implements BankWorker {

    public void operation() {
        System.out.println("进行转账操作");
    }

}
/*
 * 负责基金业务的业务员
 */
public class JiJinBankWorker implements BankWorker {

    public void operation() {
        System.out.println("进行基金申购操作");
    }
}

可以看到,这样的形式就可以做到,增加业务只需新增业务员即可,不必对原有业务进行任何的修改,也符合了开放封闭原则。

3、开放封闭原则的优越性

  • 通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,是变化中的软件有一定的适应性和灵活性。
  • 已有的软件模块,特别是最重要的抽象模块不能再修改,这就使变化中的软件系统有一定的稳定性和延续性。

使用建议

  1. 开放封闭原则,是最为重要的设计原则,Liskov替换原则和合成/聚合复用原则为开放封闭原则的实现提供保证。
  2. 可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭、对扩展开放的设计思路。
  3. 封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态一般将其封装为一个抽象,例如银行业务中的IBankProcess接口。
  4. 拒绝滥用抽象,只将经常变化的部分进行抽象,这种经验可以从设计模式的学习与应用中获得。

二、单一职责原则(SRP:Single responsibility principle)

1、单一职责原则概述

单一职责原则(SRP:Single responsibility principle)又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。

所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。

2、为什么要使用单一职责原则

如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责

原则。此原则的核心就是解耦和增强内聚性。

就好比,现在的手机,拥有各种各样的功能,拍照,打电话,发短信,玩游戏,导航。但是每一个功能又都不是那么强大,远远比不上专业的。

拍照比不上专业单反,玩游戏比不上电脑或专业游戏机,导航比不上专业导航设备等等,这就是因为手机的职责太多了。

一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。

三、里氏代换原则(Liskov Substitution Principle LSP)

3、里氏代换原则概述

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
  里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

简单的理解为一个软件实体如果使用的是一个父类,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,软件里面,把父类都替换成它的子类,程序的行为没有变化。

但是反过来的代换却不成立,里氏代换原则(Liskov Substitution Principle):一个软件实体如果使用的是一个子类的话,那么它不能适用于其父类。

举个例子解释一下这个概念

先创建一个Person类

public class Person {
     public void display() {
        System.out.println("this is person");
     }
 }

再创建一个Man类,继承这个Person类

public class Man extends Person {

    public void display() {
        System.out.println("this is man");
    }   
}

运行一下

public class MainClass {
    public static void main(String[] args) {
        Person person = new Person();//new一个Person实例
        display(person);
        
        Person man = new Man();//new一个Man实例
        display(man);
    }
    
    public static void display(Person person) {
        person.display();
    }
}

可以看到

在这里插入图片描述

运行没有影响,符合一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类和子类对象的区别这句概念,这也就是java中的多态。

而反之,一个子类的话,那么它不能适用于其父类,这样,程序就会报错。

public class MainClass {
    public static void main(String[] args) {
        Person person = new Person();
        display(person);//这里报错
        
        Man man = new Man();
        display(man);
    }
    
    public static void display(Man man) {//传入一个子类
        man.display();
    }
}

继续再举一个很经典的例子,正方形与长方形是否符合里氏代换原则,也就是说正方形是否是长方形的一个子类。

以前,我们上学都说正方形是特殊的长方形,是宽高相等的长方形,所以我们认为正方形是长方形的子类,但真的是这样吗?

从图中,我们可以看到长方形有两个属性宽和高,而正方形则只有一个属性边长。
  在这里插入图片描述

所以,用代码如此实现

//长方形
public class Rectangle{
    private long width;
    private long height;
    
    public long getWidth() {
        return width;
    }
    public void setWidth(long width) {
        this.width = width;
    }
    public long getHeight() {
        return height;
    }
    public void setHeight(long height) {
        this.height = height;
    }
}
//正方形
public class Square{
    private long side;

    public long getSide() {
        return side;
    }

    public void setSide(long side) {
        this.side = side;
    }
}

可以看到,它们的结构根本不同,所以正方形不是长方形的子类,所以长方形与正方形之间并不符合里氏代换原则。

当然我们也可以强行让正方形继承长方形

//正方形
public class 
Square extends Rectangle{
    private long side;

    public long getHeight() {
        return this.getSide();
    }

    public long getWidth() {
        return this.getSide();
    }

    public void setHeight(long height) {
        this.setSide(height);
    }

    public void setWidth(long width) {
        this.setSide(width);
    }

    public long getSide() {
        return side;
    }

    public void setSide(long side) {
        this.side = side;
    }
}

这个样子,编译器是可以通过的,也可以正常使用,但是这样就符合里氏代换原则了吗,肯定不是的。

我们不是为了继承而继承,只有真正符合继承条件的情况下我们才去继承,所以像这样为了继承而继承,强行实现继承关系的情况也是不符合里氏代换原则的。

但这是为什么呢?,我们运行一下

public class MainClass {
    public static void main(String[] args) {
        Rectangle rectangle= new Rectangle ();
        rectangle.setHeight(10);
        rectangle.setWidth(20);
        test(rectangle);
        
        Rectangle square = new Square ();
        square.setHeight(10);
        test(square);
    }
    
    public static void test(Rectangle rectangle) {
        System.out.println(rectangle.getHeight());
        System.out.println(rectangle.getWidth());
    }
}

结果:

在这里插入图片描述
我们忽然发现,很正常啊,为什么不可以,但是我们继续修改

public class MainClass {
    public static void main(String[] args) {
        Rectangle rectangle= new Rectangle ();
        rectangle.setHeight(10);
        rectangle.setWidth(20);
        resize(rectangle);
        
        Rectangle square= new Square();
        square.setHeight(10);
        resize(square);
    }
    
    public static void test(Rectangle rectangle) {
        System.out.println(rectangle.getHeight());
        System.out.println(rectangle.getWidth());
    }
    
    public static void resize(Rectangle rectangle) {
        while(rectangle.getHeight() <= rectangle.getWidth()) {
            rectangle.setHeight(rectangle.getHeight() + 1);
            test(rectangle);
        }
    }
}

当长方形运行时,可以正常运行,而正方形则会造成死循环,所以这种继承方式不一定恩能够适用于所有情况,所以不符合里氏代换原则。

还有一种形式,我们抽象出一个四边形接口,让长方形和正方形都实现这个接口

 public interface Quadrilateral{
     public long getWidth();
     public long getHeight();
 }
public class Rectangle implements Quadrilateral{
    private long width;
    private long height;
    
    public long getWidth() {
        return width;
    }
    public void setWidth(long width) {
        this.width = width;
    }
    public long getHeight() {
        return height;
    }
    public void setHeight(long height) {
        this.height = height;
    }
}
package com.ibeifeng.ex3;

public class Square implements Quadrilateral{
    private long side;

    public long getHeight() {
        return this.getSide();
    }

    public long getWidth() {
        return this.getSide();
    }

    public void setHeight(long height) {
        this.setSide(height);
    }

    public void setWidth(long width) {
        this.setSide(width);
    }

    public long getSide() {
        return side;
    }

    public void setSide(long side) {
        this.side = side;
    }
}
public class MainClass {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle .setHeight(10);
        rectangle .setWidth(20);
        test(rectangle);
        
        Square square= new Square ();
        square.setHeight(10);
        test(square);
    }
    
    public static void test(Quadrilateral quadrilateral) {
        System.out.println(quadrilateral.getHeight());
        System.out.println(quadrilateral.getWidth());
    }
}

对于长方形和正方形,取width和height是它们共同的行为,但是给width和height赋值,两者行为不同,因此,这个抽象的四边形的类只有取值方法,没有赋值方法。上面的例子中那个方法只会适用于不同的子类,LSP也就不会被破坏。

注意事项
  在进行设计的时候,尽量从抽象类继承,而不是从具体类继承。如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口。当然这个只是一个一般性的指导原则,使用的时候还要具体情况具体分析。

四、依赖倒转原则(Dependence Inversion Principle )

依赖倒转原则概述

依赖倒转(Dependence Inversion Principle ):是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

1.抽象不应该依赖于细节,细节应该依赖于抽象。

2.高层模块不依赖底层模块,两者都依赖抽象。

我们举个例子:电脑有不同的组件,硬盘,内存,主板。

硬盘抽象类

 //硬盘抽象类
 public abstract class HardDisk {
    public abstract void doSomething();
 }

具体硬盘(希捷硬盘)

//希捷硬盘
public class XiJieHardDisk extends HardDisk {

    public void doSomething() {
        System.out.println("希捷硬盘");
    }
}

具体硬盘(西数硬盘)

public class XiShuHardDisk extends HardDisk {

    public void doSomething() {
        System.out.println("西数硬盘");
    }
}

主板抽象类

 //主板抽象类
 public abstract class MainBoard {
     public abstract void doSomething();
 }

具体主板(华硕主板)

public class HuaShuoMainBoard extends MainBoard{

    public void doSomething() {
        System.out.println("华硕主板");
    }
}

具体主板(微星主板)

public class WeiXingMainBoard extends MainBoard {

    public void doSomething() {
        System.out.println("微星主板");
    }
}

内存抽象类

 //内存抽象类
 public abstract class Memory {
     public abstract void doSomething();
 }

具体内存(金士顿内存)

public class JinShiDunMemory extends Memory {

    public void doSomething() {
        System.out.println("金士顿内存");
    }
}

具体内存(三星内存)

public class SanxingMemory extends Memory {

    public void doSomething() {
        System.out.println("三星内存");
    }
}

现在,电脑的各个零部件都有了,只差电脑了。首先,我们不按照依赖倒转原则,按照传统模式

传统的过程式设计倾向于使高层次的模块依赖于低层次的模块,抽象层依赖于具体的层次。
在这里插入图片描述

这样,电脑应该是这样的

//电脑
public class Computer{
    private HuaShuoMainBoard huaShuoMainBoard;
    private JinShiDunMemory jinShiDunMemory;
    private XiJieHardDisk xiJieHardDisk;
    
    public HuaShuoMainBoard getHuaShuoMainBoard() {
        return huaShuoMainBoard;
    }
    public void setHuaShuoMainBoard(HuaShuoMainBoard huaShuoMainBoard) {
        this.huaShuoMainBoard = huaShuoMainBoard;
    }
    public JinShiDunMemory getJinShiDunMemory() {
        return jinShiDunMemory;
    }
    public void setJinShiDunMemory(JinShiDunMemory jinShiDunMemory) {
        this.jinShiDunMemory = jinShiDunMemory;
    }
    public XiJieHardDisk getXiJieHardDisk() {
        return xiJieHardDisk;
    }
    public void setXiJieHardDisk(XiJieHardDisk xiJieHardDisk) {
        this.xiJieHardDisk = xiJieHardDisk;
    }
}

这时,要组装一台电脑。

public class MainClass {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.setHuaShuoMainBoard(new HuaSuoMainBoard());
        computer.setJinShiDunMemory(new JinShiDunMemory());
        computer.setXiJieHardDisk(new XiJieHardDisk());
        
        computer.setHuaShuoMainBoard(new WeiXingMainBoard());//报错,无法安装
    }
}

可以看到,这种情况下,这台电脑就只能安装华硕主板,金士顿内存和希捷硬盘了,这对用户肯定是不友好的,用户有了机箱肯定是想按照自己的喜好,选择自己喜欢的配件。

电脑就是高层业务逻辑,主板,内存,硬盘就是中层模块,还有更低的底层模块我们没有写那么细,但都是一个意思,这样的方式显然是不可取的。

下面,我们改造一下,让Computer依赖接口或抽象类,下面的模块同样如此

在这里插入图片描述
在这里插入图片描述

Computer

public class Computer {
    private MainBoard mainBoard;
    private Memory memory;
    private HardDisk harddisk;

    public MainBoard getMainBoard() {
        return mainBoard;
    }

    public void setMainBoard(MainBoard mainBoard) {
        this.mainBoard = mainBoard;
    }

    public Memory getMemory() {
        return memory;
    }

    public void setMemory(Memory memory) {
        this.memory = memory;
    }

    public HardDisk getHarddisk() {
        return harddisk;
    }

    public void setHarddisk(HardDisk harddisk) {
        this.harddisk = harddisk;
    }
}

这时,再组装

public class MainClass {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.setMainBoard(new HuaSuoMainBoard());
        computer.setMemory(new JinShiDunMemory());
        computer.setHarddisk(new XiJieHardDisk());
        
        computer.setMainBoard(new WeiXingMainBoard());//完全没有问题
    }
}

这样,用户就可以根据自己的喜好来选择自己喜欢的品牌,组装电脑了。

为什么要采取依赖倒转这种方式

面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。

面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。

依赖倒转模式应用实例

  1. 工厂方法模式
  2. 模板方法模式
  3. 迭代模式

综上所诉,我们可以看出一个应用中的重要策略决定及业务模型正是在这些高层的模块中。也正是这些模型包含着应用的特性。但是,当这些模块依赖于低层模块时,低层模块的修改将会直接影响到它们,迫使它们也去改变。这种境况是荒谬的。应该是处于高层的模块去迫使那些低层的模块发生改变。应该是处于高层的模块优先于低层的模块。无论如何高层的模块也不应依赖于低层的模块。而且,我们想能够复用的是高层的模块。通过子程序库的形式,我们已经可以很好地复用低层的模块了。当高层的模块依赖于低层的模块时,这些高层模块就很难在不同的环境中复用。但是,当那些高层模块独立于低层模块时,它们就能很简单地被复用了。这正是位于框架设计的最核心之处的原则。

五、迪米特法则(Law of Demeter )

迪米特法则概述

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

迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,于1987年秋天由lan holland在美国东北大学为一个叫做迪米特的项目设计提出的。

迪米特法则的模式与意义

迪米特法则可以简单说成:talk only to your immediate friends。 对于OOD来说,又被解释为下面几种方式:一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。

迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

狭义的迪米特法则

如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一类的某一个方法的话,可以通过第三者转发这个调用。

这么看不太形象,我们来举个例子,和陌生人说话,甲和朋友认识,朋友和陌生人认识,而甲和陌生人不认识,这时甲可以直接和朋友说话,朋友可以直接和陌生人说话,而如果甲想和陌生人说话,就必须通过朋友

首先,第一种方式代码实现:

public class Jia{
    public void play(Friend friend){
        friend.play();
    }
    
    public void play(Stranger stranger) {
        stranger.play();
    }
}
//朋友
public class Friend {
    public void play(){
        System.out.println("朋友");
    }
}
//陌生人
public class Stranger {
    public void play(){
        System.out.println("陌生人");
    }
}

这种方式是肯定不对的,甲根本没有通过朋友,直接引用了陌生人的方法,不符合迪米特法则

第二种方式
在这里插入图片描述

代码实现

//甲
public class Jia{
    public void play(Friend friend){
        friend.play();
        Stranger stranger = friend.getStranger();
        stranger.play();
    }
}
//朋友
public class Friend {
    public void play(){
        System.out.println(朋友");
    }
    
    public Stranger getStranger() {
        return new Stranger();
    }
}
//陌生人
public class Stranger {
    public void play(){
        System.out.println("陌生人");
    }
}

这样的方式呢,看上去陌生人的实例是通过朋友来创建了,但还是不行,因为甲中包含的陌生人的引用,甲还是和陌生人直接关联上了,所以,不符合迪米特法则,我们要的是甲和陌生人没有一丁点直接关系

第三种方式

在这里插入图片描述

//甲
public class Jia{
    private Friend friend;
    
    public Friend getFriend() {
        return friend;
    }

    public void setFriend(Friend friend) {
        this.friend = friend;
    }

    public void play(Friend friend){
        friend.play();
    }
}
//朋友
public class Friend {
    public void play(){
        System.out.println("朋友");
    }
    
    public void playWithStranger() {
        Stranger stranger = new Stranger();
        stranger.play();
    }
}
 public class Stranger {
    public void play(){
        System.out.println("陌生人");
    }
 }

这种方式,甲和陌生人之间就没有了任何直接联系,这样就避免了甲和陌生人的耦合度过高

当然还有一种更好的方式,与依赖倒转原则结合,为陌生人创建一个接口

在这里插入图片描述

//甲
public class Jia{
    private Friend friend;
    private Stranger stranger;

    public Stranger getStranger() {
        return stranger;
    }

    public void setStranger(Stranger stranger) {
        this.stranger = stranger;
    }

    public Friend getFriend() {
        return friend;
    }

    public void setFriend(Friend friend) {
        this.friend = friend;
    }

    public void play() {
        System.out.println("someone play");
        friend.play();
        stranger.play();
    }
}
 public class Friend {
     public void play(){
         System.out.println("朋友");
    }
 }
 //陌生人抽象类
 public abstract class Stranger {
     public abstract void play();
 }
//具体陌生人
public class StrangerA extends Stranger {

    public void play() {
        System.out.println("陌生人");
    }

}

这样的方式,和甲直接通信的是陌生人的抽象父类,和具体陌生人没有直接关系,所以符合迪米特法则

狭义的迪米特法则的缺点:

  1. 在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商务逻辑无关。
  2. 遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。

迪米特法则应用实例

  1. 外观模式
  2. 中介者模式

猜你喜欢

转载自blog.csdn.net/qq_21918021/article/details/88094485
今日推荐