设计模式中的七个基本设计原则

1. 单一原则(SRP)

一个类只负责一个功能领域的相应职责,将一个类拆分成多个小类,每个小类都只关注一个职责,提高代码的复用性和可维护性

2.  开闭原则(OCP)

对扩展开放,对修改关闭:一个软件实体应以对扩展开放的方式设计,以支持系统的 扩展而不是重构,这个原则应用在模块设计、接口设计、抽象类设计、类的继承等方面。

3. 里氏替换原则(LSP)

所有引用基类对象的地方都能够透明地使用其子类对象。既子类可以扩展父类的方法,但不能改变父类原有的方法特性,保证父类与子类之间的基本锲约关系不受破坏,用这个原则定义出的子类,才能保证在集成场景下对系统的正常运行不产生任何影响。

里氏替换原则内在含义:

  1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法【核心概念】
  2. 子类中可以增加自己特有的方法。
  3. 前置条件放大,子类在重载父类的已实现方法时,方法的前置条件(形参)范围应该比父类更加宽松.
  4. 后置条件缩小,子类在实现父类的抽象方法时,方法的后置条件(返回值)范围应该比父类更加严格或相同。

如下图,父类parent的function方法形参是collection,son子类的继承parent类,并且重载了父类的function方法,但是son子类function的形参范围比父类的要小,因为List继承Collection接口。

当我们执行下面的测试用例时,代码及输出结果如下:

但是当我们用son子类使用同一对象调用同一方法却产生了不同的结果。此时子类便有可能不能完全透明的替换掉父类,即子类替换掉父类后可能会影响到原程序的运行状况。

4. 依赖倒置原则(DIP)

高层模块不应该依赖底层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象,即依赖于抽象,而不依赖于具体实现。抽象对代码来说即接口或者抽象类,细节对代码来说即实现类

对比代码如下:

工作人员接收微信老板发来的加班消息

方案一:

/**
 * 定义工作人员Worker.java
 */
public class Worker {
    public void getMessage(WeChat weChat){
        weChat.sendMessage();

    }
}
/**
 * 定义微信消息类WeChat.java
 */
public class WeChat {
    public void sendMessage() {
        System.out.println("微信上,老板让你加班了");
    }
}
  /**
     * 方案一测试代码,违反依赖倒置原则
     */
    @Test
    public void test03(){
        new Worker().getMessage(new WeChat());
    }

方案一中,违反了依赖倒置原则,如果功能需求扩展,比如说需要扩展一种飞书发送消息,我们需要新增一个飞书类,并实现发送消息的功能,工作人员的类也需要修改接收消息的方法,客户端也需要进行相应的修改,改动大,风险大

方案二:


/**
 * 定义消息接口IMessage.java
 */
public interface IMessage {
    void sendMessage();
}
/**
 * 定义微信消息类WeChatNew.java
 */
public class WeChatNew implements IMessage{

    @Override
    public void sendMessage() {
        System.out.println("微信上,老板让你加班了");
    }
}
/**
 * 定义飞书类
 */
public class Feishu implements IMessage{
    @Override
    public void sendMessage() {
        System.out.println("飞书上,老板让你加班了");
    }
}
/**
 * 定义工作人员Worker.java
 */
public class WorkerNew {
    public void getMessage(IMessage iMessage){
        iMessage.sendMessage();
    }
}
    @Test
    public void test04(){
        new WorkerNew().getMessage(new WeChatNew());
        new WorkerNew().getMessage(new Feishu());
    }

方案二遵守了依赖倒置原则,同样的需求扩展,抽象一个公共的消息接口,所有的微信,飞书等发送消息的类只要实现该接口,重写发送消息的方法,工作人员的接收消息方法以消息接口为入参,客户端只需要传入相应的消息实体类,扩展方便,耦合低。

5. 接口隔离原则(ISP)

不应该强迫一个类实现它不需要的接口,或者使用一个类需要大量的调用其他接口的方法。将接口进行拆分,提供多个功能小而专一的接口。

结合案例:

假设有这样一个案例场景,现在有一个接口Repair,给定他有三个能力,可以修汽车,修火车,修飞机, 两个实现类张师傅,李师傅分别具有这些能力,有一个4S店类,假设是需要张师傅来修汽车和修飞机,而另一个4s店类需要李师傅来修汽车和修火车

方案一:

/**
 * 定义维修接口
 */
public interface Repair {
    /**
     * 维修汽车
     */
    void car();

    /**
     * 维修火车
     */
    void train();

    /**
     * 维修飞机
     */
    void air();
}
/**
 * 维修王师傅
 */
public class RepairLi implements Repair{
    @Override
    public void car() {
        System.out.println("李师傅修汽车");
    }

    @Override
    public void train() {
        System.out.println("李师傅修火车");
    }

    @Override
    public void air() {
        System.out.println("李师傅修飞机");
    }
}
/**
 * 维修王师傅
 */
public class RepairWang implements Repair{
    @Override
    public void car() {
        System.out.println("王师傅修汽车");
    }

    @Override
    public void train() {
        System.out.println("王师傅修火车");
    }

    @Override
    public void air() {
        System.out.println("王师傅修飞机");
    }
}

