设计模式七大原则

设计模式七大原则

  1. 单一职责原则
  • 对类来说,一个类只负责一项职责。比如说A类有两个职责,职责1,职责2;那么就可以把A类分为两个类,这就是遵循了单一职责原则
  1. 接口隔离原则
  • 客户端类不需要依赖它不需要的接口,既一个类对另一类依赖应该建立在最小接口上
  • 简单的说客户类,要使用一个接口的时候,不要把用不到的方法也实现了
  1. 依赖倒转原则
  • 高层模块不要依赖底层模块,二者都要依赖其抽象
  • 抽象不要依赖细节,细节应该依赖抽象
  • 依赖倒转(倒置)的中心思想就是面向接口编程
  • 依赖倒转原则基于这样的设计理念;对于细节的多边形,抽象要稳定的多。以抽象为基础搭建的架构要比以细节为基础的架构稳定的多。在Java中抽象就是接口或抽象类,细节就是实体类
  • 使用接口或抽象类目的是指定好的规范,而不涉及任何具体操作。把展示细节部分交给他们的实现类完成
  1. 里氏替换原则
  • 在继承上,尽量不要重写父类的方法
  • 适当情况下,可以使用组合、聚合来代替继承
  • 可以使用父类的地方也可以使用子类
  1. 开闭原则OCP
  • 开闭原则是最重要的原则
  • 对扩展开发;对修改关闭。
  • 对于增加服务的时候,不会影响已使用的代码;
  1. 迪米特法则
  2. 合成复用原则

设计模式目的

  • 代码重用性
  • 可读性
  • 可扩展性
  • 可靠性
  • 使程序高内聚,低耦合

单一职责原则

说明
对类来说,一个类只负责一项职责。比如说A类有两个职责,职责1,职责2;那么就可以把A类分为两个类,这就是遵循了单一职责原则
代码演示的升级变化

/**
 * @author HiWin10
 * @date 2020/7/29
 * 学习单一职责原则,使用交通工具来进行举例,
  * 该版本类,是没有遵循单一职责原则的,应该这个类的公交类很复杂,使用了很多交通工具,解决办法是,根据交通工具的类别分别创建对应的类
  */ public class SingleResponsibility1 {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
  vehicle.run("摩托车");
  vehicle.run("飞机");
  vehicle.run("轮船");
  }
}

/**
 * 交通工具类
  * 没有遵循单一职责原则,解决办法是,根据交通工具分别创建对应类  具体看SingleResponsibility2
 */ class Vehicle {
    public void run (String vehicle) {
        System.out.println(vehicle + "在公路上运行....");
  }
}
/**
 * @author HiWin10
 * @date 2020/7/29
 * 1. 遵守了单一职责原则,
  * 2. 但是这样改动很大,将类分解,还要修改客户端
  * 3. 直接修改Vehicle,对于咱们目前的场景,这个类只有一个功能的话,那么可以有方法单一职责具体看SingleResponsibility3,
 * 当然SingleResponsibility3具体要看需求的
  */ public class SingleResponsibility2 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
  roadVehicle.run("摩托");
  AriVehicle ariVehicle = new AriVehicle();
  ariVehicle.run("飞机");
  WaterVehicle waterVehicle = new WaterVehicle();
  waterVehicle.run("轮船");
  }
}

class RoadVehicle {
    public void run (String vehicle) {
        System.out.println(vehicle + "在公路上运行....");
  }
}

class AriVehicle {
    public void run (String vehicle) {
        System.out.println(vehicle + "在天空运行....");
  }
}

