软件设计六大原则

37次阅读

共计 3804 个字符,预计需要花费 10 分钟才能阅读完成。

软件设计六大原则

网上有很多朋友都介绍过这六个原则,下面我简单说说我对这六个原则的一些理解。

如上图所示,这就是软件设计的六大原则。

其中我认为最重要的就是 开闭原则,可以理解为这是最终目的。其余的五种原则都是为了达到这个目的而存在的。

要理解开闭原则,则需要先看看另外五个原则。

单一职责(Single Responsibility Principle)

单一职责原则(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。

个人理解

这是单一职责的概念,我对这个原则的理解是,一个类最好只有一种职责,返回来说就是,把相同的职责放到一个类中。这样就实现了高内聚。

职责可以从各种维度去切割,比如以中间件为维度拆分类,或者以业务规则为维度拆分类等等。

想要将职责才分的恰到好处,需要对业务领域的深入理解,和对软件设计的经验。

例子

举个例子,用户模块的业务需要 MySqlRedis这两个中间件来支持。

如果我创建一个 UserRepository,里面既有访问MySql 存储数据的方法,又有访问 Redis 存储数据的方法,那么可以理解为这个类有两个职责,访问 MySqlRedis

那么可以创建两个类:UserRepository封装访问 MySql 的方法,UserRedisRepository封装访问 Redis 的方法,这种类的职责就单一了。

再举个例子,天气模块中的类 WeatherService 有一个方法getWeather(),该方法会发起 HTTP 请求访问第三方资源接口获取天气数据(返回 JSON 格式),随后将 JSON 数据转换成WeatherEntity

这里最好在进行一次拆分,可以把请求第三方接口当作一种职责,提供一个 WeatherProxy 类,然后将数据转换作为一种职责,提供一个 WeatherConverter 类。

好处

降低类的复杂度、提高可读性、提高可维护性、提高扩展性、降低了变更引起的风险。

里氏替换(Liskov Substitution Principle)

如果对每一个类型为 S 的对象 o1,都有类型为 T 的对象 o2,使得以 T 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 S 是类型 T 的子类型。

所有引用基类的地方必须能透明地使用其子类的对象,通俗的说,就是派生类(子类)对象可以在程序中代替其基类(父类)对象。

个人理解

通俗的理解是,在使用父类的地方可以任意使用其子类,但是在使用子类的地方不一定能使用父类。

例子

最简单的一个例子,在 Java 中,大家都有使用过List<T>,它的实现最初可以是ArrayList,随后也可以修改成LinkedList

在做修改时,List<T>的使用并不会被影响。

List<T> list = new ArrayList<>();
list.add(...);
list.forEach(t -> {...});

上面代码中,可随意切换 List<T> 的实现类,对后续使用的代码不会造成任何影响。

List<T> list = new LinkedList<>(); // 修改实现类不会对下面的代码造成影响
list.add(...);
list.forEach(t -> {...});

上面的例子中的 list.forEach 就实现了里氏替换原则,该方法接收任何 List 对象,此处传递 ArrayListLinkedList都是可以的,做到了 子类可以在程序中代替其父类

好处

增强程序的健壮性,即使切换了子类,不会对使用代码造成影响。

依赖倒置原则(Dependence Inversion Principle)

高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。

抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

个人理解

这里应该指的是面相接口编程,说的是对象与对象之间的依赖关系。

通俗的说就是把业务的功能划分清楚后,定义在接口和抽象类中,然后编写业务类继承或实现该接口或抽象类,如果对外发布封装的业务功能,要发布接口或抽象类,而不是具体的实现类。

例子

依赖倒置在工作中是最常使用的。

项目中常用的分层架构,Controller 层,Service 层,Dao 层。Controller 层会调用 Service 层,Service 层会调用 Dao 层。

假如使用 Spring Framework 开发用户模块,编写数据访问层的时候,都会编写一个 UserDao 接口,然后编写一个 UserDaoImpl 的实现类。UserServiceImpl会创建一个 UserDao 对象,然后使用 @Autowired 自动装配。

@Service("userService")
public class UserServiceImpl implements UserService {

    ....
    
    @Autowired
    public void setUserDao(UserDao userDao) {this.userDao = userDao;}

    
    private UserDao userDao;

}

这里就用到了依赖倒置原则,依赖抽象接口而不依赖于具体的实现类。

好处

可以减少类见的耦合性,提高系统的稳定性,可高代码的可读性和可维护性,也可以降低同时开发时引起的风险。

接口隔离原则(Interface Segregation Principle)

拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。

个人理解

设计接口的时候,尽量不要设计出一个拥有非常多的方法的庞大接口,应该尽量使用多个专门提供某种功能的接口。

例子

还是用开发用户模块举例,在编写根据手机号登录,如果登录不存在则注册的时候,需要用到发送短信验证码、验证短信验证码、登录、注册等功能。

此时,如果根据接口隔离原则,就需要登录将一个 LoginService 接口拆分成多个专门提供某种功能的接口,例如:

  • LoginService接口,里面有登录功能;
  • PinCodeService接口,里面有发送 PinCode 和验证 PinCode 的功能;
  • RegeditService接口,里面有注册的功能;

那么在登录时有可能会用到这三个接口。

此时,还有一个业务场景,修改手机号时,需要输入旧的手机号,获取 Pin 码并验证,然后输入新的手机号,获取 Pin 码并验证,最后调用修改电话号的功能。那么在修改手机号时只需要用到 PinCodeService 即可,不需要使用 LoginServiceRegeditService

这就是接口隔离原则的一个实现例子。

满足接口隔离原则的同时,先满足单一职责原则。

好处

使程序解耦,从而容易重构、更改和重新部署。

最少知识原则(Least Knowledge Principle)

又叫迪米特法则,系统中的类,尽量不要过多的与其他类互相作用,减少类之间的耦合度。

  • 每个单元对其他单元只拥有有限的知识,只了解与当前单元紧密联系的单元;
  • 每个单元只能和它的 “ 朋友 ” 交谈,不能和 “ 陌生人 ” 交谈;
  • 只和自己直接的 “ 朋友 ” 交谈;

个人理解

一个对象调用另一个对象时,应该不去过分关注这个对象的实现。被调用的对象的内部是如何复杂都和调用者无关,简单的说,调用者就知道被调用者提供的的 public 方法就行,其他的不需要关心。

例子

假设一个场景,老板要统计公司的人数,可以这个命令委托给主管,由主管去统计人数。


public class Boss {public void command(Manager manager) {....}
}

public class Manager {
    private List<Employee> employees;
    
    public Manager(List<Employee> employees) {this.employees = employees;}
    
    public int employeeCount() {return this.employees.size();
    }
}

public class Employee {}

public class Client {public static void mand(String[] args) {List<Employee> employees = new ArrayList<>();
        ... 添加 20 个 employee
        
        Boss boss = new Boss();
        boss.command(new Manager(employees));
    }
}

其中 Boss 只与 Manager 进行交谈,Manager只与 Employee 进行交谈,Boss不与 Employee 进行交谈,这就是最少知识原则的一个例子。

好处

类负责的职责清晰明了,使类可以高度内聚降低类与类之间的耦合。

开闭原则(Open Close Principle)

软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。

个人理解

我对开闭原则的理解是,程序应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。面向对象可以达到这种方式完成对软件的扩展。

在对软件进行设计时,应该尽可能的考虑到后续会变更的地方,以便在变更真发生时,可以尽可能在不修改或修改很少的情况下,把变更实现。

例子

开闭原则是最基础的设计原则,其它的五个设计原则都是开闭原则的具体形态。

好处

开闭原则可以提高复用性,维护性,减少测试范围。

正文完
 0