读《设计模式之禅》笔记一(六大设计原则)

本文深入解析设计模式中的六大核心原则:单一职责、里氏替换、依赖倒置、接口隔离、迪米特法则及开闭原则。通过具体示例阐述各原则的应用场景与实践方法,助力提升代码质量和系统可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

读《设计模式之禅》笔记一(六大设计原则)

单一职责(Single Responsibility Principle SRP)

There should never be more than one reason for a class to change
一个类的变化不应该有一个以上的原因

原则演示例子

简单的电话功能:
简单的电话功能
这个电话接口有两个职责:一个是协议管理(拨通电话、挂电话),另一个是数据传输(通话)。且其中一个职责的变化不会引起另一个职责的变化。则需要考虑将两个职责拆分开来,符合SRP。

进行SRP设计的简单电话功能
SRP设计的简单电话功能
单一职责最难的就是划分职责,一个职责一个接口,但是职责没有一个量化的标准,一个类到底负责几个职责,职责如何细化这些问题都是需要根据实际项目和环境考虑的。

单一职责不但适合接口,类,还适合方法

一个修改用户信息的例子:修改用户信息
这个方法十分实用,根据参数可以修改用户的信息。但是从单一职责角度出发,这个方法有很大问题:方法职责不清晰,不单一。每当涉及到用户某一项信息功能变化时,此方法都要受到影响。
单一职责改良后:
SRP改良后方法
这样可读性和扩展性都有提高。

类的单一职责原则的建议

在实际的项目开发中,一个类很难做到单一职责原则,书中作者的建议是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。


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

父类能出现的地方子类就可以出现。
要做此原则,有如下要求:

  • 子类必须完全实现父类的方法。
  • 子类可以有自己的个性。(LSP可以正着用,即子代替父,不能反着用,父代替子)
  • 实现或覆盖父类的方法时输入参数可以被放大。(即子类输入参数类可以是父类输入参数类的父类)
  • 实现或覆盖父类的方法时输出结果可以被缩小。

图书作者建议
在项目中采用里氏替换原则,尽量避免子类的“个性”,当子类拥有个性,子类与父类的关系就难以调和,把子类当父类用,子类的个性被抹杀;子类单独作为业务用,又让代码间耦合关系变得扑朔迷离,缺乏类替换的标准。


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

原则要求

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象。
  • 抽象不应该依赖细节。
  • 细节应该依赖抽象。

低层与高层模块解释:
每一个逻辑的实现都由原子逻辑组成,不可分割的原子逻辑就是底层模块。
原子逻辑的再组装就是高层模块。

在JAVA语言中表现:

  • 模块间的依赖通过抽象产生。
  • 接口或抽象类不依赖实现类。
  • 实现类依赖接口或抽象类。
    这就是面向接口编程OOD(Object-Oriented Design)

接口隔离原则(Interface Segregation Principle)

先讲接口,接口分两种

  • 实例接口:在JAVA中声明一个类,然后用new关键字产生一个实例,它是对一个类型的事务描述,这是一种接口。可能有人会说,这不是类吗?这是在Java语言浸染时间太长了。只要知道,从这个角度来说,Java的类也是一种接口。
  • 类接口:Java中interface关键词定义的接口。

什么是隔离?(两种定义)

  • 客户端不应该依赖它不需要的接口。
  • 类间的依赖关系应该建立在最小的接口上。

图书作者理解:
把两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗点讲就是:接口尽量细化,同时接口中的方法尽量少。有人可能会疑惑:这不和单一职责要求一样吗?错,接口隔离与单一职责的审视角度不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是从业务逻辑上划分的。而接口隔离原则则要求接口的方法尽量少。

当单一职责与接口隔离矛盾
在讲单一职责时,仔细分析一下IConnectionManager接口是否可以继续拆分下去?
SRP设计的简单电话功能
挂电话有两种方式:

  1. 正常挂断。
  2. 异常挂断。(例如手机没电)

看到这里是不是要把IConnectionManager接口拆封成两个,一个负责连接,一个负责挂断。且慢,再思考一下。如果拆分就不符合单一职责了,从业务逻辑讲:通信的建立与关闭已是最小的业务单位。如果再拆分,就算对业务或是协议的拆分,这样的设计就是失败的设计。但是一个原则要拆,一个原则又不拆,怎么办?好办,根据接口原则拆分接口时,首先满足单一职责原则