class WaterVehicle {
    public void run (String vehicle) {
        System.out.println(vehicle + "在水里运行....");
  }
}
/**
 * @author HiWin10
 * @date 2020/7/29
 * 1. 这种方式没有对SingleResponsibility1而言没有大的修改,只是增加方法
  * 2. 这种方式虽然对类没有遵循单一职责原则,但是在方法上,遵循了单一职业原则
  * 3. 所以说即使是设计模式,也没有说必须的套路,要根据实际应用来使用
  */ public class SingleResponsibility3 {
    public static void main(String[] args) {
        Vehicle3 vehicle = new Vehicle3();
  vehicle.run("摩托");
  vehicle.runAir("飞机");
  vehicle.runWater("轮船");
  }
}

class Vehicle3 {
    public void run(String vehicle) {
        System.out.println(vehicle + "在公路上跑....");
  }

    public void runAir(String vehicle) {
        System.out.println(vehicle + "在天空运行....");
  }

    public void runWater(String vehicle){
        System.out.println(vehicle + "在水中运行....");
  }
}

小结

  1. 降低类的复杂度,一个类只负责意见事情
  2. 提高代码的可读性和维护性,因为一个类只做一件事情,那么这个类可以说是很简单
  3. 修改其他有关代码,不会影响其他代码
  4. 通常情况下,我们应对遵循单一职责原则,只有在代码逻辑十分简单,可以不遵守或者可以在方法级别上遵守

接口隔离原则

概念
客户端类不应该依赖它不要的接口,既一个类对另一个类的依赖应该建立在最小接口上
简答的说就是不需要的类不要依赖,不要的方法不要实现。

  1. 该图片中,A类使用B类的1,2,3方法;C类使用D类的1,4,5方法;B类和D类分别都实现了Interface1的所有方法。
  2. 那么根据接口隔离原则来讲,使用不到的不实现。也就是说B类不需要实现Interface1的4,5方法,D类不要实现Interface1的2,3方法
  3. 解决办法:将Interface1接口中方法分离,1方法单独一个接口,2,3单独一个接口,4,5单独一个接口
  4. 根据实际情况来定,这个接口隔离原则还可以使用抽象类来解决,或者普通的类也可以解决。

下面是没有遵循接口隔离原则的场景模拟

/**
 * @author HiWin10
 * @date 2020/7/31
 * 接口隔离原则模拟
  * 接口隔离就是一个接口有很多方法,每个实现都要排除不必要的方法
  */ public class Segregation1 {
    public static void main(String[] args) {
        A a = new A();
  a.depend1(new B());
  a.depend2(new B());
  a.depend3(new B());
  System.out.println("-----------------------");
  C c = new C();
  c.depend1(new D());
  c.depend4(new D());
  c.depend5(new D());
  }
}

/**
 * 接口有5个方法,全部都要实现
  */ interface Interface1 {
    void operation1();
 void operation2();
 void operation3();
 void operation4();
 void operation5(); }

/**
 * 接口的实现,B,但是A类只会依赖B类(使用),使用B类的1,2,3,方法
  */ class B implements Interface1{
    @Override
  public void operation1() {
        System.out.println("B 实现了Interface1的 operation1方法 ");
  }

    @Override
  public void operation2() {
        System.out.println("B 实现了Interface1的 operation2方法 ");
  }

    @Override
  public void operation3() {
        System.out.println("B 实现了Interface1的 operation3方法 ");
  }

    @Override
  public void operation4() {
        System.out.println("B 实现了Interface1的 operation4方法 ");
  }

    @Override
  public void operation5() {
        System.out.println("B 实现了Interface1的 operation5方法 ");
  }
}

/**
 * 接口的实现D,但是C类只会依赖D类(使用),使用D类的1,4,5,方法
  */ class D implements Interface1 {
    @Override
  public void operation1() {
        System.out.println("D 实现了Interface1的 operation1方法 ");
  }

    @Override
  public void operation2() {
        System.out.println("D 实现了Interface1的 operation2方法 ");
  }

    @Override
  public void operation3() {
        System.out.println("D 实现了Interface1的 operation3方法 ");
  }

    @Override
  public void operation4() {
        System.out.println("D 实现了Interface1的 operation4方法 ");
  }

    @Override
  public void operation5() {
        System.out.println("D 实现了Interface1的 operation5方法 ");
  }
}

