专栏首页孟君的编程札记设计模式几大原则

设计模式几大原则

本文来简单介绍一下设计模式采用的几大原则。

一. 单一职责原则

含义

单一职责原则(Single Responsibility Principle,SRP):一个类只负责一个功能领域中的相应职责或可以定义为:就一个类而言,应该只有一个引起它变化的原因。

举例

做一个简单的程序用做图表展示客户列表数据,其中包括图表的创建和展示、数据库连接和获取客户列表的方法,如下图所示:

从上图可以看出,CustomerDataChart其职责不够单一,其包括数据库连接、数据库操作、图表创建和展示,这些功能应该拆分到不同的类中,比如:

这样修改以后,数据库连接就由DBUtils来完成;CustomDao定义获取客户列表方法,其具体实现由DBUtil来完成;CustomerDataChart则包括图表的创建和展示,展示的内容则由CustomerDao来完成;经过上述调整,每个类都有单一的清晰的职责。

二. 开闭原则

意图

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代 码,而是要扩展原有代码,实现一个热插拔的效果。

先来看一个示例:

还是图表展示,其支持饼图和柱状图的展示:

如果我们需要增加一个其他图形的展示,我们需要增加else if的条件来进行扩展,这样原来的代码就别修改。

那么为了满足开闭原则,需要怎么做呢?那就要对系统采用抽象化设计抽象化是开闭原则的关键。

经过抽象化之后,ChartDisplay的变量就成为AbstarctChart,如果有新的Chart需要扩展,如增加一个AbcChart,那么AbcChart只要继承AbstractChart即可,如果ChartDisplay需要使用AbcChart进行图表展示,则其只要setChart(AbcChart)即可,这就满足了扩展,且不会对原有代码进行修改。

三. 里氏替换原则

意图

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。里氏代换原 则中说,任何基类可以出现的地方,子类一定可以出现。

比如,我们有一个功能用于给普通用户和VIP用户发送邮件的功能,如下图所示:

我们可以看到,其实CommonCustomer和VipCustomer发送邮件很类似。我们可以采用里氏替换原则,可以采用如下具体步骤:

  • 建立抽象;通过抽象来建立规范;
  • 具体实现在运行时取代抽象;保证了系统的扩展性和灵活性;

修改后如下图所示,抽象一个Customer,EmailSender中send方法的参数为Customer即可。

这样,

Customer commomCus = new CommonCustomer();
EmailSender.send(commomCus );就可以发送普通用户邮件;

同样,

Customer vipCus = new VipCustomer();
EmailSender.send(vipCus );就可以发送VIP用户邮件;

四. 依赖倒转原则

意图

面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

比如,有一个小功能,读取文件中的用户名单,然后添加到数据库中。可以采用如下方式实现:

上述示例中,CustomDao与具体的文件数据转换类进行交互式,其不符合依赖倒转原则。依据依赖倒转原则,需要与具体类的上层接口或者抽象类交互,那么我们可以采用如下方式进行。

定义一个抽象类DataConvertor,其具有两个子类TXTDataConvertor和ExcelDataConvertor完成具体的数据转换。

至于CustomDao采用哪一种类型进行数据转换,其可以采用配置的方式进行。

这样,程序将不在于具体的实现类进行直接交互。关于依赖倒转原则,相信使用Spring来进行开发的同学深有体会。

五. 接口隔离原则

意图

每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

举个例子,自己实现ArrayList和LinkedList,我们可以定义一个List接口,

public interface List<T> {
  public T get();
  public void add(T t);
  public T poll();
  public T peek();  
}

我们来实现LinkedList:

public class LinkedList implements List<Integer>{

  @Override
  public Integer get() {
    // Implement this method
    return null;
  }

  @Override
  public void add(Integer t) {
    // Implement this method
  }

  @Override
  public Integer poll() {
    // Implement this method
    return null;
  }

  @Override
  public Integer peek() {
    // Implement this method
    return null;
  }
}

然后再来实现ArrayList:

public class ArrayList implements List<Integer>{

  @Override
  public Integer get() {
    // Implement this method
    return null;
  }

  @Override
  public void add(Integer t) {
    // Implement this method
  }

  @Override
  public Integer poll() {
    // ArrayList does not require this method
    return null;
  }

  @Override
  public Integer peek() {
    // ArrayList does not require this method
    return null;
  }
}
 

想想这样做,有什么问题吗?

我们可以看到poll和peek方法,在ArrayList中用不到,但是我们却一定要实现它们,这就是因为List接口太大导致的。