图书作者建议:

  1. 一个接口只服务与一个子模块或业务逻辑。
  2. 通过业务逻辑压缩接口中public方法。
  3. 已经污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理。
  4. 了解环境,拒绝盲从。每个项目或产品都有特定的环境因素。看到大师是这样做的你就照抄是不行的。环境不同,接口拆分就不同,深入了解业务逻辑,最好的接口设计就出自你的手中!

迪米特法则(Law of Demeter LoD)

原则要求:
一个对象应该对其他对象有最少的了解。通俗的讲就是对类的低耦合提出要求,其包含以下4层含义:

1. 只与朋友类交流。(朋友类的定义:出现在成员变量、方法的输入输出参数中的类称为成员朋友类)
2. 朋友间也是有距离的。
讲一个软件安装的例子:在安装软件时,经常有导向动作,第一步是确认是否安装,第二步确认License,第三步选择安装目录等等流程。先设计出类图:
软件安装过程类图

Wizard导向类

public class Wizard {
	private Random rand = new Random(System.currentTimeMillis());
	//第一步
	public int first(){
		System.out.println("执行第一个方法...");
		return rand.nextInt(100);
	}
	//第二步
	public int second(){
		System.out.println("执行第二个方法...");
		return rand.nextInt(100);
	}
	//第三步
	public int third(){
		System.out.println("执行第三个方法...");
		return rand.nextInt(100);
	}
}

InstallSoftware类

public class InstallSoftware{
	public boolean installWizard(Wizard wizard){
		int first = wizard.first();
		//根据first返回的结果,看是否需要执行second
		if(first > 50){
			int second = wizard.second();
			if(second > 50){
				int third = wizard.third();
				return true;
			}
		}
		return false;
	}
}

仔细思考一下,上述设计是否符合第二条“朋友间也是有距离的”?Wizard类把太多的方法暴露给了InstallSoftware类,InstallSoftware类只用关心是否安装好,并不在意流程,两者的朋友关系太过亲密。如果要将Wizard类中的first方法返回值由int改为boolean,就需要修改InstallSoftware类。我们重新设计一下:
重构后的软件安装过程类图
具体的代码我就不写了,通过重构,类间的耦合关系变弱了,结构也清晰了,变更引起的风险也变小了。

3. 是自己的就是自己的
在实际应用中经常会出现这样一个方法:放在本类也可以,放在其他类中也没有错,那该怎么办?可以坚持一个原则:如果一个方法放在本类中,既不增加类间的关系,也对本类不产生负面影响,那就放置在本类中。

4. 谨慎使用Serializable
请自行百度。

图书作者建议:
迪米特法则的核心观念就是类间解耦,弱耦合。但是解耦有限度,在项目中须适度的考虑此原则。


开闭原则(Open Closed Principle)

原则要求:
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
书店销售书籍的例子:
书店售书类图
项目投产了,书籍正常销售出去了,当书店开了一周年,想举行图书打折活动,对于项目来说,这就是个变化,如何应对这样的变化,有三种做法:

  1. 修改接口: 在IBook上新增一个方法getOfficePrice(),专门用于进行打折处理,所有的实现类实现该方法。这样做的结果是NovelBook类要修改,BookStore中的main方法也要修改,同时IBook作为接口应该是稳定且可靠的,不应该经常发生变化,所以这个方案Pass。
  2. 修改实现类 修改NovelBook类中的方法,直接在getPrice()中实现打折处理,这的确算是个好办法,但是该方法也有缺陷,例如采购书籍人员也要看图书原价,而该方法导致看到的价格就是书籍折后价格,容易引起信息上的误会,所以该方案也不是个最优的方案,
  3. 通过扩展实现变化 增加一个子类OffNovelBook, 覆写getPrice方法,高层次的模块通过OffNovelBook类产生新的对象,完成业务变化对系统的最小化开发。好办法,修改也少,风险也小。修改后的类图:
    扩展后的书店售书类图
    要注意:开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。

如何使用开闭原则:

  1. 抽象约束。
  2. 元数据控制模块行为。(通俗点就是配置参数,从配置文件中获取参数和依赖)
  3. 制定项目章程。
  4. 封装变化。(两层含义:将相同的变化封装到一个接口或抽象中;第二将不同的变化封装到不同的接口和抽象中。)

博主言:

此书是我认为讲的通俗易懂的好书,以上内容多是书中原话,感兴趣的人可以自行去购买阅读,我比较偷懒书中的大多数代码没有敲上来,我本着服务自己的态度写了这篇博客,如有不好的地方,值得去改进的地方,希望能与大家探讨,共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值