/**
 * A类通过接口依赖B类(使用B类,只会使用1,2,3方法)
 * 通过这个就可以看到,我只想使用接口的三个方法,但是B类却实现了Interface1接口的所有方法
  * 这样的话对于A类来说4,5方法都是使用不到的,却实现了,从这里看代码就非常多余
  */ class A {
    public void depend1(Interface1 i){
        i.operation1();
  }
    public void depend2(Interface1 i){
        i.operation2();
  }
    public void depend3(Interface1 i){
        i.operation3();
  }
}

class C { // C类通过接口依赖D类(使用D类,只会使用1,4,5方法)
  public void depend1(Interface1 i){
        i.operation1();
  }
    public void depend4(Interface1 i){
        i.operation4();
  }
    public void depend5(Interface1 i){
        i.operation5();
  }
}

遵循接口隔离原则

package com.itcast.接口隔离原则;   /**
 * @author HiWin10
 * @date 2020/7/31
 * 接口隔离原则模拟
  * 接口隔离就是一个接口有很多方法,每个实现都要排除不必要的方法
  * 解决办法:
  * 将几个方法根据使用不同来拆除,比如说A类使用1,2,3方法;C类使用1,4,5方法,那么1方法就是1个类,2,3一类,4,5一个类
  * 也可以使用抽象类解决该问题
  */ public class Segregation2 {
    public static void main(String[] args) {
        A2 a = new A2();
  a.depend1(new B2());
  a.depend2(new B2());
  a.depend3(new B2());
  System.out.println("------------------");
  C2 c = new C2();
  c.depend1(new D2());
  c.depend4(new D2());
  c.depend5(new D2());    }
}

/**
 * 将接口方法根据使用来拆除,后期实现可以根据实际情况使用抽象类解决该问题
  */ interface Interface2 {
    void operation1(); }

interface Interface3 {
    void operation2();
 void operation3(); }
interface Interface4 {
    void operation4();
 void operation5(); }

class B2 implements Interface2, Interface3 {
    @Override
  public void operation1() {
        System.out.println("B2 实现了 Interface2的operation1方法");
  }

    @Override
  public void operation2() {
        System.out.println("B2 实现了 Interface2的operation2方法");
  }

    @Override
  public void operation3() {
        System.out.println("B2 实现了 Interface2的operation3方法");
  }
}

class D2 implements Interface2, Interface4 {
    @Override
  public void operation1() {
        System.out.println("D2 实现了 Interface2的operation1方法");
  }

    @Override
  public void operation4() {
        System.out.println("D2 实现了 Interface2的operation4方法");
  }

    @Override
  public void operation5() {
        System.out.println("D2 实现了 Interface2的operation5方法");
  }
}

class A2 {
    public void depend1(Interface2 i){
        i.operation1();
  }
    public void depend2(Interface3 i){
        i.operation2();
  }
    public void depend3(Interface3 i){
        i.operation3();
  }
}

class C2 {
    public void depend1(Interface2 i){
        i.operation1();
  }
    public void depend4(Interface4 i){
        i.operation4();
  }
    public void depend5(Interface4 i){
        i.operation5();
  }
}

依赖倒转原则

  1. 高层模块不要依赖底层模块,二者都要依赖其抽象
  2. 抽象不要依赖细节,细节应该依赖抽象
  3. 依赖倒转(倒置)的中心思想就是面向接口编程
  4. 依赖倒转原则基于这样的设计理念;对于细节的多边形,抽象要稳定的多。以抽象为基础搭建的架构要比以细节为基础的架构稳定的多。在Java中抽象就是接口或抽象类,细节就是实体类
  5. 使用接口或抽象类目的是指定好的规范,而不涉及任何具体操作。把展示细节部分交给他们的实现类完成

不遵循依赖倒转原则的代码

