软件设计原则

225 阅读7分钟

1. 开闭原则:

一个软件实体如类、模块和函数应该对外扩展开放,对修改关闭 强调用抽象构建框架,用实现扩展细节

/**
 * 面向接口编程
 * 接口不应该经常变化,应该是稳定且可靠的
 */
public interface ICourse {
    Integer getId();
    String getName();
    Double getPrice();
}
public class JavaCourse implements ICourse{
    private Integer id;
    private String name;
    private Double price;

    public JavaCourse(Integer id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public Integer getId() {
        return id;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Double getPrice() {
        return price;
    }
}
public class Test {
    public static void main(String[] args) {
        ICourse javaCourse = new JavaCourse(2333, "Java从零开始", 666d);
        System.out.println("课程ID:" + javaCourse.getId() + " 课程名称:" + javaCourse.getName() + " 课程价格:" + javaCourse.getPrice());
    }
}

那如果有促销,是要在加一个属性? 这里开闭原则可以根据JavaCourse实现一个打折的类,扩展性和复用性更高

/**
 * 这里能有效避免在接口中添加新的字段,或则在原来的JavaCourse中处理逻辑
 */
public class JavaDiscountCourse  extends JavaCourse{
    public JavaDiscountCourse(Integer id, String name, Double price) {
        super(id, name, price);
    }
    // 获得原价
    public Double getOriginPrice() {
        return super.getPrice();
    }

    @Override
    public Double getPrice() {
        // 处理打折的业务逻辑
        return super.getPrice() * 0.8;
    }
}

变化的都是应用层的子模块

优点:提高软件系统的可复用性及可维护性

2. 依赖倒置原则

定义:高层模块不应该依赖低层模块,二者都应依赖其抽象 补充:抽象不应该依赖细节;细节应该依赖抽象 针对接口编程,不要针对实现编程

首先先看面向实现类编程的一个例子:

public class Archer {
    public void javaCourse() {
        System.out.println("学习Java课程");
    }

    public void algorithmCourse() {
        System.out.println("学习J算法课程");
    }

    // public void pythonCourse(){
    //     System.out.println("学习Python课程");
    // }
}
```java
public class Test {
    public static void main(String[] args) {
        Archer archer = new Archer();
        archer.javaCourse();
        archer.algorithmCourse();
        //archer.pythonCourse();
    }
}

那么如果想要增加一个学习Python的需求,那难道还要在Archer.java(低层模块)中添加方法,扩展性比较差Test(应用层,高层模块)中的修改是依赖于底层实现的

引出接口抽象: ICourse.java

public interface ICourse {
    void studyCourse();
}

低层模块: 具体的实现类交给高层次模块

public class Archer {
    public void studyCourse(ICourse iCourse) {
        iCourse.studyCourse();
    }
}

也可以通过构造器的形式传参

public class Archer {
    private ICourse iCourse;
    public Archer(ICourse iCourse) {
        this.iCourse = iCourse;
    }
    
    public void studyCourse() {
        iCourse.studyCourse();
    }
}

当然为了减少多次注入(防止每次注入都要新创建Archer)

public class Archer {
    private ICourse iCourse;

    public void setICourse(ICourse iCourse) {
        this.iCourse = iCourse;
    }

    public void studyCourse() {
        iCourse.studyCourse();
    }
}

实现类: 如果还想扩展,那么就可以独立编写实现类来实现

public class JavaCourse implements ICourse{
    @Override
    public void studyCourse() {
        System.out.println("学习Java课程");
    }
}
public class AlgorithmCourse implements ICourse{
    @Override
    public void studyCourse() {
        System.out.println("学习算法课程");
    }
}
Test中
```java
public class Test {
    // v1
//    public static void main(String[] args) {
//        Archer archer = new Archer();
//        archer.javaCourse();
//        archer.algorithmCourse();
//        archer.pythonCourse();
//    }
    // v2
//    public static void main(String[] args) {
//        Archer archer = new Archer();
//        archer.studyCourse(new JavaCourse());
//        archer.studyCourse(new AlgorithmCourse());
//    }

    // v3
//    public static void main(String[] args) {
//        Archer archer = new Archer(new JavaCourse());
//        archer.studyCourse();
//    }

    // v4
    public static void main(String[] args) {
        Archer archer = new Archer();
        archer.setICourse(new JavaCourse());
        archer.studyCourse();
        archer.setICourse(new AlgorithmCourse());
        archer.studyCourse();
    }
}

以抽象为基础搭建起来的架构要比以细节为基础的架构更加稳定

优点:可以减少类之间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险

3.单一职责原则

不要存在多于一个导致类变更的原因 体现在一个类/接口/方法只负责一项职责 优点:降低类的复杂度、提高类的可读性,提高系统的可维护性,降低变更引起的风险

例如类:

public class Bird {
    public void mainWay(String name) {
        if (name.equals("鸵鸟"))
            System.out.println(name + "用脚走");
        else
            System.out.println("用翅膀飞");
    }
}

如果需要增加特殊的鸟类,那么需要写更多的if else,对原来的bird类也有影响 所以可以将类分离,作用更加单一

public class WalkBird {
    public void mainWay(String name) {
        System.out.println(name + "用脚走");
    }
}
public class FlyBird {
    public void mainWay(String name) {
        System.out.println("用翅膀飞");
    }
}

对于接口:

/***
 * ICourse有两个职责
 *
 * 获得课程信息
 * 管理课程
 * 管理课程可能会影响课程这个接口的课程信息的内容
 */
public interface ICourse {
    String getCourseName();
    byte[] getCourseVideo();

    void studyCourse();
    void refundCourse();
}

对其进行拆分

public interface ICourseContent {
    String getCourseName();
    byte[] getCourseVideo();
}
public interface ICourseManager {
    void studyCourse();
    void refundCourse();
}

对于方法,尽可能是方法的作用更加明确和单一,便于扩展供其他方法调用

实际开发中,为了防止类的数量过多,一般接口和方法执行单一变量原则,类看实际情况

4.接口隔离原则

定义:用多个专门的接口,而不是用单一的总接口,客户端不应该依赖它不需要的端口 一个类对一个类的依赖应该建立在最小的接口上 建立单一接口,不要建立庞大臃肿的接口

最应该注意的原则: 注意适度原则,一定要适度

优点:符合高内聚低耦合的设计思想,从而使类具有更好可读性,可扩展性和可维护性

public interface AnimalAction {
    void eat();
    void fly();
    void swim();
}

这样设计,例如Dog这个实现类,它如果实现这个接口,那么就会产生fly()这个空实现 然后接口隔离原则分别实现eat()方法的接口IEat,fly()方法的接口IFly,swim()方法的接口ISwim,那么最终Dog这个类只用实现IEat和ISwim

单一职责原则强调的是职责,接口隔离原则强调的是接口

5.迪米特原则(最少知道原则)

一个对象应该对其他对象保持最少的了解

强调:尽量降低类与类之间的耦合 只和朋友交流,不和陌生人说话 朋友:出现在成员变量、方法的输入输出参数中的类称为成员朋友类 而出现在方法体内部的类不属于朋友类

优点:降低类与类之间的耦合(弱耦合)

6. 里式替换原则

如果对每一个类型为T1的对象o1,都有类型为T2的对象02,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。 定义扩展: 一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。(反对子类重写父类)

子类可以扩展父类的功能,但不能改变父类原有的功能。

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。(入参宽松)
  • 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。(出参严谨)

约定子类最好不要重写父类的方法,如果一定要重写的话,可以使用组合聚合等方法实现(没找到合适的例子) 人可以生娃,非洲人欧洲人都可以生娃。反例:变形金刚机器人不可以生娃(入参宽松,出参严谨)

入参宽松

public class Base {
    public void method(HashMap hashMap) {
        System.out.println("执行父类的HashMap方法");
    }

    /**
     * 反例
     */
    public void method(Map map) {
        System.out.println("执行父类的map方法");
    }
}
public class Child extends Base {
    /*@Override
    public void method(HashMap hashMap) {
        System.out.println("执行子类的HashMap方法");
    }*/

    /**
     * 使用重载
     * 重载是入参Map会比HashMap更加宽松,此时执行的时候会执行父类,不执行重载的类
     * 入参更加宽松,可以不引起逻辑的混乱
     */
    public void method(Map map) {
        System.out.println("执行之类的Map方法");
    }

    /**
     * 反例
     */
    public void method(HashMap hashMap) {
        System.out.println("执行子类的HashMap方法");
    }

}
public class Test {
    public static void main(String[] args) {
        Child child = new Child();
        HashMap hashMap = new HashMap();
        child.method(hashMap);
    }
}

出参严谨

public abstract class Base {
    public abstract Map method();
}
/**
 * 出参Object包含Map直接报错
 */
public class Child extends Base{
    /*@Override
    public Object method() {
        return null;
    }*/

    @Override
    public Map method() {
        return null;
    }
}

优点: 约束了继承泛滥,很多非子类父类关系的类,没必要使用继承关系; 加强程序的可维护性,降低需求变更时引起的风险