/**
 * 4s店1
 */
public class Shop1 {
    /**
     * 修汽车
     * @param repair
     */
    public void repairCar(Repair repair){
        repair.car();;
    }

    /**
     * 修火车
     * @param repair
     */
    public void repairTrain(Repair repair){
        repair.train();
    }
}
/**
 * 4s店2
 */
public class Shop2 {
    /**
     * 修汽车
     * @param repair
     */
    public void repairCar(Repair repair){
        repair.car();;
    }

    /**
     * 修飞机
     * @param repair
     */
    public void repairAir(Repair repair){
        repair.air();
    }
}

方案一中违反了接口隔离原则,因为李师傅和王师傅都实现了维修接口的三个方法:修汽车,修火车,修飞机。但需求中并不需要这么多,只需要李师傅修汽车和火车,王师傅修汽车和飞机,依赖关系不是建立在最小接口上。

方案二:

/**
 * 修飞机
 */
public interface RepairAir {
    void repairAir();
}
/**
 * 修汽车
 */
public interface RepairCar {
    void repairCar();
}
/**
 * 修火车
 */
public interface RepairTrain {
    void repairTrain();
}
/**
 * 维修李师傅
 */
public class RepairLiNew implements RepairCar,RepairTrain{
    @Override
    public void repairCar() {
        System.out.println("李师傅修汽车");
    }

    @Override
    public void repairTrain() {
        System.out.println("李师傅修火车");
    }
}
/**
 * 维修王师傅
 */
public class RepairWangNew implements RepairCar,RepairAir {
    @Override
    public void repairAir() {
        System.out.println("王师傅修飞机");
    }

    @Override
    public void repairCar() {
        System.out.println("王师傅修汽车");
    }
}
/**
 * 4s店1修汽车和火车
 */
public class Shop1New {
    public void car(RepairCar repairCar){
        repairCar.repairCar();
    }

    public void Train(RepairTrain repairTrain){
        repairTrain.repairTrain();
    }
}
/**
 * 4s店2修汽车和飞机
 */
public class Shop2New {
    public void car(RepairCar repairCar){
        repairCar.repairCar();
    }

    public void air(RepairAir repairAir){
        repairAir.repairAir();
    }
}

方案二遵守了接口隔离原则,对李师傅和王师傅类都进行了接口隔离,实现了各自的两个方法,避免了耦合

6. 迪米特法则(LOD)

一个对象应该对其他对象保持最少的了解,即一个类应该对自己需要耦合或调用的类知道得最少。通俗的讲,就是一个类不应该知道太对其他类得细节,只需要了解自己的职责即可。

迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部

举个案例:

有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。先来看一下违反迪米特法则的设计。

/**
 * 总公司员工
 */
public class Employee {
    private String id;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}
/**
 * 分公司员工
 */
public class SubEmployee {
    private String id;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}
public class SubCompanyManager {
    public List<SubEmployee> getAllEmployee() {
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0;i<100;i++){
            SubEmployee subEmployee = new SubEmployee();
            subEmployee.setId("分公司"+i);
            list.add(subEmployee);
        }
        return list;
    }
}
public class CompanyManager {
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0;i<100;i++){
            Employee employee = new Employee();
            employee.setId("总公司"+i);
            list.add(employee);
        }
        return list;
    }

    public void printAllEmployee(SubCompanyManager subCompanyManager){
        List<SubEmployee> allSubEmployee = subCompanyManager.getAllEmployee();
        for(SubEmployee subEmployee: allSubEmployee){
            System.out.println(subEmployee.getId());
        }

        List<Employee> allEmployee = this.getAllEmployee();
        for(Employee employee: allEmployee){
            System.out.println(employee.getId());
        }
    }
}

方案一中,主要问题出在CompanyManager中,根据迪米特法则,只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。修改后的代码如下:

public class SubCompanyManagerNew {
    public List<SubEmployee> getAllEmployee() {
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0;i<100;i++){
            SubEmployee subEmployee = new SubEmployee();
            subEmployee.setId("分公司"+i);
            list.add(subEmployee);
        }
        return list;
    }

    public void printAllEmployee(){
        List<SubEmployee> allSubEmployee = this.getAllEmployee();
        for(SubEmployee subEmployee: allSubEmployee){
            System.out.println(subEmployee.getId());
        }
    }
}
public class CompanyManager {
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0;i<100;i++){
            Employee employee = new Employee();
            employee.setId("总公司"+i);
            list.add(employee);
        }
        return list;
    }

    public void printAllEmployee(SubCompanyManagerNew subCompanyManagerNew){
        subCompanyManagerNew.printAllEmployee();
        List<Employee> allEmployee = this.getAllEmployee();
        for(Employee employee: allEmployee){
            System.out.println(employee.getId());
        }
    }
}
   @Test
    public void test08(){
        CompanyManager companyManager = new CompanyManager();
        companyManager.printAllEmployee(new SubCompanyManagerNew());
    }

修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

7. 组合/聚合复用原则(CARP)

要尽量使用组合/聚合。而不是使用继承来达到复用得目的。这样可以在运行时动态的改变对象的行为,并且改变的行为不会影响到其他拥有相同基类的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值