/**
 * @author HiWin10
 * @date 2020/7/31
 * 依赖倒转原则
  *  1. 高层模块不要依赖底层模块,二者都要依赖其抽象
  *  2. 抽象不要依赖细节,细节应该依赖抽象
  *  3. 依赖倒转(倒置)的中心思想就是面向接口编程
  *  4. 依赖倒转原则基于这样的设计理念;对于细节的多边形,抽象要稳定的多。
  * 以抽象为基础搭建的架构要比以细节为基础的架构稳定的多。在Java中抽象就是接口或抽象类,细节就是实体类
  *  5. 使用接口或抽象类目的是指定好的规范,而不涉及任何具体操作。把展示细节部分交给他们的实现类完成
  *
 * 案例:就是接受消息的一个案例
  */ public class DependecyInversion {
    public static void main(String[] args) {
        Person person = new Person();
  person.receive(new Email());
  }
}

/**
 * 消息类
  */ class Email {
    String getInfo(){
        return "邮箱消息:Hello, World!";
  }
}

/**
 * 分析:
  *  1. 简单,比较容易想到
  *  2. 缺乏灵活性,如果我们发送Person类的消息不是Email的了,而是微信消息那么该如何操作?这种写法的话,代码写死了缺乏灵活性
  * 解决思路:中间定义一个抽象层,方法参数为抽象层,实体类实现了抽象层,这样来调用就会灵活的多
  */ class Person{
    public void receive(Email email){
        System.out.println(email.getInfo());
  }
}

遵循依赖倒转原则

/**
 * @author HiWin10
 * @date 2020/7/31 */ public class DependecyInversion_OK {
    public static void main(String[] args) {
        Person1 person = new Person1();
  // 发送电子邮箱消息
  person.receive(new Email1());    // 发送微信消息
  person.receive(new Weixin());    }
}

interface IReceive {
    public String getInfo(); }

/**
 * 电子邮件消息类
  */ class Email1 implements IReceive {
    @Override
  public String getInfo(){
        return "邮箱消息:Hello, World!";
  }
}

/**
 * 微笑消息类
  */ class Weixin implements IReceive {
    @Override
  public String getInfo() {
        return "微信消息:Are you OK";
  }
}

class Person1{
    public void receive(IReceive receive){
        System.out.println(receive.getInfo());
  }
}

依赖倒转原则关系传递的三种方式

  1. 构造器传递
  2. setter方法传递
  3. 方法接口传递

代码示例

/**
 * @author HiWin10
 * @date 2020/7/31
 * 依赖倒转原则三种传递方式
  */ public class DependencyPass {
}

// 方式1:通过接口传递 // 开关的接口 interface IOpenAndClose {
    public void open(ITV itv); // 抽象方法,接受接口 }

/**
 * 接口
  */ interface ITV {
    public void play(); }

/**
 * 电视的实现
  */ class ChangHong implements ITV {
    @Override
  public void play() {
        System.out.println("长虹电视开机了...");
  }
}

/**
 * 操作类的实现
  */ class OpenAndClose implements IOpenAndClose {
    @Override
  public void open(ITV itv) {
        itv.play();
  }
}

/*******************/ // 方式2:构造器传递 interface IOpenAndClose2 {
    public void open(ITV itv); // 抽象方法,接受接口 }
/**
 * 接口
  */ interface ITV2 {
    public void play(); }

class ChangHong2 implements ITV2 {
    @Override
  public void play() {
        System.out.println("长虹电视开机了...");
  }
}

/**
 * 构造器方式
  */ class OpenAndClose2 implements IOpenAndClose2 {
    private ITV2 itv2;   public OpenAndClose2(ITV2 itv2) {
        this.itv2 = itv2;
  }

    @Override
  public void open(ITV itv) {

    }
}

/*******************/ // 方式3:set方法 interface IOpenAndClose3 {
    public void open(ITV itv); // 抽象方法,接受接口 }
