设计模式前要-七大软件架构设计原则

    科技2026-01-16  12

    目录

    一:开闭原则

    二:依赖倒置原则

    三:单一职责原则

    四:接口隔离原则。

    五:迪米特法则

    六:里氏替换原则

    七:合成复用原则

    设计原则总结:


    一:开闭原则

    开闭原则(Open-Closed Principle,OCP),指一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。强调的是用抽象构建框架用实现扩展细节,可以提高软件的可复用性以及可维护性。开闭原则可以指导我们不修改源码,但是可以增加新功能。

    比如我们的service层,dao层用接口来实现,用实现类来扩展功能。

    二:依赖倒置原则

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

    比如有一个Tom类,Tom学习Java,Python课程。

    public class Tom { public void studyJavaCourse(){ System.out.println("tom 学习Java课程"); } public void studyPythonCourse(){ System.out.println("tom 学习Python课程"); } }

    在使用的时候如下:

    public class CourseMain { public static void main(String[] args) { Tom tom = new Tom(); tom.studyJavaCourse(); tom.studyPythonCourse(); } }

    如果后面Tom需要学习新的课程,就需要修改Tom的源码继续增加其它课程的方法。代码要从底层到高层(调用层)依次修改。如此一来系统发布以后维护很不稳定。

    因此要把这个功能分成抽象接口来实现。

    首先有个课程的接口ICourse

    public interface ICourse { void study(); }

    Java课程单独实现一个类。

    public class JavaCourse implements ICourse { @Override public void study() { System.out.println("学习Java"); } }

    Python课程单独实现一个类。

    public class PythonCourse implements ICourse { @Override public void study() { System.out.println("学习Python"); } }

    对于Tom类,依赖课程的抽象ICourse.

    public class Tom { public void study(ICourse course){ System.out.println("tom 正在"); course.study(); } }

    调用的时候:

    public class CourseMain { public static void main(String[] args) { Tom tom = new Tom(); tom.study(new JavaCourse()); tom.study(new PythonCourse()); } }

    这个时候看代码,无论Tom再想学什么课程,只要新增课程的实现类,再高层调用的时候传入就行了,而不需要修改底层的代码。实际上这是一中非常熟悉的方式,依赖注入,注入的方式还有构造器方法注入和Setter方法注入,只是向Tom传递ICourse实现类的方式不一样而且。

    注意:

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

    三:单一职责原则

    单一职责原则(Simple Responsibility Principle)SRP,指不要存在一个以上导致类变更的原因,假设一个类负责两个功能,一旦需求发生变化,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。因此我们需要用两个类进行实现两个不同的职责功能,进行解耦。总体来说,一个Class,Interface,Method只负责一项职责。

    这个比较好理解,就不举例子了。

    四:接口隔离原则。

    接口隔离原则(Interface Segregation Principle ,ISP)指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。因此设计接口时应当注意一下几点:

    (1)一个类对另外一个类的依赖应该建立在最小接口上。

    (2)建立单一接口,不要建立庞大臃肿的接口。

    (3)尽量细化接口,接口中方法尽量少(也不是越少越好,要适度)

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

    假如有个动物的顶级接口:  

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

    实现一只狗:

    package com.my.ioc.pojo.design; public class Dog implements IAnimal { @Override public void eate() { System.out.println("dog 吃东西"); } @Override public void fly() { } @Override public void swim() { } }

    实现一只鸟:

    package com.my.ioc.pojo.design; public class Bird implements IAnimal { @Override public void eate() { } @Override public void fly() { System.out.println("bird fly"); } @Override public void swim() { } }

    由以上两个实现类看出Dog类中的fly方法只能空着,Bird中的swim方法也只能空着,这个时候就需要针对不同的动物行为来设计不同的接口,分别设计IEateAnimal,ISwimAnimal,IFlyAnimal接口。

    public interface IEateAnimal { void eate(); } public interface IFlyAnimal { void fly(); } public interface ISwimAnimal { void swim(); }

    针对不同的动物有不同的行为来实现不同的接口。

    public class DogNew implements IEateAnimal,ISwimAnimal { @Override public void eate() { System.out.println("dog 要吃东西"); } @Override public void swim() { System.out.println("dog 其实是可以游泳的"); } } public class BirdNew implements IFlyAnimal,IEateAnimal { @Override public void eate() { System.out.println("bird 吃东西"); } @Override public void fly() { System.out.println("bird 要飞起来"); } }

    五:迪米特法则

    迪米特法则(Law of Demeter ,LoD)又叫最少知道法则,指一个对象应该对其它对象保持最少的了解,尽量降低类与类之间的耦合。

    它主要强调只和朋友交流,不和陌生人说话。出现在成员变量,方法的输入输出参数中的类都可以称为成员朋友,而出现在方法体内部的类不属于朋友类。

    迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

    比如:我们经常使用的三层架构,controller层中调用service层,在service层中调用dao层,在dao层中访问数据库,而不是跳过中间层直接controller访问dao即使service层有时候并不处理任何业务逻辑,只起到友元类的作用。

    六:里氏替换原则

            里氏替换原则(Liskov Substitution Principle ,LSP)指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有对象O1替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。

           定义可以理解为: 一个软件实体如果适用于一个父类,则一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。换句话说,子类可以扩展父类的功能,但不能改变父类原有的功能。根据这个理解,我们对其总结如下:

    1)子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

    2)子类可以增加自己特有的方法。

    3)当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类方法更宽松。

    4)当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的返回值要比父类的方法更加严格或相等。

    用正方形,矩形,和四边形的关系说明里氏替换原则,我们都知道正方形是一个特殊的长方形,可以新建一个长方形父类Rectangle.

    package com.my.ioc.pojo.design; public class Rectangle { private long heigth; private long width; public long getHeigth() { return heigth; } public void setHeigth(long heigth) { this.heigth = heigth; } public long getWidth() { return width; } public void setWidth(long width) { this.width = width; } }

    创建正方形继承长方形:

    package com.my.ioc.pojo.design; public class Squar extends Rectangle { private long length; public long getLength() { return length; } public void setLength(long length) { this.length = length; } @Override public long getHeigth() { return getLength(); } @Override public void setHeigth(long heigth) { setLength(heigth); } @Override public long getWidth() { return getLength(); } @Override public void setWidth(long width) { setLength(width); } }

     使用:

    public class CourseMain { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.setHeigth(10); rectangle.setWidth(20); resize(rectangle); } public static void resize(Rectangle rectangle){ while (rectangle.getWidth()>=rectangle.getHeigth()){ rectangle.setHeigth(rectangle.getHeigth()+1); System.out.println("width:"+rectangle.getWidth()+"heigth"+rectangle.getHeigth()); } System.out.println("end: width:"+rectangle.getWidth()+"heigth"+rectangle.getHeigth()); } } > Task :spring-myselftest:CourseMain.main() width:20heigth11 width:20heigth12 width:20heigth13 width:20heigth14 width:20heigth15 width:20heigth16 width:20heigth17 width:20heigth18 width:20heigth19 width:20heigth20 width:20heigth21 end: width:20heigth21

    由运行结果可知,高比宽还大,这在长方形中是不正常的。看下如果把长方形换成子类正方形看下结果:

    public static void main(String[] args) { Squar rectangle = new Squar(); rectangle.setHeigth(10); rectangle.setWidth(20); resize(rectangle); }

    结果运行是个死循环:  

    public static void main(String[] args) { Squar rectangle = new Squar(); rectangle.setHeigth(10); rectangle.setWidth(20); resize(rectangle); } width:12190heigth12190 width:12191heigth12191 width:12192heigth12192 width:12193heigth12193 width:12194heigth12194 width:12195heigth12195 width:12196heigth12196 width:12197heigth12197 width:12198heigth12198 width:12199heigth12199 width:12200heigth12200 width:12201heigth12201 > Task :spring-myselftest:CourseMain.main() FAILED

    在将父类替换为子类之后,程序运行结果没有达到预期,违背了里氏替换原则,因此代码设计处在一定的风险。里氏替换原则只存在于父类与子类之间,约束继承泛滥。

    再来新建一个四边形接口-QuardRangle

    public interface QuardRangle { long getWidth(); long getHeigth(); } public class Rectangle implements QuardRangle{ private long heigth; private long width; public long getHeigth() { return heigth; } public void setHeigth(long heigth) { this.heigth = heigth; } public long getWidth() { return width; } public void setWidth(long width) { this.width = width; } } public class Squar implements QuardRangle { private long length; public void setLength(long length) { this.length = length; } @Override public long getWidth() { return length; } @Override public long getHeigth() { return length; } }

    如果此时把resize方法的参数换成Squar就会报错,Squar不再是Rectangle的子类,约束了继承。

    七:合成复用原则

    合成复用原则(Composite/Aggregate Reuse Principle,CARP)指尽量使用对象组合(has-a)或对象聚合(contanis-a)的方式实现代码复用,而不是通过继承关系达到代码复用的目的。降低类之间的耦合,使得一个类的变化对另外的类影响相对较小。

    继承,又称为白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合又称为黑盒复用对类以外的对象是无法获取实现细节的。

     

    设计原则总结:

    设计原则是设计模式的基础,在实际开发过程中不一定所有代码都遵循设计原则,但是我们需要考虑业务场景进行权衡代码的设计。

               设计原则                 归纳  目的          开闭原则对扩展开放,对修改关闭降低代码维护带来的新风险       依赖倒置原则高层不应该依赖底层更有利于代码结构的升级扩展        单一职责原则一个类只做一件事便于理解,提高代码可读性       接口隔离原则一个接口只做一件事功能解耦,高内聚,低耦合       迪米特法则不该知道的不知道只和朋友交流不和陌生人说话,减少代码臃肿     里氏替换原则子类重写方法功能发生改变,不应该影响父类方法的含义防止继承泛滥    合成复用原则尽量使用组合代码复用,而不是继承降低代码耦合

     

     

     

     

     

     

     

     

     

     

    参考书籍《设计模式就该这样学》-谭勇德

     

     

     

     

     

    Processed: 0.018, SQL: 9