zoukankan      html  css  js  c++  java
  • 工作那么久,才知道的 SOLID 设计原则

    认识 SOLID 原则

    无论是软件系统设计,还是代码实现,遵循有效和明确的设计原则,都利于系统软件灵活可靠,安全快速的落地,更重要的是能灵活地应对需求,简化系统扩展和维护,避免无效的加班。本文主要讨论面向对象软件开发中最流行的设计原则- SOLID,它是五个设计原则为了方便记忆而组成的首字母缩写:

    • 单一职责原则
    • 开放/封闭原则
    • 里式替换原则
    • 接口隔离原则
    • 依赖倒置原则

    S:单一职责原则 (SRP)

    基本概念

    单一职责原则 (SRP) 英文全称为 Single Responsibility Principle,是最简单,但也是最难用好的原则之一。它的定义也很简单:对于一个类而言,应该仅有一个引起它变化的原因。其中变化的原因就表示了这个类的职责,它可能是某个特定领域的功能,可能是某个需求的解决方案。


    这个原则表达的是不要让一个类承担过多的责任,一旦有了多个职责,那么它就越容易因为某个职责而被更改,这样的状态是不稳定的,不经意的修改很有可能影响到这个类的其他功能。因此,我们需要将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,不同类之间的变化互不影响。

    实例说明

    举一个具体的例子,有一个用于实现编辑和打印报表的类,这样的类存在两个变化的原因:第一,报表的内容可以改变(编辑)。第二,报表的格式可以改变(打印)。如果有一个对于报表编辑流程的修改,而报表的编辑流程会导致公共状态或者依赖关系的改变,使得打印功能的代码无法工作。所以单一职责原则认为这两个变化的原因事实上是两个分离的功能,它们应该分离在不同的类中。
    image.png

    相关设计模式

    面对违背单一职责原则的程序代码,我们可以利用外观模式,代理模式,桥接模式,适配器模式,命令模式对已有设计进行重构,实现多职责的分离。

    小结

    单一职责原则用于控制类的粒度大小,减少类中不相关功能的代码耦合,使得类更加的健壮;另外,单一职责原则也适用于模块之间解耦,对于模块的功能划分有很大的指导意义。

    O:开闭原则 (OCP)

    基本概念

    开闭原则 (OCP) 英文全称为 Open-Closed Principle,基本定义是软件中的对象(类,模块,函数等)应该对于扩展是开放的,但是对于修改是封闭的。这里的对扩展开放表示这添加新的代码,就可以让程序行为扩展来满足需求的变化;对修改封闭表示在扩展程序行为时不要修改已有的代码,进而避免影响原有的功能。


    要实现不改代码的情况下,仍要去改变系统行为的关键就是抽象和多态,通过接口或者抽象类定义系统的抽象层,再通过具体类来进行扩展。这样一来,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,达到开闭原则的要求。

    实例说明

    同样,举个例子来更深刻地理解开闭原则:有一个用于图表显示的 Display 类,它能绘制各种类型的图表,比如饼状图,柱状图等;而需要绘制特定图表时,都强依赖了对应类型的图表,Display 类的内部实现如下:

    public void display(String type) {
        if (type.equals("pie")) {  
          PieChart chart = new PieChart();  
          chart.display();  
        }  else if (type.equals("bar")) {  
          BarChart chart = new BarChart();  
          chart.display();  
    	} 
    }
    

    基于上述的代码,如果需要新增一个图表,比如折线图 LineChart ,就要修改 Display 类的 display() 方法,增加新增的判断逻辑,很显然这样的做法违反开闭原则。而让类的实现符合开闭原则的方式就是引入抽象图表类 AbstractChart,作为其他图表的基类,让 Display 依赖这个抽象图表类 AbstractChart,然后通过 Display 决定使用哪种具体的图表类,实现代码变成了这样:

    private Abstractchart chart;
    
    public void display() {
        chart.display();  
    }
    

    现在我们需要新增折线图显示,在客户端向 Display 中注入一个 LineChart 对象即可,无须修改现有类库的源代码。
    image.png

    相关设计模式

    面对违背开闭原则的程序代码,可以用到的设计模式有很多,比如工厂模式,观察者模式,模板方法模式,策略模式,组合模式,使用相关设计模式的关键点就是识别出最有可能变化和扩展的部分,然后构造抽象来隔离这些变化。

    小结

    有了开闭原则,面向需求的变化就能进行快速的调整实现功能,这大大提高系统的灵活性,可重用性和可维护性,但会增加一定的复杂性。

    L: 里式替换原则 (LSP)

    基本概念

    里式替换原则 (LSP) 英文全称为 Liskov Substitution Principle,基本定义为:在不影响程序正确性的基础上,所有使用基类的地方都能使用其子类的对象来替换。这里提到的基类和子类说的就是具有继承关系的两类对象,当我们传递一个子类型对象时,需要保证程序不会改变任何原基类的行为和状态,程序能正常运作。

    实例说明

    为了能理解里式替换原则,这里举一个经典的违反里式替换原则的例子:正方形/长方形问题。
    image.png
    上图为正方形/长方形问题的类层次结构,Square 类继承了 Rectangle 类,但是 Rectangle 类的宽高可以分别修改,但是 Suqare 类的宽高则必须一同修改。如果 User 类操作 Rectangle 类时,但实际对象是 Suqare 类型时,就会造成程序的出错,如下方代码:

    Rectangle r = ...; // 返回具体类型对象
    r.setWidth(5);
    r.setHeight(2);
    assert(r.area() == 10);
    

    当返回具体类型对象为 Suqare 类型,面积为 10 的断言就是失败,这样明显是不符合里式替换原则的。

    小结

    要让程序代码符合里式替换原则,需要保证子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法,换句话就是子类可以扩展父类的功能,但不能改变父类原有的功能。


    另一方面,里式替换原则也是对开闭原则的补充,不仅适用于继承关系,还适用于实现关系的设计,常提到的 IS-A 关系是针对行为方式来说的,如果两个类的行为方式是不相容,那么就不应该使用继承,更好的方式是提取公共部分的方法来代替继承。

    I:接口隔离原则 (ISP)

    基本概念

    接口隔离原则 (ISP) 英文全称为 Interface Segregation Principle,基本定义:客户端不应该依赖那些它不需要的接口。客户端应该只依赖它实际使用的方法,因为如果一个接口具备了若干个方法,那就意味着它的实现类都要实现所有接口方法,从代码结构上就十分臃肿。

    实例说明


    image.png


    现在我们看下一个违反接口隔离原则的例子,从上面类结构图中,有多个用户需要操作 Operation 类。如果 User1 只需要使用 operation1 方法,User2 只需要使用 operation2 方法,User3 只需要使用 operation3 方法,那么很明显对于 User1 来说,不应该看到 operation2 和 operation3 这两个方法,要减少对自己不关心的方法的依赖,防止 Operation 类中 operation2 和 operation3 方法的修改,影响到 User1 的功能。这个问题可以通过将不同的操作隔离成独立的接口来解决,具体如下图所示。
    image.png






    基于接口隔离原则,我们需要做的就是减少定义大而全的接口,类所要实现的接口应该分解成多个接口,然后根据所需要的功能去实现,并且在使用到接口方法的地方,用对应的接口类型去声明,这样可以解除调用方与对象非相关方法的依赖关系。总结一下,接口隔离原则主要功能就是控制接口的粒度大小,防止暴露给客户端无相关的代码和方法,保证了接口的高内聚,降低与客户端的耦合。

    D:依赖倒置原则 (DIP)

    基本概念

    依赖倒置原则 (DIP) 英文全称 Dependency Inversion Principle, DIP),基本定义是:

    • 高层模块不应该依赖低层模块,应该共同依赖抽象;
    • 抽象不应该依赖细节,细节应该依赖抽象。

    这里的抽象就是接口和抽象类,而细节就是实现接口或继承抽象类而产生的类。

    实例说明

    如何理解“高层模块不应该依赖低层模块,应该共同依赖抽象”呢?如果高层模块依赖于低层模块,那么低层模块的改动很有可能影响到高层模块,从而导致高层模块被迫改动,这样一来让高层模块的重用变得非常困难。
    file而最佳的做法就如上图一样,在高层模块构建一个稳定的抽象层,并且只依赖这个抽象层;而由底层模块完成抽象层的实现细节。这样一来,高层类都通过该抽象接口使用下一层,移除了高层对底层实现细节的依赖。

    相关设计模式

    关于依赖倒置原则,可以用到的设计模式有工厂模式,模板方法模式,策略模式。

    小结

    依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。同时依赖倒置原则也是框架设计的核心原则,善于创建可重用的框架和富有扩展性的代码,比如 Tomcat 容器的 Servlet 规范实现,Spring Ioc 容器实现。

    结语

    到这里,SOLID 设计原则就全部介绍完了,本文的主要目的还是对这六项原则系统地整理和总结,在后续的程序设计开发过程中能有意识地识别出设计原则和模式。如果大家对设计原则有更多想法和理解,欢迎留言,大家共同探讨。

    本文由博客一文多发平台 OpenWrite 发布!

  • 相关阅读:
    HDU 1114 Piggy-Bank
    HDU 2955 Robberies
    NTOJ 290 动物统计(加强版)
    POJ 3624 Charm Bracelet
    HDU 2602 Bone Collector
    POJ 1523 SPF(无向图割顶)
    HDU 5311 Hidden String
    HDU 1421 搬寝室
    HDU 1058 Humble Numbers
    POJ 3259 Wormholes(spfa判负环)
  • 原文地址:https://www.cnblogs.com/one12138/p/13260123.html
Copyright © 2011-2022 走看看