/**
 * 接口
  */ interface ITV3 {
    public void play(); }

class ChangHong3 implements ITV3 {
    @Override
  public void play() {
        System.out.println("长虹电视开机了...");
  }
}

/**
 * set方法
  */ class OpenAndClose3 implements IOpenAndClose3 {
    private ITV3 itv3;   public void setItv3(ITV3 itv3) {
        this.itv3 = itv3;
  }

    @Override
  public void open(ITV itv) {
        this.itv3.play();
  }
}

小结

  1. 底层模块尽量都要有抽象或接口。或者两者都有,程序稳定性会更好
  2. 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
  3. 继承时遵循里氏替换原则

里氏替换原则

继承带来的优劣势

  1. 继承包含这样一个含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然没有强制所有子类必须遵循这些契约,但是如果子类随意重写父类实现的方法,就会对继承体系造成破坏

  2. 继承在给程序带来便利同时,也会有弊端。比如使用继承会给程序带来侵入性,程序的可移植性减低,增加对象的耦合,如果一个类被其他类所继承,则这个类需要修改时,必须考虑所有子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障

  3. 问题提示:在编程中,如何正确的使用继承?=> 里氏替换原则

里氏替换原则概念

  1. 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1都代还成o2时,程序P的行为没有发生变化,那么T2是类型T1的子类。或句话说,所有引用基类的地方必须能透明地使用其子类对象
  2. 使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  3. 里氏替换原则告诉我们,继承实际上让两个类的耦合提高了,在适当情况下,可以通过聚合、组合、依赖来解决问题

问题产出

/**
 * @author HiWin10
 * @date 2020/8/3
 * 里氏替换原则
  * 里氏替换原则需要满足一下几点:
  *      1. 所有使用基类的地方都可以使用子类来代替
  *      2. 尽量不要重写父类的方法
  *      3. 在适当情况下,可以选择使用聚合、组合、依赖来解决问题
  */ public class Liskow1 {
    public static void main(String[] args) {
        A a = new A();
  System.out.println("20-10=" + a.func(20, 10));
  System.out.println("20-18=" + a.func(20, 18));    System.out.println("------------");
  B b = new B();
  System.out.println("11-3=" + b.func(10, 20));
  System.out.println("20-18+1=" + b.fun2(20, 18));    }
}

class A {
    /**
 * 减法
  */
  public Integer func(int a, int b){
        return a - b;
  }
}

// 按照main中B对象的逻辑,是想要使用A的方法,但是B类不小心重写了A的其中方法,所以说才会发生此事,计算失误。违反了里氏替换原则 // 解决办法:使用聚合、组合、依赖来解决问题 class B extends A {
    /**
 * B类不小心重写了 A类的方法
  */
  public Integer func(int a, int b) {
        return a + b;
  }

    public Integer fun2(int a, int b) {
        return func(a, b) + 1;
  }
}
  • A类有一个方法func作用减法。
  • B类继承A类想要在main中使用func但是不小心重写了A类的func方法,所以在使用的时候逻辑上不正确
  • 解决方法就是使用使用聚合、组合、依赖来解决问题

代码如下:

/**
 * @author HiWin10
 * @date 2020/8/3
 * 里氏替换原则
  * 里氏替换原则需要满足一下几点:
  *      1. 所有使用基类的地方都可以使用子类来代替
  *      2. 尽量不要重写父类的方法
  *      3. 在适当情况下,可以选择使用聚合、组合、依赖来解决问题
  */ public class Liskow2 {
    public static void main(String[] args) {
        A1 a = new A1();
  System.out.println("20-10=" + a.func(20, 10));
  System.out.println("20-18=" + a.func(20, 18));    System.out.println("------------");
  B1 b = new B1();
  System.out.println("11-3=" + b.func(11, 3));
  System.out.println("20-18+1=" + b.fun2(20, 18));    }
}

class A1 {
    /**
 * 减法
  */
  public Integer func(int a, int b){
        return a - b;
  }
}

