设计模式-1.设计原则

目录

单一职责原则

里氏替换原则

依赖倒置原则

接口隔离原则

迪米特法则

开闭原则

合成/复用原则

总结


// 设计模式的本质是封装变化

单一职责原则

  • 定义

应该有且仅有一个原因引起类的变更。即一个类只做一件事情

  • 思考

其实单一职责原则最难划分的就是职责,所以有很多时候职责的取舍是很难说的事情。从逻辑上来说,单一职责原则都适用于接口、类、方法三个层级。我们要做的就是,接口严格按单一职责划分,类可适当根据实际情况灵活应用,方法则尽量拆开,不要混在一起。

 

里氏替换原则

  • 定义

任何父类出现的地方,都应该可以被替换为它的子类。即儿子随时可以替换爹

  • 思考

其实我们日常中的面向接口编程,即在类中调用其他类时都使用父类或接口,就已经是满足里氏替换原则了

为了满足里氏替换原则,我们需要注意以下四点:

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

        不然没法用儿子去替代爹。

2.子类可以有自己的个性

        比如说,子类可以扩充很多父类没有的方法。这个也就是我们常说的,向下转型不安全。不过这样做是符合里氏替换原则的。

3. 子类重载方法时,入参范围要大于等于父类。(前置条件)

        即调用时优先调用父类方法。这样才能保证,用儿子替代爹时,调用子类父类的同名方法,入参同时满足子父类的方法时,执行的是父类的方法。

4.子类重写方法时,返回值范围要小于等于父类。(后置条件)

        即返回时要兼容父类方法。这样才能保证,原本调用父类方法的地方,换成子类时,返回值也一定是符合条件的。当然,当违反这一条时,编译器会自动报错的。

第3、4条示例如下:

public class Father {
     public Map<String, Object> cleanHome(ArrayList<String> list) {
          return null;
     }
     public Map<String, Object> washCloth(List<String> list) {
          return null;
     }
}

public class Son extends Father {
     // 重载,入参范围要大于等于父类,从而保证父类方法的优先调用
     public Map<String, Object> cleanHome(List<String> list) {
          return null;
     }
     // 重写,返回值范围要小于等于父类,避免子类替换父类时返回值向下转型
     @Override
     public HashMap<String, Object> washCloth(List<String> list) {
          return null;
     }
}

总之,为了避免麻烦,采用里氏替换原则时,要尽量避免子类的“个性”。

 

依赖倒置原则

  • 定义

面向接口编程。即,能用接口表示具体类的地方,都用接口表示。这个原则其实包含3层意思:1.模块间的依赖通过抽象发生。2.接口或抽象类不依赖于实现类。3.实现类依赖接口或抽象类。

  • 依赖的意思

就是持有对方啦。上面三条就变成:1.模块间持有对方的接口。2.接口不持有实现类。3实现类持有接口。不就是面向接口编程么。

  • 倒置的意思

正常的思维,依赖是面向实现的,实实在在的、具体的。我开车要开奔驰就开奔驰,要开宝马就开宝马。即“正置”。而面向接口时,依赖是面向抽象的,代替了人类的传统思维,也就是“倒置”

  • 依赖倒置的优点

1.减少类间的耦合。这样扩展性就更好,这个也是后面开闭原则的前提。

2.有利于并行开发。多人开发时,只要依赖的地方有一个接口,自己的程序就可以运行。如果依赖的地方不是接口,则必须要等到那个人实现那个类以后,自己的程序才能跑起来。依赖于接口,就可以使用JMock工具等进行单元测试,测试驱动开发,先写好单元测试类,再写实现类,这个也是TDD的精髓。

  • 依赖的三种写法

1,通过构造函数注入

public interface IDriver {
     // 开车
     public void drive();
}

public class Driver implements IDriver {
     private ICar car;    

     // 构造函数注入
     public Driver(ICar carIn) {
          this.car = carIn;
     }     

     public void drive() {
          this.car.run();
     }    
}

2.通过Setter注入

public interface IDriver {
     // 开车
     public void drive();
}

public class Driver implements IDriver {
     private ICar car;     

     // Setter注入
     public void setCar(ICar carIn) {
          this.car = carIn;
     }    

     public void drive() {
          this.car.run();
     }    
}

3.通过接口注入

public interface IDriver {
     // 开车
     public void drive(ICar carIn);
}

public class Driver implements IDriver {
    // 接口注入
    public void drive(ICar carIn) {
         carIn.run();
    }   
}

当然,实际项目中,也要灵活运用,不是死死抓住一个原则不放。

 

接口隔离原则

  • 定义

