[DesignPattern] 设计之禅读书笔记(一)

  • 六大原则

    • 单一职责原则

      • There should be no more than one reason for a class to change
      • 最佳实践: 接口的设计一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
    • 里氏替换原则

      • Functions that use pointers or reference to base classes must be able to use objects of derived classes without knowing it (所有引用基类的地方必须能透明的使用其子类的对象)

      • 包含意义:

        1. 子类必须完全实现父类的方法。

          • 如果子类不能完全实现父类的方法, 或者父类的某些方法在子类中已经发生“畸变”, 则建议断开父子继承关系,采用依赖,聚合,组合等关系代替继承。
        2. 子类可以有自己的个性。

          • 有子类出现的地方未必可以用父类代替
        3. 子类覆盖或实现父类的方法时输入参数可以被放大

          • 子类的输入参数的类的范围要大于父类(子类的前置条件要大于父类的前置条件),如果父类是 public void func(HashMap hashmap), 则子类的覆盖需要是 public void func(Map map)
        4. 子类覆写或实现父类的方法时输出结果可以被缩小

          • 父类的一个方法的返回值时一个类型T, 子类的相同方法(重载或覆写)的返回值时S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。
      • 最佳实践: 采用里氏替换原则时, 尽量避免子类的“畸变个性”,一旦子类有“个性”, 这个子类和父类之间的关系就很难调和了,把子类当作父类使用,子类的“个性”被抹杀————委屈了点:把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离————缺乏类替换的标准。

    • 依赖倒置原则

      • High level modules should not depend up low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. etails should depend upon abstractions.

        1. 高层模块不应该依赖低层模块,两者都应该依赖其抽象
        2. 抽象不应该依赖细节
        3. 细节应该依赖抽象
      • 在Java中,只要定义变量就必然要有类型, 一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型。

        • IDriver zhangSan = new Driver(); zhangSan的表面类型是IDriver, 实际类型是Driver。
      • 依赖的三种写法

        1. 构造函数传递依赖对象

          public interface IDriver{
              public void drive();
          }
          
          public class Driver implements IDriver{
              private ICar car;
              public Driver(Icar car) {
                  this.car = car;
              }
              @Override
              public void drive(){
                  this.car.run();
              }
          }
          
        2. Setter方法传递依赖

        3. 接口声明依赖对象

          public interface IDriver{
              public void setCar(ICar car);
              public void drive();
          }
          
          public class Driver implements IDriver {
              private Icar car;
              
              public void setCar(Icar car) {
                  this.car = car;
              }
          
              public void drive() {
                  this.car.run();
              }
          }
          
      • 最佳实践

        • 依赖倒置原则的本质就是通过抽象类或者接口,使各个类或者模块的实现彼此独立,互不影响,实现模块间的松耦合。其核心就是“面向接口编程”

          1. 每个类尽量都有接口或者抽象类,或者两者皆具备。

          2. 变量的表面类尽量都是接口或者抽象类

            • 但一般xxxUtils不需要接口或者抽象类
            • 如果使用类的clone方法,就必须使用现实类,这个是JDK提供的一个规范
          3. 任何类都不应该从具体类派生

            • 万一有也不能超过两层依赖
          4. 尽量不要覆写(override)基类的方法

            • 如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写,不然破坏依赖的稳定性。
          5. 结合里氏替换原则使用

            • 接口负责定义public属性和方法, 并且声明与其他对象的依赖关系
            • 抽象类负责公共构造部分的实现,
            • 实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
    • 接口隔离原则

      • Clients should not be forced to depend upon interfaces that they don't use. (客户端不应该依赖它不需要的接口)

      • The dependency of one class to another one should depend on the smallest possible interface. (类间的依赖关系应该建立在最小的接口上)

      • 总结: 建立单一接口, 不要建立臃肿庞大的接口。接口尽量要细化,同时接口中的方法尽量少。

      • 保持接口的纯洁性

        1. 接口尽量要小,但根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
        2. 接口要高内聚: 高内聚就是提高接口、类、模块的处理能力,减少对外的交互。在接口中尽量减少公布public方法, 接口时对外的承诺, 承诺越少对系统的开发越有利,变更的风险越少, 同时有利于降低成本。
        3. 定制原则
        4. 接口设计时有限度的
      • 最佳实践:

        • 一个接口只服务一个子模块或业务逻辑;
        • 通过业务逻辑压缩接口中的public方法,接口时常回顾,尽量让接口达到“满身筋骨肉”, 而不是“肥嘟嘟”的一大堆方法;
        • 已经被污染了的接口, 尽量区修改,若变更的风险较大, 则采用适配器模式进行转化处理;
        • 了解环境,拒绝盲从。
    • 迪米特法则(Law of Demeter, LoD)

      • 一个对象应该对其他对象有最少的了解。

        1. 只和朋友交流:

          • Only talk to your immediate friends.
          • 朋友类的定义:出现在成员变量、方法的输入输出参数中的类称为成员的朋友类, 而出现在方法内部的类不属于朋友类。(例如,在方法中new一个类,这个不属于朋友, 应该避免)
          • 一个类只和朋友类交流,不要出现getA().getB().getC().getD()这种情况(在一种极端的情况下允许出现这种访问, 即每一个点号后面的返回类都相同),类于类之间的关系时建立在类间的,而不是方法间, 因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。
        2. 朋友间也是有距离的:

          • 在设计需要反复衡量:是否可以再减少public方法和属性, 是否可以修改private,package-private(包类型, 再类、方法、变量前不加访问权限,则默认为包类型), protected等访问权限, 是否可以加上final关键字等。
          • 迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量, 尽量内敛, 多使用private, package-private, protected等访问权限。
        3. 是自己的就是自己的

          • 如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置再本类中。
        4. 谨慎使用Serializable

      • 最佳实践:

        • 核心:类间解耦,弱耦合
        • 再实际应用中,如果一个类跳转了两次以上才能访问到另一个类,就需要想办法进行重构了, 跳转次数越多,系统越复杂, 维护就越困难。
    • 开闭原则

      • Software entities like classes, modules and functions should be open for extension but closed for modifications. (一个软件实体如类, 模块和函数应该对扩展开放,对修改关闭。)

      • 开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合, 否则就是一个孤立无意义的代码片段。

      • 变化的种类:

        1. 逻辑变化: 变化是一个逻辑,不设计其他模块,eg. ab+c => ab*c 这种变化通过修改原有类中的方法来完成,前提是所有依赖或关联类都按照相同的逻辑处理
        2. 子模块变化: 低层次的模块变化必然引起高层模块的变化,因此在通过扩展变化时, 高层次的模块修改时必然的。
        3. 可见视图变化
      • 重要性:

        1. 开闭原则对测试的影响: 一个方法的测试一般不少于三种, 正常的业务逻辑,边界,异常
        2. 开闭原则可以提高复用性
        3. 开闭原则可以提高可维护性
        4. 面向对象开发的要求
      • 如何使用:

        1. 抽象约束:

          1. 通过接口或者抽象类约束扩展,对扩展进行边界设定, 不允许出现在接口不存在的public方法
          2. 参数类型,引用对象尽量使用接口或者抽象类, 恶如是实现类
          3. 抽象层尽量保持稳定, 一旦确定即不允许修改。
        2. 元数据(metadata)控制模块行为

          • 使用最多就是spring容器
        3. 制定项目章程

        4. 封装变化

          1. 将相同的变化封装到一个接口或者抽象类种
          2. 将不同的变化封装到不同的接口或者抽象类种, 不应该有两个不同的变化出现在同一个接口或抽象类中。
      • 最佳实践

        • 开闭原则也只是一个原则:要灵活使用
        • 项目规章非常重要
        • 预知变化
      • 有趣小知识点:
        我们把价格定义为int类,在非金融类的项目中对货币处理时,一般取2位精度, 通常的设计方法时在运算过程中扩大100倍, 在需要展示时再缩小100倍,减少精度带来的误差。

            NumberFormat formatter = NumberFormat.getCurrencyInstance();
            formatter.setMaximumFractionDigits(2);
            formatter.format(book.getPrice()/100.0);
        

posted on 2020-10-11 07:47  codingEskimo  阅读(90)  评论(0编辑  收藏  举报

导航