zoukankan      html  css  js  c++  java
  • 设计模式七大原则——依赖倒转原则

    一、基本介绍

      依赖倒转原则(Dependence Inversion Principle)是指:

      (1)高层模块不应该依赖低层模块,二者都应该依赖其抽象

      (2)抽象不应该依赖细节,细节应该依赖抽象

      (3)依赖倒转(倒置)的中心思想是面向接口编程

      (4)依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象指的是接口(interface)或抽象类(abstract class),细节就是具体的实现类

      (5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成

    二、应用实例

      编程完成Person接收消息功能

      具体代码如下:

     1 public class DependencyInversion {
     2     public static void main(String[] args) {
     3         Person person = new Person();
     4         person.receive(new Email());
     5     }
     6 }
     7 
     8 class Email {
     9     public String getInfo() {
    10         return "电子邮件信息:Hello World!";
    11     }
    12 }
    13 
    14 //完成Person接收消息
    15 class Person {
    16     public void receive(Email email) {
    17         System.out.println(email.getInfo());
    18     }
    19 }

      运行结果:

      

      分析:

        以上代码虽然完成了功能,但是Person类和Email类之间存在严重的耦合,如果我们接收信息的对象不是Email,而是微信,短信,则需要增加新的类,而且Person类中需要增加新的方法

      解决方案:

        引入一个接口IReceiver,表示接收者,这样Person类和IReceiver接口发生依赖,因为微信,短信等都属于接收的方式,所以实现IReceiver接口即可,这样就符合了依赖倒转原则

      具体代码实现:

     1 public class DependencyInversion {
     2     public static void main(String[] args) {
     3         Person person = new Person();
     4         person.receive(new Email());
     5         person.receive(new Wechat());
     6     }
     7 }
     8 
     9 //定义接口
    10 interface IReceiver {
    11     String getInfo();
    12 }
    13 
    14 //电子邮件
    15 class Email implements IReceiver {
    16     @Override
    17     public String getInfo() {
    18         return "电子邮件信息:Hello World!";
    19     }
    20 }
    21 
    22 //微信
    23 class Wechat implements IReceiver {
    24     @Override
    25     public String getInfo() {
    26         return "微信信息:Hello World!";
    27     }
    28 }
    29 
    30 //完成Person接收消息
    31 class Person {
    32     public void receive(IReceiver iReceiver) {
    33         System.out.println(iReceiver.getInfo());
    34     }
    35 }

      运行结果:

      

      这样写之后,每次如果新增接收消息的方式,直接实现IReceiver接口即可,同时将Person类中的receive方法的参数改为IReceiver接口,也避免了类与类之间的耦合

    三、依赖关系传递的三种方式和应用案例

      三种方式:接口传递、构造方法传递、setter方式传递

      应用实例:编程实现打开电视机操作(重在理解思想)

      方式一:

      (1)接口传递

     1 /**
     2  * 通过接口传递实现依赖
     3  */
     4 public class InterfaceImplements {
     5     public static void main(String[] args) {
     6         ChangHongTV changHongTV = new ChangHongTV();
     7         OpenAndClose openAndClose = new OpenAndClose();
     8         openAndClose.open(changHongTV);
     9     }
    10 }
    11 
    12 //开关的接口
    13 interface IOpenAndClose {
    14     void open(ITV itv);
    15 }
    16 
    17 //TV的接口
    18 interface ITV {
    19     void play();
    20 }
    21 
    22 //实现接口
    23 class OpenAndClose implements IOpenAndClose {
    24     @Override
    25     public void open(ITV itv) {
    26         itv.play();
    27     }
    28 }
    29 
    30 //用户自定义电视
    31 class ChangHongTV implements ITV {
    32     @Override
    33     public void play() {
    34         System.out.println("长虹电视机已开启!");
    35     }
    36 }

      运行结果:

      

       分析:首先定义了一个专门用于电视机开关操作的抽象接口IOpenAndClose,然后定义了一个用于描述电视机的抽象接口ITV,接着使用OpenAndClose方法实现IOpenAndClose接口,并且自定义一个电视机类实现ITV接口,在main方法中实例化用户自定义的电视机类并且实例化OpenAndClose类,使用OpenAndClose类中的open方法,将用户自定义的电视机类的对象作为参数传递进去

      答疑:

      Q:看到这里很多读者可能会疑惑,为什么搞这么麻烦,直接写个开关的类来调用电视机类不就好了吗?

      A:这样确实可以,但是代码不够成熟,可拓展性也不好,因为电视不是唯一的,开关也不是唯一的,拟定一个统一的接口就像制定一个标准一样,不管是什么电视什么开关,实现这个接口就必须实现其中的所有抽象方法,实现了这些方法也就遵循了这个标准,有了这两个接口,不管是任何电视任何开关都能适用,降低了各个类之间耦合度的同时也提升了代码的可拓展性

      方式二:

      (2)构造方法传递

     1 /**
     2  * 通过构造方法实现依赖传递
     3  * */
     4 public class ConstructionImplements {
     5     public static void main(String[] args) {
     6         OpenAndClose openAndClose = new OpenAndClose(new AppleTV());
     7         openAndClose.open();
     8     }
     9 }
    10 
    11 //开关的接口
    12 interface IOpenAndClose{
    13     void open();
    14 }
    15 
    16 //TV的接口
    17 interface ITV{
    18     void play();
    19 }
    20 
    21 //实现接口
    22 class OpenAndClose implements IOpenAndClose{
    23     private ITV itv;
    24 
    25     public OpenAndClose(ITV itv) {
    26         this.itv = itv;
    27     }
    28 
    29     @Override
    30     public void open() {
    31         this.itv.play();
    32     }
    33 }
    34 
    35 class AppleTV implements ITV{
    36     @Override
    37     public void play() {
    38         System.out.println("苹果电视已开启!");
    39     }
    40 }

      运行结果:

      

       分析:开关和电视的接口定义与上面相同不再阐述,不过这里的OpenAndClose类换了另一种方法来调用TV接口,将其作为成员变量,使得OpenAndClose类在实例化的时候就传入TV接口,并在open方法中可以直接调用成员变量的play方法,降低了耦合度的同时提升了效率

      方式三:

      (3)setter方式传递

     1 /**
     2  * 通过setter方法实现依赖传递
     3  */
     4 public class SetterImplements {
     5     public static void main(String[] args) {
     6         OpenAndClose openAndClose = new OpenAndClose();
     7         openAndClose.setItv(new XiaomiTV());
     8         openAndClose.open();
     9     }
    10 }
    11 
    12 //开关的接口
    13 interface IOpenAndClose {
    14     void open();
    15 
    16     void setItv(ITV itv);
    17 }
    18 
    19 //TV的接口
    20 interface ITV {
    21     void play();
    22 }
    23 
    24 //实现接口
    25 class OpenAndClose implements IOpenAndClose {
    26     private ITV itv;
    27 
    28     @Override
    29     public void setItv(ITV itv) {
    30         this.itv = itv;
    31     }
    32 
    33     @Override
    34     public void open() {
    35         this.itv.play();
    36     }
    37 }
    38 
    39 class XiaomiTV implements ITV {
    40     @Override
    41     public void play() {
    42         System.out.println("小米电视已开启!");
    43     }
    44 }

      运行结果:

      

       分析:电视的接口定义与上面相同不再阐述,开关接口的定义与上面不同在于,定义了setItv方法,并将TV的接口作为参数,这就意味着实现IOpenAndClose接口必须实现这两个方法,先传入一个TV接口,然后再调用通过该实现该TV接口的类调用open方法

    四、注意事项和细节

      (1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。

      (2)变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。

      (3)继承时遵循里氏替换原则

  • 相关阅读:
    Duang!异地多活!_InfoQ_传送门
    异地多活设计辣么难?其实是你想多了!-博客-云栖社区-阿里云
    途牛谭俊青:多数据中心状态同步&两地三中心的理论
    魅族多机房部署方案
    荔枝FM架构师刘耀华:异地多活IDC机房架构
    zz
    “异地多活”多机房部署经验谈_运维管理_数据中心_中国IDC圈
    天猫双11晚会和狂欢城的互动技术方案
    案例 | 魅族云端同步的架构实践和协议细节
    如何应对大促?看京东核心中间件团队的高可用实践指南
  • 原文地址:https://www.cnblogs.com/yijiahao/p/14319266.html
Copyright © 2011-2022 走看看