软件开发中的原则 - SOLID

前言

遵守软件开发的原则,可以让我们在‘软件构建活动’中,写出扩展性、复用性极强的代码。

一、开发原则SOLID

面向对象的基本原则(solid)是五个,但是在经常被提到的除了这五个之外还有 迪米特法则合成复用原则等, 所以在常见的文章中有表示写六大或七大原则的; 除此之外我还将给出一些其它相关书籍和互联网上出现的原则。

1、S单一职责SRP

原则定义

Single-Responsibility Principle, 一个(或者大到模块,小到方法), 最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则的引申,将职责定义为引起变化的原因,以提高内聚性减少引起变化的原因。

原则分析

  • 一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
  • 类的职责主要包括两个方面: 【数据职责】和【行为职责】,数据职责通过其属性来体现,而行为职责通过其方法来体现。

原则示例

SpringMVC 中Entity,DAO,Service,Controller, Util等的分离。

2、O开放封闭原则OCP

原则定义

Open - ClosedPrinciple ,OCP, 对扩展开放,对修改关闭(设计模式的核心原则)。意思是,在一个系统或者模块中,对于扩展是开放的,对于修改是关闭的,一个好的系统是在不修改源代码的情况下,可以扩展你的功能,而 实现开闭原则的关键就是抽象化

原则分析

  • 当软件实体因需求要变化时, 尽量通过扩展已有软件实体,可以提供新的行为,以满足对软件的新的需求;而不是修改已有的代码,使变化中的软件有一定的适应性和灵活性 。已有软件模块,特别是最重要的抽象层模块不能再修改,这使变化中的软件系统有一定的稳定性和延续性。

  • 实现开闭原则的关键就是抽象化 : 在"开-闭"原则中,不允许修改的是抽象的类或者接口,允许扩展的是具体的实现类 抽象类和接口在"开-闭"原则中扮演着极其重要的角色…即要预知可能变化的需求.又预见所有可能已知的扩展…所以在这里"抽象化"是关键!

原则示例

设计模式中模板方法模式和观察者模式都是开闭原则的极好体现.

3、L里氏替换原则LSP

原则定义

Liskov Substitution Principle ,LSP: 任何基类可以出现的地方,子类也可以出现,这一思想表现为对继承机制的约束规范。只有子类能够替换其基类时, 才能够保证系统在运行期内识别子类, 这是保证继承复用的基础

原则分析

  • 讲的是基类和子类的关系,只有这种关系存在时,里氏代换原则才存在。正方形是长方形是理解里氏代换原则的经典例子。
  • 里氏代换原则可以通俗表述为: 在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。
  • 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象

原则示例

4、I接口隔离法则ISL

原则定义

(Interface Segregation Principle,ISL): 客户端不应该依赖那些它不需要的接口。(这个法则与迪米特法则是相通的)。

另一种定义方法: 一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。

注意,在该定义中的接口指的是所定义的方法 。例如外面调用某个类的public方法。这个方法对外就是接口。

原则分析

  • 接口隔离原则是指使用多个专门的接口,而不使用单一的总接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干。
    一个接口就只代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以叫做“角色隔离原则”。
    – 接口仅仅提供客户端需要的行为,即所需的方法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
  • 使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
  • 可以在进行系统设计时采用定制服务的方式,即为不同的客户端提供宽窄不同的接口,只提供用户需要的行为,而隐藏用户不需要的行为。

原则示例

5、D依赖倒置原则DIP

原则定义

Dependency-Inversion Principle 要依赖抽象, 而不要依赖具体的实现。

高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:
1)抽象不应当依赖于细节;细节应当依赖于抽象;
2)要针对接口编程,不针对实现编程。

原则分析

  • 如果说开闭原则是面向对象设计的目标, 依赖倒转原则是到达面向设计"开闭"原则的手段…如果要达到最好的"开闭"原则,就要尽量的遵守依赖倒转原则. 可以说依赖倒转原则是对"抽象化"的最好规范! 我个人感觉,依赖倒转原则也是里氏代换原则的补充…你理解了里氏代换原则,再来理解依赖倒转原则应该是很容易的
  • 依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中。类之间的耦合: 零耦合关系,具体耦合关系,抽象耦合关系。
  • 依赖倒转原则要求客户端依赖于抽象耦合,以抽象方式耦合是依赖倒转原则的关键。

原则示例

理解这个依赖倒置,首先我们需要明白依赖在面向对象设计的概念:

依赖关系(Dependency): 是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系。(假设A类的变化引起了B类的变化,则说名B类依赖于A类。)大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。在UML中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方。

例子: 某系统提供一个数据转换模块,可以将来自不同数据源的数据转换成多种格式,如可以转换来自数据库的数据(DatabaseSource)、也可以转换来自文本文件的数据(TextSource),转换后的格式可以是XML文件(XMLTransformer)、也可以是XLS文件(XLSTransformer)等。
在这里插入图片描述
由于需求的变化,该系统可能需要增加新的数据源或者新的文件格式,每增加一个新的类型的数据源或者新的类型的文件格式,客户类MainClass都需要修改源代码,以便使用新的类,但违背了开闭原则。现使用依赖倒转原则对其进行重构。
在这里插入图片描述

  • 当然根据具体的情况,也可以将AbstractSource注入到AbstractStransformer,依赖注入的方式有以下三种:
/** 
 * 依赖注入是依赖AbstractSource抽象注入的,而不是具体 
 * DatabaseSource 
 * 
 */  
abstract class AbstractStransformer {
    
      
    private AbstractSource source;   
    /** 
     * 构造注入(Constructor Injection): 通过构造函数注入实例变量。 
     */  
    public void AbstractStransformer(AbstractSource source){
    
      
        this.source = source;           
    }  
    /**      
     * 设值注入(Setter Injection): 通过Setter方法注入实例变量。 
     * @param source : the sourceto set        
     */       
    public void setSource(AbstractSource source) {
    
                
        this.source = source;             
    }  
    /** 
     * 接口注入(Interface Injection): 通过接口方法注入实例变量。 
     * @param source 
     */  
    public void transform(AbstractSource source ) {
    
        
        source.getSource();  
        System.out.println("Stransforming ...");    
    }      
}

二、其它原则

1、合成/聚合复用原则

原则定义

(Composite/Aggregate ReusePrinciple ,CARP): 要尽量使用对象组合,而不是继承关系达到软件复用的目的。

原则分析

  • 在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承。
    • 继承复用: 实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用)
    • 组合/聚合复用: 耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。(“黑箱”复用)
  • 组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
  • 此原则和里氏代换原则氏相辅相成的,两者都是具体实现"开-闭"原则的规范。违反这一原则,就无法实现"开-闭"原则。

总结

  • 封装变化
  • 少用继承 多用组合
  • 针对接口编程 不针对实现编程
  • 为交互对象之间的松耦合设计而努力
  • 类应该对扩展开发 对修改封闭(开闭OCP原则)
  • 依赖抽象,不要依赖于具体类(依赖倒置DIP原则)
  • 密友原则:只和朋友交谈(最少知识原则,迪米特法则)
    说明:一个对象应当对其他对象有尽可能少的了解,将方法调用保持在界限内,只调用属于以下范围的方法:该对象本身(本
    地方法)对象的组件 被当作方法参数传进来的对象 此方法创建或实例化的任何对象
    别找我(调用我)我会找你(调用你)(好莱坞原则)
  • 一个类只有一个引起它变化的原因(单一职责SRP原则)

猜你喜欢

转载自blog.csdn.net/qq_43783527/article/details/129597163