软件设计原则

发布时间 2023-12-25 09:33:08作者: 扰扰

1、开闭原则(Open-Close Principle)

指的是一个软件实体(类、软件、模块)应该对扩展开放、对修改关闭。这里的开闭,指的就是对扩展和修改的两个行为的一个原则。强调的是使用抽象建立框架,用实现扩展细节,可以提高程序的可复用性和可维护性。开闭原则的主要思想为在不修改原来的代码的情况下扩展新的功能。
以下为举例说明:
使用一个商店的商品为例,商品有三个属性:id、name、pric。当商品进行促销降价的时候,如何在不修改源代码的情况下完成降价。

以下为商品的接口

interface MyCoures{
    Integer getId();
    String getName();
    Double getPrice();
}

以下Wie原本的商品的实现类

public class MyCouresImpl implements MyCoures {
    private int id;
    private String name;
    private Double price;
    @Override
    public Integer getId() {
        return id;
    }

    public MyCouresImpl(int id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

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

    @Override
    public Double getPrice() {
        return price;
    }
}

现在要进行的是对商品进行打折促销,如果在源代码上进行修改,会存在风险,也是我们要注意开闭原则的原因。
所以在此处重写一个类继承这个类,并重写其中获取价格的方法。代码如下所示

public class MyDiscountCoureesImpl extends MyCouresImpl{
    public MyDiscountCoureesImpl(int id, String name, Double price) {
        super(id, name, price);
    }
    @Override
    public Double getPrice() {

        return super.getPrice() * 0.8;
    }
}

类结构图如下所示

 2、依赖倒置原则(Dependence Inversion Principle)

指的是在设计代码结构的时候,高层模块不应该依赖于底层模块,应该都依赖于抽象。抽象不应该依赖于细节,应该依赖于接口,通过依赖倒置,可以减少类与类之间的耦合度,提高程序的稳定性,提高代码的可读性和可维护性。
以下为使用依赖倒置原则的例子
以下是keys类,其中定义了学习java和JavaScript方法

public class Keys {
    public void studyJavaCourse(){
        System.out.println("keys正在学java");
    }
    public void studyJavaScriptCourse(){
        System.out.println("keys正在学javaScript");
    }
}

调用代码:

public static void main(String[] args) {
        Keys keys = new Keys();
        keys.studyJavaCourse();
        keys.studyJavaScriptCourse();
    }

此时假如需求改变了,keys的学性大发,想要学习更多的新知识,就需要在keys的类上添加新的方法,这对于一个已经上线的项目来说是存在风险的。不符合依赖倒置原则。理想的状态是不修改原来的代码,添加新的类就可以进行扩展。

以下为优化代码
将课程提升为接口,里面有一个study方法。

public interface MyCourse {
    void study();
}

keys也有一个study的方法

public class KeysPlus {
    public void study(MyCourse myCourse){
        myCourse.study();
    }
}

学习的方法实现接口

public class JavaCoures implements MyCourse{
    @Override
    public void study() {
        System.out.println("keys正在学java");
    }
}
public class JavaScriptCoures implements MyCourse{
    @Override
    public void study() {
        System.out.println("keys正在学javaScript");
    }
}

学习的实现

public static void main(String[] args) {
        KeysPlus keys = new KeysPlus();
        keys.study(new JavaCoures());
        keys.study(new JavaScriptCoures());
    }

如果这个时候想要学习新的知识,只需要新添加一个MyCourse的实现类,并实现方法,代码如下

public class GoCoures implements MyCourse{
    @Override
    public void study() {
        System.out.println("keys正在学go");
    }
}

新的测试如下

public class Test {
    public static void main(String[] args) {
        KeysPlus keys = new KeysPlus();
        keys.study(new JavaCoures());
        keys.study(new JavaScriptCoures());
        keys.study(new GoCoures());
    }
}

所以我们在编程的过程中,需要以抽象为基准搭建架构,因为它比以细节为架构的要稳健的多,所以需要我们面向接口编程。

3、单一职责原则(Simple Responsibility Principle)

单一职责指的是不要存在多于 一个导致一个类发生变更的原因(只有一个原因导致类发生变化)。
需要使用单一职责的原因:假设现在的一个类负责 两个职能,一旦需求发生变化,修改其中的一个职责的逻辑代码,有可能导致另一个职能的的功能发生变化。所以需要对着一个类的两个只能进行解耦合。将这个类写成两个类。也是一种解耦合的方式的体现,核心的思想在于如果到了不得不修改类的局面,要尽可能的修改较为少的代码。提高程序的可维护性。
言而总之就是要使得一个类、接口方法只负责一项职能。

4、接口隔离原则(Interface Segregation Principle)

定义 接口隔离原则指的是我们应该使用更为细则化的接口,,而不应该使用单一的臃肿的接口,客户端不应该依赖于他不需要的接口,这个原则有三个基本要求:

一个类对另一个类的依赖应该建立在最小的接口之上
建立单一化接口,不要使用一个臃肿的接口
尽量细化接口,接口中的方法尽量少(不是越少越好)
目的接口隔离原则符合我们常说的高内聚、低耦合的思想,可以使得类有更高的扩展性、可读性和可维护性。
举例说明:下面是描述动物行为的接口
没有使用接口隔离原则:使用单一的接口

public class Brid implements IAnimals {
    @Override
    public void eat() {
        System.out.println("鸟为食亡");
    }