客户端不应该依赖它不需要的接口。其实这只是结果,本质则是,接口尽量细化。即接口中的方法尽量少,尽量使用多个专门的接口。

接口隔离原则其实包含了4层含义:

1.接口要尽量小。这个是核心定义。在不违反单一职责原则的情况下,尽量小。

2.接口要高内聚。高内聚就是,依赖于外部的信息很少。比如说,你对一个高手说,去川普办公室偷个文件,其他你啥也不用做,一个月后,文件就偷到了。具体到实际操作,就是,在接口中,尽量少的公布public方法。

3.只提供访问者需要的方法。如果有访问者不需要的其他方法,则这个接口很可能就需要再拆了。

4.接口的设计是有限度的。满足接口隔离原则最理想的情况,当然是一个方法一个接口。但是这是不可能的,在实际操作中,就需要我们根据经验来控制粒度的大小。

  • 注意

接口其实分两种。1.实例的接口,也就是类。由于类是实例要遵循的标准,所以类也是一种接口。2.类的接口,就是我们常说的interface。

  • 单一职责原则和接口隔离原则的区别

单一职责原则注重的是职责,是业务逻辑层面的划分,同一个接口里面有很多个方法也无所谓。而接口隔离原则则是要求接口的方法尽量少。

  • 总结

总之,就是在不违背单一职责原则的情况下,让接口尽量小。一个接口只服务一个子模块或业务逻辑。粒度大小需要根据实际情况控制。

 

迪米特法则

  • 定义

也叫最少知识原则。核心观念就是类间解耦,弱耦合。一个对象应该对其他对象有最少的了解。即,一个类需要对自己需要耦合或调用的类知道得最少

  • 理解

最少知识原则包含了以下几层含义:

1.对直接调用类的最小知识:直接调用类时,被调用的类要高内聚,不需要让我知道的方法,都不要让我知道。例如:去酒吧点一杯鸡尾酒,直接调用小哥的购酒方法,就得到了一杯鸡尾酒。而不是调用小哥的取杯子方法取杯子、调用小哥的倒酒方法倒酒、调用小哥的调酒方法调酒......这样我还要小哥干啥?小哥只需要提供给我一个获得鸡尾酒的public方法,其他的private方法要设置为private,因为我根本不关心。

2.对间接调用类的最小知识通过中介类调用其他类时,只与中介类交互,不关心其具体的实现。例如:老师叫班长去给同学发书,他只是把班长叫过来,告诉他去发书就行了。并不需要再给班长发放的人员列表,否则就违背了最少知识原则。即,我只叫你去做,并不关心你是怎么实现的

3.是自己的就是自己的。当一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响时,那就放置在本类中。

其他:解耦后会造成大量的中转或跳转类,在实际调用中,如果一个类跳转两次以上才能访问到另一个类,就要想办法重构了。所以最小知识原则也是需要灵活运用的。

 

开闭原则

  • 定义

一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。

  • 理解

我们要尽量通过增量修改的方式来实现变化,而不是通过修改已有的代码来实现。

开闭原则是最基本的一个原则,也是前面五个原则的精神领袖。前面五个原则只是实现开闭原则的工具和方法。也不局限与这五个原则。

  • 为什么要采用开闭原则

1.对测试友好:不用再去重新改单元测试的代码了,新增测试代码即可。

2.提高了代码复用性:缩小逻辑粒度,好维护,复用性强。

3.提高了代码可维护性:毕竟研究、修改老代码是一件很痛苦的事情。

4.面向对象开发的要求。

  • 如何使用开闭原则

1.抽象约束。a.通过接口或抽象类约束扩展。b.参数类型、引用对象尽量使用接口或抽象类。c.抽象层尽量保持稳定。

2.元数据控制模块行为。元数据就是描述数据的数据,其实就是配置参数。例如,Spring通过修改配置文件引入子类。

3.指定项目章程。建立团队的章程,约定由于配置。

4.封装变化。要预知变化,a.将相同的变化封装到同一个接口或抽象类。b.将不同的变化封装到不同的接口或抽象类中。

 

合成/复用原则

通常,Has A 比 Is A 要好。

 

总结

开闭原则是总原则,六大原则一句话总结如下:

开闭原则:增量修改

 

单一职责原则:一个类只做一件事 

里氏替换原则:子类可以替换父类 

 

依赖倒置原则:面向接口编程

接口隔离原则:满足单一职责情况下,接口方法尽量少。

 

迪米特法则:最少知识,不关心调用类或中间类的内部方法。高内聚。

合成/复用原则:通常,Has A 比 Is A 要好。

 

六大原则就讲完了。最后,设计模式的本质是封装变化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值