• 软件设计原则


    前言

    参考资料:

    软件设计原则的个人理解: 面向单一职责接口编程(开闭、里氏替换、依赖倒转、接口隔离)+ 高度封装实体类(迪米特)+ 多用关联少用继承(合成复用)

    1.开闭原则

    官方解释:对扩展开放,对修改关闭。 在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

    简而言之:使用接口和抽象类, 当需要对模块进行扩展的时候,只需要新增一些实现类就能实现。

    分析: 我现在有个动物叫声的接口:AnimalCalls ,现在我这个模块有猫的叫声和狗的叫声。

    而现在,我想要向这个模块中加入兔子的叫声,我只需要新加一个实体类 Rabbit 去继承 AnimalCalls 并重写一下 call() 就可实现,只是在 AnimalCallscall() 上去进行了扩展,并没有改写 call() 的内容(因为它是一个抽象方法,没有内容)。

    2.里氏代换原则

    官方解释:任何基类可以出现的地方,子类一定可以出现。

    简而言之:子类可以扩展父类的功能,但不能改变父类原有的功能。 就是子类尽量别去重写父类的方法,这样就能保证:父类使用的地方,子类也能使用,因为子类只是在父类的基础上新增了功能,所有子类可以使用父类的所有功能。而为了实现这种效果,就是让子类实现接口

    分析: 经典案例 List list = new ArrayList<>();Map map = new HashMap<>();,打开源码或者查看API文档就能体会到了,这里就不做过多解释了

    3.依赖倒转原则

    官方解释:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

    这里,来对上面提到的几个名词做一下解释:

    • 抽象——接口/抽象类。
    • 细节——实现类。

    简而言之:对抽象进行编程,而不是对实现进行编程, 降低客户与实现模块之间的耦合度。

    分析: 现在我要组装一件商品,他需要两种零件(A和B),而这两种零件分别有两种品牌(GuA、CastB)。那么直接用实现类实现就是这样的:

    而现在,又有一种新的品牌(ByteA)进入市场,它比起GuA有着更低的售价,所以我决定要替换掉GuA,那么我首先得和GuA断绝联系,再使用ByteA,如下:

    这样就显得过程很繁琐。

    那来看一下使用接口的方案:

    这样,就能很轻松地根据市场行情切换我想要使用的零件品牌

    4.接口隔离原则

    官方解释:不应强迫客户端依赖它不使用的方法。

    简而言之:使接口的行为单一, 也就是一个接口只专注于一个功能(一个方法,或者没有方法)。

    分析: 经典案例:

    • java.io.Serializable 这个接口啥方法都没有,它的作用就是证明实现它的实体类可以进行序列化
    • java.lang.Comparable 这个接口就只有一个方法compareTo,只专注于实体类的排序
    • java.lang.Cloneable 这个接口没有方法,它的作用就是声明实现类可以调用Object.clone()对实体类的实例进行克隆

    好了,看完这三个经典案例,想必大家就已经了然于心了

    5.迪米特法则

    官方解释:一个类对于其他类知道的越少越好, 一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

    这里,来对上面提到的几个名词做一下解释:

    • 朋友:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

    简而言之: 一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。其目的是降低类之间的耦合度,提高模块的相对独立性。

    分析: 房主把房子交给中介,让中介与租户联系实现房屋出租就能很好解释这个法则:



    上述案例的具体实现可观看我的博客:代理模式(静态代理、jdk动态代理、CGLib动态代理),好的,这一段就到这里吧

    6.合成复用原则

    官方解释:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

    简而言之: 继承具有很多缺点,建议使用组合或聚合。

    • 继承的缺点:
      • 破坏了类的封装性:因为继承会将父类的实现细节暴露给子类,父类对子类是透明的
      • 父子耦合度高:父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护
      • 限制了复用的灵活性:从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化
    • 组合或聚合的优点:
      • 维持了类的封装性:因为成分对象的内部细节是新对象看不见的
      • 对象间的耦合度低:可以在类的成员位置声明抽象
      • 复用的灵活性高:这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象

    分析:

    • 汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。类图如下:

      从上面类图我们可以看到使用继承复用产生了很多子类,如果现在又有新的动力源或者新的颜色的话,就需要再定义新的类。我们试着将继承复用改为聚合复用看一下:

    上述案例的具体实现代码可以去看这篇文章:设计原则之合成复用原则,这里我就懒得写了

    好了,这篇文章就到此结束吧。

  • 相关阅读:
    深度解读AIGC存储解决方案
    计算机毕业设计SSM《JavaEE开发技术》课程学习网站【附源码数据库】
    GBase8s物理日志
    [附源码]Python计算机毕业设计Django面向高校活动聚App
    归并排序算法
    Spring的事务传遍机制是什么?
    钓鱼攻击防不胜防,该如何预防网络钓鱼攻击?
    Juniper防火墙SSG-140 session 过高问题
    LED显示屏高刷新率和低刷新率有什么区别
    谈基于大语言模型的图数据库路径检索
  • 原文地址:https://blog.csdn.net/m0_54355172/article/details/126158373