七大软件架构设计原则-读书笔记

文章目录

  • 开闭原则(Open-Closed Principle,OCP)
      • demo1
  • 依赖倒置原则(Dependence Inversion Principle,DIP)
      • demo1
  • 单一职责原则(Simple Responsibility Principle,SRP)
  • 接口隔离原则(Interface Segregation Principle,ISP)
      • demo1
  • 迪米特法则(Law of Demeter,LoD)又叫作最少知道原则(Least Knowledge Principle,LKP)
      • demo1
  • 里氏替换原则(Liskov Substitution Principle,LSP)
      • demo1
  • 合成复用原则(Composite/Aggregate Reuse Principle,CARP)
  • 总结


开闭原则(Open-Closed Principle,OCP)

指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。强调的是用抽象构建框架,用实现扩展细节,可以提高软件系统的可复用性及可维护性。开闭原则是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统,例如版本更新,我们尽可能不修改源码,但是可以增加新功能

demo1

1、课程接口ICourse
2、他有一个Java架构课程的类JavaCourse
3、现在要给Java架构课程做活动,价格优惠。
如果修改JavaCourse中的getPrice()方法,则会存在一定风险,可能影响其他地方的调用结果。
如何在不修改原有代码的前提下,实现价格优惠这个功能呢?
4、我们再写一个处理优惠逻辑的JavaDiscountCourse类,而不是直接修改JavaCourse类

ublic interface ICourse {
   Double getPrice();
}

public class JavaCourse implements ICourse {
    public Double price;
    public JavaCourse(Double price) {
        this.price = price;
    }
    @Override
    public Double getPrice() {
        return price;
    }
}

public class JavaDiscountCourse extends JavaCourse {
    public JavaDiscountCourse(Double price) {
        super(price);
    }
    public Double getDiscountPrice(Double discountRate) {
        return price * discountRate;
    }
    public Double getOriginPrice() {
        return price;
    }
}

依赖倒置原则(Dependence Inversion Principle,DIP)

指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象抽象不应该依赖细节,细节应该依赖抽象。通过依赖倒置,可以降低类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并降低修改程序带来的风险

demo1

public class Tom {
    public void studyEnglish(){}
    public void studyChinese(){}
}

public class MainBusiness {
    public static void main(String[] args) {
        Tom tom = new Tom();
        tom.studyEnglish();
        tom.studyChinese();
    }
}

Tom再追加学习画画的话。
这个时候,需要业务扩展,代码要从底层到高层(调用层)一次修改代码。
在Tom类中增加studyDraw()的方法,在高层也要追加调用。
如此一来,在系统发布以后,实际上是非常不稳定的,在修改代码的同时会带来意想不到的风险。

public interface ICourse {
    void study();
}
public class ChineseCourse implements ICourse {
    @Override
    public void study() {
    }
}
public class EnglishCourse implements ICourse {
    @Override
    public void study() {
    }
}
public class Daming {
    void study(ICourse course){
        course.study();
    }
}
public class MainBusiness {
    public static void main(String[] args) {
        Daming daming = new Daming();
        daming.study(new ChineseCourse());
        daming.study(new EnglishCourse());
    }
}

如上涉及到了代码注入,目前已知有31、作依赖注入
        Daming daming = new Daming();
        daming.study(new ChineseCourse());
        
2、构造器注入
		Daming daming = new Daming(new ChineseCourse());
		
3Setter注入方式(如果lingLing的ICourse是全局单例,则只能选择Setter注入方式)
        public class LingLing {
            ICourse course;
            public void setCourse(ICourse course) {
                this.course = course;
            }
            public void study() {
                course.study();
            }
        }

抽象为基准比以细节为基准搭建起来的架构要稳定得多,因此大家在拿到需求后,要面向接口编程,按照先顶层再细节的顺序设计代码结构。


单一职责原则(Simple Responsibility Principle,SRP)

不要存在一个以上导致类变更的原因。假设有一个Class负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。这样一来,这个Class就存在两个导致类变更的原因。
如何解决这个问题呢?
我们就要分别用两个Class来实现两个职责,进行解耦。后期需求变更维护互不影响。这样的设计,可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。总体来说就是一个Class、Interface、Method只负责一项职责。

在实际项目中,代码会存在依赖、组合、聚合关系,在项目开发过程中还受到项目的规模、周期、技术人员水平、对进度把控的影响,导致很多类都不能满足单一职责原则。但是,我们在编写代码的过程中,尽可能地让接口和方法保持单一职责,对项目后期的维护是有很大帮助的。

