设计模式收关:重建火神山-理解7大设计原则

本文以新冠疫情下火神山、雷神山医院建设为背景,深入浅出地介绍了软件工程中的七大设计模式原则,包括开闭原则、依赖倒置原则、单一职责原则等,并通过实例详细解析了各原则的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设计模式有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.合成复用原则

比较简单,少用继承,多用组合的方式,因为组合更为灵活。

8.设计原则总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值