// 按照main中B对象的逻辑,是想要使用A的方法,但是B类不小心重写了A的其中方法,所以说才会发生此事,计算失误。违反了里氏替换原则 // 解决办法:使用聚合、组合、依赖来解决问题/本次使用组合来解决 class B1  {
    private A1 a = new A1();    /**
 * B类不小心重写了 A类的方法
  */
  public Integer func(int a, int b) {
//        return a + b;
  return this.a.func(a, b);
  }

    public Integer fun2(int a, int b) {
        return this.a.func(a, b) + 1;
  }
}

开闭原则[最重要]

概念

  1. 开闭原则是编程中最基础、最重要的原则
  2. 一个软件实体如类,模块和函数应该对扩展开发(对提供方),对修改关闭(对使用方)。由抽象构建框架,用实现扩展细节。
  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码实现变化。
  4. 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则

代码示例
该代码是,通过方法来决定绘画那种图形,这里是没有遵循开闭原则的,你会发现,再添加一个图形的时候,不管是服务方还是使用方都要修改或扩展代码

/**
 * @author HiWin10
 * @date 2020/8/3
 * 开闭原则
  *  1. 对功能扩展开放,对修改关闭
  *  2. 尽量使用软件的实体行为实现变化,而不是修改已有代码
  */ public class Ocp1 {
    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
  graphicEditor.drawShape(new Rectangle());
  graphicEditor.drawShape(new Circle());    // 绘画新添加的三角形
  graphicEditor.drawShape(new Triangle());
  }
}

// 没有遵循开闭原则,当要添加一个图形的时候,要修改的东西太多, // 首先,服务需要修改 其次使用方也要修改代码 // [使用方] // 解决办法:添加抽象层,添加好规范好,由具体实现来完成具体图形的绘画 
class GraphicEditor {
  public void drawShape(Shape s){
        if (s.m_type == 1){
            drawRectangle(s);
  } else  if (s.m_type == 2) {
            drawCircle(s);
  } // 首先这里修改 添加的三角形
  else if (s.m_type == 3){
            drawTriangle(s);
  }
    }

    // 绘制矩形
  private void drawRectangle(Shape s) {
        System.out.println(" 绘制矩形 ");
  }
    // 绘制圆形
  private void drawCircle(Shape s) {
        System.out.println(" 绘制圆型 ");
  }

    // 这里修改添加新方法drawTriangle画三角形
  private void drawTriangle(Shape s){
        System.out.println("绘制 三角形");
  }
}

class Shape {
    int m_type; }

// 矩形 class Rectangle extends Shape {
    public Rectangle() {
        super.m_type = 1;
  }
}

// 圆形 class Circle extends Shape {
    public Circle() {
        super.m_type = 2;
  }
}

// 新添加三角形 [服务方] class Triangle extends Shape {
    public Triangle() {
        super.m_type = 3;
  }
}

问题

  • 虽然对提供方是扩展(新增类),对使用方还是修改
  • 解决思路:中间添加抽象层,然后图形的绘画交给具体实现,而不是由使用方来绘画
/**
 * @author HiWin10
 * @date 2020/8/3
 * 开闭原则
  *  1. 对功能扩展开放,对修改关闭
  *  2. 尽量使用软件的实体行为实现变化,而不是修改已有代码
  *
 * 开始使用开闭原则
  * 添加抽象类,定义接口 然后每个实现来绘画对象图形
  */ public class Ocp2 {
    public static void main(String[] args) {
        GraphicEditor1 graphicEditor = new GraphicEditor1();
  graphicEditor.drawShape(new Rectangle1());
  graphicEditor.drawShape(new Circle1());    // 绘画新添加的三角形
  graphicEditor.drawShape(new Triangle1());
  }
}

class GraphicEditor1 {
    public void drawShape(Shape2 s){
        s.drawShape();
  }
}