接口隔离原则(Interface Segregation Principle,ISP)

指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口

这个原则指导我们在设计接口时,应当注意以下几点。
(1)一个类对另一个类的依赖应该建立在最小接口上。
(2)建立单一接口,不要建立庞大臃肿的接口。
(3)尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度)。

接口隔离原则符合“高聚合、低耦合”的设计思想,使得类具有很好的可读性、可扩展性和可维护性。在设计接口的时候,要多花时间思考,要考虑业务模型,包括还要对以后可能发生变更的地方做一些预判。所以,在实际开发中,我们对抽象、业务模型的理解是非常重要的。

demo1

public interface IAnimal {
    void fly();
    void swim();
    void run();
}

大多数时候鸟类的swim()方法可能只能空着,
陆地动物的fly()方法可能只能空着,代表接口就有些臃肿了
这时候,我们针对不同动物的行为来设计不同的接口
public interface IFlyAnimal {
    void fly();
}
public interface IRunAnimal {
    void run();
}
public interface ISwimAnimal {
    void swim();
}

迪米特法则(Law of Demeter,LoD)又叫作最少知道原则(Least Knowledge Principle,LKP)

指一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合。迪米特法则主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入和输出参数中的类都可以被称为成员朋友类,而出现在方法体内部的类不属于朋友类

demo1

public class Task {}

public class Employee {
    void checkTask(List<Task> tasks){}
}

public class Leader {
    public void commandCheck(Employee employee){
        List<Task> taskList = new ArrayList<>();
        employee.checkTask(taskList);
    }
}
public class MainBusiness {
    public static void main(String[] args) {
        Leader leader = new Leader();
        Employee employee = getTaskList();
        leader.commandCheck(employee);
    }
}

代码实际意义:领导吩咐员工取检查任务

根据迪米特法则,TeamLeaderCourse并不是朋友。
Employee统计需要引用Course对象,Leader只想要结果,不需要跟Course产生直接交流。

PS:领导让你统计报表你让老板去替你收集数据也说不过去

public class Employee {
    void checkTask1(){
        List<Task> taskList = getTaskList();
        // 校验逻辑
    }
}
public class Leader {
    public void commandCheck1(Employee employee){
        employee.checkTask1();
    }
}
public class MainBusiness {
    public static void main(String[] args) {
        Leader leader = new Leader();
        Employee employee1 = new Employee();
        leader.commandCheck1(employee1);
    }
}

领导发话就可以了,不需要做任何员工的任务量

里氏替换原则(Liskov Substitution Principle,LSP)

指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型

子类对象能够替换父类对象,而程序逻辑不变。说明,子类可以扩展父类的功能,但不能改变父类原有的功能。根据这个理解,我们对里氏替换原则的定义总结如下。

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

demo1

public class JavaCourse implements ICourse {
    public Double price;
    public JavaCourse(Double price) {
        this.price = price;
    }
    @Override
    public Double getPrice() {
        return price;
    }
}
public class JavaDiscountCourse extends JavaCourse {
    public JavaDiscountCourse(Double price) {
        super(price);
    }
    public Double getDiscountPrice(Double discountRate) {
        return price * discountRate;
    }
    public Double getOriginPrice() {
        return price;
    }
}

getOriginPrice() 就是典型的违反了里氏替换原则。我们不应该覆盖getPrice()方法

public class JavaDiscountCourse extends JavaCourse {
    public JavaDiscountCourse(Double price) {
        super(price);
    }
    public Double getDiscountPrice(Double discountRate) {
        return price * discountRate;
    }
    public Double getPrice() {
        return price;
    }
}

使用里氏替换原则有以下优点。
(1)约束继承泛滥,是开闭原则的一种体现。
(2)加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。


合成复用原则(Composite/Aggregate Reuse Principle,CARP)

指尽量使用对象组合(has-a)或对象聚合(contanis-a)的方式实现代码复用,而不是用继承关系达到代码复用的目的。合成复用原则可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较小。

继承,又被称为白箱复用,相当于把所有实现细节暴露给子类。
组合/聚合又被称为黑箱复用,对类以外的对象是无法获取实现细节的。
我们要根据具体的业务场景来做代码设计,其实也都需要遵循面向对象编程(Object Oriented Programming,OOP)模型。


总结

七大软件架构设计原则-读书笔记_第1张图片

-----------------------------------------------------------------------------摘自《设计模式就该这样学:基于经典框架源码和真实业务场景》(谭勇德)

你可能感兴趣的:(基础知识,java,开发语言)