我们可以创建一个新的接口:

public interface Deque<T> {
  public T poll();
  public T peek();  
}

然后移除之前List中的poll和peek方法:

public interface List<T> {
  public T get();
  public void add(T t);  
}
 

这样,

ListLinked的实现就变成:

public class LinkedList implements List<Integer>,Deque<Integer>{

  @Override
  public Integer get() {
    // Implement this method
    return null;
  }

  @Override
  public void add(Integer t) {
    // Implement this method
  }

  @Override
  public Integer poll() {
    // Implement this method
    return null;
  }

  @Override
  public Integer peek() {
    // Implement this method
    return null;
  }
}
 

ArrayList的实现为:

public class ArrayList implements List<Integer>{

  @Override
  public Integer get() {
    // Implement this method
    return null;
  }

  @Override
  public void add(Integer t) {
    // Implement this method
  }
}
 

这思想不就是和JDK源代码中ArrayList和LinkList的思想一致吗~~

六. 迪米特法则

意图

一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过 public 方法提供给外部。

一个软件实体应该尽可能少地和其它实体产生作用。

比如,我们很多人一起在线聊天,我说话不需要和每个人都说一下进行交互,只需要一个聊天室,就能将大家联系在一起。迪米特法则思想的体现,可以在中介者模式中体现,具体可以参考:中介者模式浅析

七. 合成复用原则

意图

尽量使用合成复用,而不是采用继承来达到复用的目的。

比如,CustomerDao可以采用继承DBUtil完成数据库操作。

采用复用的方式,CustomerDao只要委托DBUtil来完成数据库操作,DBUtil其可以采用Oracle、MySQL或者其他数据库的链接实现,这样更加灵活。

再如:

当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口,
而具体的子类则有不同的方式加以实现。但是此方法有时候不够灵活。
继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分
独立地进行修改、扩充和重用。

这个我在《桥接模式浅析》中已经做了详细的说明,有兴趣的同学可以去扩展看看。

八. 小结

面向对象的一般原则,和其关系如下如图所示:

本文分享自微信公众号 - 孟君的编程札记(gh_0f0f5e0ae1de),作者:孟君

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 门面模式浅析

    在生活中,比如在医院有接待员帮助病人完成门诊、挂号、付费以及取药,病人只接触接待员即可,由接待员负责与医院的各个部门打交道。

    孟君
  • 状态模式解析

    状态在我们的生活中无处不在。比如,听音乐的时候,我们可以选择不同的播放状态,可以是顺序播放、可以是单曲播放、也可以随机播放等;又如:线上购物后,订单从提交到完成...

    孟君
  • 还在new对象吗?Builder构建对象了解一下?

    在平时开发中,我们经常需要去new一个对象。如果一个类的属性很多,就要设置较多的setXXX,这样实例化和赋值分开,较为分散。

    孟君
  • JAVA8新特性(二)——通用函数接口

    java.util.function包中是通用性函数接口,它满足一下几种基本的使用。

    逝兮诚
  • 桥接模式

    概述 当一个抽象对象可能有多个 实现时,通常用继承来协调他们。抽象类定义对该抽象的接口,而具体的子类则用不同的方式来实现。但是此方法有时候不太灵活。继承机制将抽...

    xiangzhihong
  • Top 15 不起眼却有大作用的 .NET功能集

    目录 1. ObsoleteAttribute 2. 设置默认值属性: DefaultValueAttribute 3. DebuggerBrowsableAt...

    葡萄城控件
  • 请说明Java的接口和C++的虚类的相同和不同处。

    由于Java不支持多继承,而有可能某个类或对象要使用分别在几个类或对象里面的方法或属性,现有的单继承机制就不能满足要求。

    剑走天涯
  • .NET程序优化(GCServer )

    现在的服务器都是多个cpu,在.NET Framework 2.0在GC上有个新特性GCServer ,不知道有多少人用过这个东东。 关于GC可以看这篇文章GC...

    张善友
  • .NET Core微服务之基于MassTransit实现数据最终一致性(Part 1)

      关于数据一致性的文章,园子里已经有很多了,如果你还不了解,那么可以通过以下的几篇文章去快速地了解了解,有个感性认识即可。

    Edison Zhou
  • Spring之Aware接口介绍

      在Bean对象的生命周期的方法中有好几个接口是Aware接口的子接口,所以弄清楚Aware接口对于理解Spring框架还是很有帮助的。

    用户4919348

扫码关注云+社区

领取腾讯云代金券