abstract class Shape2 {
    int m_type;
 public abstract void drawShape(); }

// 矩形 class Rectangle1 extends Shape2 {
    public Rectangle1() {
        super.m_type = 1;
  }

    @Override
  public void drawShape() {
        System.out.println(" 绘制矩形 ");
  }
}

// 圆形 class Circle1 extends Shape2 {
    public Circle1() {
        super.m_type = 2;
  }

    @Override
  public void drawShape() {
        System.out.println(" 绘制圆型 ");
  }
}

// 新添加三角形 class Triangle1 extends Shape2 {
    public Triangle1() {
        super.m_type = 3;
  }

    @Override
  public void drawShape() {
        System.out.println("绘制 三角形");
  }
}

小结
以后在设计程序尽量使用抽象,这样的话程序会非常灵活。但是同样的程序也会非常负责。

迪米特法则

概念

  1. 一个对象应该对其他对象保持最少了解
  2. 类与类的关系越密切,耦合度越高
  3. 迪米特法则,又叫最少知道原则。既一个类对自己依赖的类知道的越少越好。也就是说。对于依赖的类不管多么复杂,都要尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息
  4. 迪米特法则还有更简单的定义:只与直接的朋友通信
  5. 直接朋友:每个对象都会与其他对象有耦合关系,只要连个对象之间有耦合关系,我们就说这两个对象是朋友关系。我们称出现成员变量、方法参数、方法返回值就是直接朋友,而在局部出现的类就不是直接朋友。也就是说,陌生的类最好不要以局部变量方式存在类中

耦合关系分别有:依赖,关联,组合,聚合等。

代码示例

  1. 有一个学校,下属有各个学院和总部,现要求打印出学校总部员工ID和学院员工ID
  2. 代码演示
/**
 * @author HiWin10
 * @date 2020/8/4
 * 迪米特法则
  * 1. 一个对象应该对其他对象保持最少的了解
  * 2. 类与类关系密切,耦合度就越高
  * 3. 迪米特法则又叫最少知道原则,既一个类对自己依赖的类知道越少越好,也就是说,对被依赖的类不管多么复杂,都要尽量将逻辑封装在类内部,对外提供public方法,不对外泄露
  * 4. 迪米特法则还有更简单定义:只与直接朋友通信
  *
 * 直接朋友:该类的属性,或者setter方法;或者方法参数
  */ public class Demeter1 {
    public static void main(String[] args) {
        SchoolManager schoolManager = new SchoolManager();
  schoolManager.printAllEmployee(new CollegeManager());
  }
}

// 学院总员工类 class Employee {
    private String id;   public String getId() {
        return id;
  }

    public void setId(String id) {
        this.id = id;
  }

    @Override
  public String toString() {
        return "Employee{" +
                "id='" + id + '\'' +
                '}';
  }
}

// 学院的员工类 class CollegeEmployee {
    private String id;   public String getId() {
        return id;
  }

    public void setId(String id) {
        this.id = id;
  }

    @Override
  public String toString() {
        return "CollegeEmployee{" +
                "id='" + id + '\'' +
                '}';
  }
}

// 管理学院员工的管理类 class CollegeManager {
    // 返回学院所有员工
  public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<>();
 for (int i = 0; i < 10; i++) {
            CollegeEmployee emp = new CollegeEmployee();
  emp.setId("学院员工id = " + i);
  list.add(emp);
  }
        return list;
  }
}

// 学院管理类 // 分析:该类的直接朋友:Employee、CollegeManager // CollegeEmployee 类不是直接朋友违背了迪米特法则。将这一段代码移动到该类实现上去 class SchoolManager {
    // 返回学院总部的员工
  public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<>();
 for (int i = 0; i < 5; i++) {
            Employee emp = new Employee();
  emp.setId("学院总员工ID = " + i);
  list.add(emp);
  }
        return list;
  }

    // 该方法返回输出学院总部和学院员工信息
  public void printAllEmployee(CollegeManager sub) {
        // 获取到学院员工
  // 解决办法:将这一段代码移动到该类实现上去,
  List<CollegeEmployee> list = sub.getAllEmployee();
  System.out.println("-------学院员工-------");
  list.forEach(System.out::println);    // 获取学院总部员工
  List<Employee> employeeList = this.getAllEmployee();
  System.out.println("-------学院总员工-------");
  employeeList.forEach(System.out::println);
  }
}