    @Override
    public void fly() {
        System.out.println("沙鸥翔集");
    }

    @Override
    public void swim() {

    }
}

 

public class Fish implements IAnimals {
    @Override
    public void eat() {
        System.out.println("愿者上钩");
    }

    @Override
    public void fly() {

    }

    @Override
    public void swim() {
        System.out.println("锦鳞游泳");
    }
}

可以看到:brid中的swim方法和Fish中的fly方法没有实现具体的功能(而且也不应该出现在实现类中),这就是单一接口所造成的问题

以下是优化过的代码:也就是使用了单一接口原则后的代码

三个方法的接口

public interface IEatAnimals {
    /**
     * 民以食为天
     */
    void eat();

}
public interface IEatAnimals {
    /**
     * 民以食为天
     */
    void eat();

}
public interface ISwimAnimals {
    /**
     * 锦鳞游泳
     */
    void swim();

}

两个实现类

public class Brid implements IEatAnimals, IFlyAnimals {
    @Override
    public void eat() {
        System.out.println("鸟为食亡");
    }

    @Override
    public void fly() {
        System.out.println("沙鸥翔集");
    }
}
public class Fish implements IEatAnimals, ISwimAnimals {
    @Override
    public void eat() {
        System.out.println("愿者上钩");
    }

    @Override
    public void swim() {
        System.out.println("锦鳞游泳");
    }
}

可以看到,这次使用了对应的原则之后,实现类中没有出现多于的方法,在后期维护也是大大减少了代码的修改量。

5、迪米特原则(Law of Demeter)

是指一个对象应该对其他对象保持最少的了解。迪米特原则强调纸盒朋友交流,不和陌生人说话,出现在成员变量、方法的输入、输出参数中的对象都可以成为朋友类,而出现在方法体内部的类不属于朋友类。

6、里氏替换原则(Liskov Substitution Principle)

如果一个软件的实体类适用于一个父类,那么他一定适用于其子类,所有可以引用这个父类的地方都必须能透明的应用这个子类,而且程序的逻辑不变。引申含义为:子类可以扩展父类的功能,但是不能改变父类原有的功能。

子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
子类可以添加自己的特有的功能
当子类重载父类的方法时,方法的参数输出的限制条件应该比父类的更加宽松
当子类实现父类的抽象方法时,输出的参数(返回值)应该比父类的更加严格或者一致
里氏替换原则的优点
约束继承泛滥,是开闭原则的一种体现
加强程序的健壮性,同时变更时做到了非常好的兼容性,提高程序的可维护性和扩展性,降低需求变更所带来的风险

7、合成复用原则

合成复用原则是指尽量使用对象组合,而不是使用继承的关系达到软件复用的目的。可以使得系统更加的灵活,降低类与类的耦合性一个类的变化。