设计模式有7个基本的思想,设计模式是更具体的的落地方法。
2020年的最大事件是从武汉开始爆发新冠疫情,抗疫决战的标志有两个:武汉封城和10天建设火神山雷神山医院。设计模式是源自工程建设,那么7个思想也一定在工程建设中有体现,本文我尝试从设计原则的角度来模拟一下建设过程。为了聚焦问题的重点,我们就假设几个场景。
1.开闭原则
开闭原则的意思是对扩展开放,对修改关闭,实现的基本思想是面向抽象編程。要增加新功能的時候不要直接去修改原來的功能,当然这个是相对的,不是每次都新创建一个,而是根据是否该在当前对象中增加这个方法。
我们假定火神山建设的时候建材价格是市场价,而建设雷神山的时候,老板给打折了,此时该如何表示呢?一种方式是直接修改Source价格,但是如果火神山还是按照原价格供货的,直接修改将导致混乱。第二种方式是在建材Source的基础上新建一个折扣类,雷神山的建材就使用折扣类的。
代码:
定义一个建材的接口
public interface ISource {
Integer getId();
String getProviderName();
Double getPrice();
}
然后定义火神山实现类,因为火神山建设早,所以建材类已经建好,并且仍在使用:
package ch1_principle.open_close;
public class HuoShenshanSource implements ISource {
private Integer id;
private String providerName;
private Double price;
public HuoShenshanSource(Integer id, String providerName, Double price) {
this.id = id;
this.providerName = providerName;
this.price = price;
}
public Integer getId() {
return id;
}
public String getProviderName() {
return providerName;
}
public Double getPrice() {
return price;
}
}
再定义雷神山建材类,因为有折扣,所以直接继承火神山类,在此基础上修改:
package ch1_principle.open_close;
public class LeishenshanSource extends HuoShenshanSource{
public LeishenshanSource(Integer id, String providerName, Double price) {
super(id, providerName, price);
}
public Double getOriginalPrice(){
return super.getPrice();
}
public Double getPrice(){
return super.getPrice()*0.6;
}
}
最后是调用方法,从代码接口看 ,几乎没有区别,但是在LeishenshanSource类中已经将价格计算方法改变了。
package ch1_principle.open_close;
public class MainTest {
public static void main(String[] args) {
ISource source=new HuoShenshanSource(1,"北京建材",100d);
System.out.println("火神山的建材价格为:"+source.getPrice());
ISource source1=new LeishenshanSource(1,"北京建材",100d);
System.out.println("雷神山的建材价格为"+source1.getPrice());
}
}
2.依赖倒置原则
依赖倒置在教材中的解释是“高层模块和底层模块都应该依赖其抽象。抽象不应该依赖细节;细节应该依赖抽象”。个人理解这里其实已经存在层次关系耦合了,或者行为与数据耦合在一起的问题了。这个设计场景有点复杂,就用一个学过的例子来看吧。例如TT类代码,到底有什么问题呢?
package ch1_principle.open_close.DIP;
public class TT {
public void studyJavaCourse() {
System.out.println("Tom 在学习java");
}
public void studyPythonCourse() {
System.out.println("Tom 在学习python");
}
public void studyAICourse() {
System.out.println("Tom 在学习AI技术");
}
}
这个代码其实包含了两个任务,TT类里决定要学什么(Java,Python和AI),以及每个技术怎么学。很明显后面 怎么学的内容应该是该课程的技术栈和应用来决定的,其他人例如mic,james也是要这么学。TT类只需要确定学什么就行了。两个这个类将两者耦合在了一起,和所以带来的问题就是每次修改或者数据(例如增加前端课程)时,这里有要改,而如果yy和zz也要学,就会出现大量的冗余代码,因此导致系统稳定性不好。
所以这里要将学什么和每个课怎么学分开进行。这样如果想修改每个课怎么学,只有修改对应课的实现就行。每个人只定义一个学习的抽象实现,具体学什么,交给业务来决定。
代码:
public interface Icourse {
void study();
}
public class AICourse implements Icourse {
public void study() {
System.out.println("AI 要学习 深度学习");
}
}
public class JavaCourse implements Icourse {
public void study() {
System.out.println("java 要学习Spring");
}
}
public class PythonCourse implements Icourse {
public void study() {
System.out.println("python 要学习Django");
}
}
而学习者只负责学习就行,具体学什么由上层业务来决定。
package ch1_principle.open_close.DIP;
public class TT2 {
public void study(Icourse icourse){
System.out.println("tt 要学习:");
icourse.study();
}
}
再添加一个人yy,代码与上面的完全一样。
package ch1_principle.open_close.DIP;
public class YY {
public void study(Icourse icourse){
System.out.println("yy 要学习:");
icourse.study();
}
}
调用代码:
public class MainTest {
public static void main(String[] args) {
TT2 tt=new TT2();
tt.study(new JavaCourse());
tt.study(new AICourse());
tt.study(new PythonCourse());
YY yy=new YY();
yy.study(new JavaCourse());
yy.study(new AICourse());
}
}
这就像公司里你的价值就是干活,干什么由领导决定,怎么干则由这个活具体是什么来决定。
这里的study还是干两件事,初始化学什么,和学习。这里可以将学什么提前注入给执行人。
也就是通过tt的构造方法将课程传入进来,或者通过set来注入进来。
通过构造方法:
package ch1_principle.open_close.DIP;
public class TT3 {
Icourse icourse;
public TT3(Icourse icourse) {
this.icourse = icourse;
}
public void study() {
icourse.study();
}
}
调用:
TT3 tt3=new Tom3(new JavaCourse());
tt3.study();
通过setter注入,可以将创建tt对象,确定学什么和学习三者分离开:
package ch1_principle.open_close.DIP;
public class TT4 {
Icourse icourse;
public Icourse getIcourse() {
return icourse;
}
public void setIcourse(Icourse icourse) {
this.icourse = icourse;
}
public void study() {
icourse.study();
}
}
TT4 tt4=new TT4();
tt4.setIcourse(new JavaCourse());
tt4.study();
3.单一职责原则
在2020年初武汉发生过一件事,一个轻症官员叫朱宝华, 在医院里要求一个护士给打扫卫生,但是这个护士是负责急救的,打扫卫生是另一个部门干的,就拒绝了。于是这个官员对护士一顿吼,结果曝光之后这个官就被处分了。这官人一定觉得护士都是一样的,没有所谓的职责划分。
单一职责是指:不要存在多于一个导致类变更的原因,也就是一个类里就干密切相关的一件事。 在一个人数比较多的条件下,每个人都必须负责单一而明确的任务,自己的一定要干好,别人的不用随便动,这也是单一职责原则的体现。
4.接口隔离原则
接口隔离原则要求多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口,这其实也是接口定义要满足单一职责,尽量简单。
例如这就不是一个好的接口,因为这要求所有动物都要能吃,能飞,能游,这明显是有问题的。
public interface IAnimal {
void eat();
void fly();
void swim();
}
比较好的方式是上面只有一个eat。然后分别定义fly和swim的接口,而后面两者都集成自IAnimal。
public interface IAnimal {
void eat();
}
public interface IFlyAnimal extends IAnimal {
void fly();
}
public interface ISwimAnimal extends IAnimal {
void swim();
}
5.迪米特法则
指一个对象应该对其他对象保持最少的了解。也就是不要给别人不需要的东西。在疫情严重的时候,我们经常看到新闻说各地都要一级响应,病患人数必须准确及时上报。我们看新闻的时候也只关注当天总人数是多少,而不关心到底是谁,这就是迪米特法则的一种体现。只有与我们可能有关联时才从专门渠道获取病患的完整信息。
我们接下来就模拟一下这个场景。
国家疾控中心每天汇总当天的患者数量,然后通过CCTV发布出去,我们通过新闻获取资讯:
package ch1_principle.open_close.lod;
import java.util.ArrayList;
import java.util.List;
public class CDC {
public Integer checkNumber(){
List<Person> personList = new ArrayList<Person>();
for (int i= 0; i < 20 ;i ++){
personList.add(new Person());
}
return personList.size();
}
}
CCTV只关注数量,不关注如何获得的:
package ch1_principle.open_close.lod;
public class CCTV {
public Integer commandCheckNumber(){
CDC cdc=new CDC();
return cdc.checkNumber();
}
}
我们只要看新闻就行了:
package ch1_principle.open_close.lod;
public class MainTest {
public static void main(String[] args) {
CCTV cctv=new CCTV();
System.out.println( cctv.commandCheckNumber());
}
}
6.里式替换原则
这个原则是说能用父类的地方也一定可以用子类,这个前提就是子类不能覆盖父类的非抽象方法,子类可以有自己的方法。
重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同,子类重载父类方法时,方法的前置条件要更宽松。因为如果不这么做,父类出现的地方就不能用子类了。
子类实现父类的方法时(重写/重载/实现抽象方法),条件要更严格。这样可以保证将父类出现的地方替换为子类时,一定还能满足要求的。
7.合成复用原则
比较简单,少用继承,多用组合的方式,因为组合更为灵活。