问题发现

  1. CollegeEmployee 类不是直接朋友违背了迪米特法则。将这一段代码移动到该类实现上
/**
 * @author HiWin10
 * @date 2020/8/4
 * 迪米特法则
  * 1. 一个对象应该对其他对象保持最少的了解
  * 2. 类与类关系密切,耦合度就越高
  * 3. 迪米特法则又叫最少知道原则,既一个类对自己依赖的类知道越少越好,也就是说,对被依赖的类不管多么复杂,都要尽量将逻辑封装在类内部,对外提供public方法,不对外泄露
  * 4. 迪米特法则还有更简单定义:只与直接朋友通信
  *
 * 直接朋友:该类的属性,或者setter方法;或者方法参数
  */ public class Demeter2 {
    public static void main(String[] args) {
        SchoolManager2 schoolManager = new SchoolManager2();
  schoolManager.printAllEmployee(new CollegeManager2());
  }
}

// 学院总员工类 class Employee2 {
    private String id;   public String getId() {
        return id;
  }

    public void setId(String id) {
        this.id = id;
  }

    @Override
  public String toString() {
        return "Employee{" +
                "id='" + id + '\'' +
                '}';
  }
}

// 学院的员工类 class CollegeEmployee2 {
    private String id;   public String getId() {
        return id;
  }

    public void setId(String id) {
        this.id = id;
  }

    @Override
  public String toString() {
        return "CollegeEmployee{" +
                "id='" + id + '\'' +
                '}';
  }
}

// 管理学院员工的管理类 class CollegeManager2 {
    // 返回学院所有员工
  public List<CollegeEmployee2> getAllEmployee() {
        List<CollegeEmployee2> list = new ArrayList<>();
 for (int i = 0; i < 10; i++) {
            CollegeEmployee2 emp = new CollegeEmployee2();
  emp.setId("学院员工id = " + i);
  list.add(emp);
  }
        return list;
  }

    // 显示移动到改方法中
  public void printAllemployee() {
        List<CollegeEmployee2> list = this.getAllEmployee();
  System.out.println("-------学院员工-------");
  list.forEach(System.out::println);
  }
}

// 学院管理类 // 分析:该类的直接朋友:Employee、CollegeManager // CollegeEmployee 类不是直接朋友违背了迪米特法则。将这一段代码移动到该类实现上去 class SchoolManager2 {
    // 返回学院总部的员工
  public List<Employee2> getAllEmployee() {
        List<Employee2> list = new ArrayList<>();
 for (int i = 0; i < 5; i++) {
            Employee2 emp = new Employee2();
  emp.setId("学院总员工ID = " + i);
  list.add(emp);
  }
        return list;
  }

    // 该方法返回输出学院总部和学院员工信息
  public void printAllEmployee(CollegeManager2 sub) {
        // 获取到学院员工
  // 解决办法:将这一段代码移动到该类实现上去,
  sub.printAllemployee();    // 获取学院总部员工
  List<Employee2> employeeList = this.getAllEmployee();
  System.out.println("-------学院总员工-------");
  employeeList.forEach(System.out::println);
  }
}

合成复用原则

就是尽量以合成/聚合的方式,而不是使用继承

设计模式总结

  1. 找到应用中可能要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起
  2. 针对接口编程。而不是针对实现编程
  3. 为了交互对象之间松耦合设计而努力
posted @ 2020-08-05 14:48  蜡笔没有大象  阅读(165)  评论(0)    收藏  举报