【设计模式】个人学习笔记

第一章 为什么学习设计模式

第二章 理解设计原则

包含五个设计原则,S O L I D

  • S:单一责任原则(SRP),Single Responsibility Principle
  • O:开放封闭原则(OCP),Open Closed Principle
  • L:里式替换原则(LSP),Liskov Substitution Principle
  • I:接口分离原则(ISP),Interface Segregation Principle
  • D:依赖倒置原则(DIP),Dependency Inversion Principle

一、单一原则

1、如何理解单一职责原则(SRP)?

单一职责原则(Single Responsibility Principle),它要求一个类或模块应该只负责一个特定的功能,这有助于降低类之间的耦合度,提高代码的可读性和可维护性。
单一职责原则的定义描述非常简单,一个类或模块只负责完成一个职责或功能,也就是说,不要设计大而全的类,要设计力度小、功能单一的类。换个角度来讲就是,一个类包含了两个或两个以上业务不相干的功能,那就可以说他职责不够单一,应该将它拆分成多个功能更加单一、力度更细的类。

2、如何判断类的职责是否足够单一?

举例:在一个社交产品中,下面的UserInfo类来记录用户的信息,你认为UserInfo类的设计是否满足单一职责原则呢?

public class UserInfo {
	private long userId;
	private String username;	
	private String password;
	private String telephone;
	private String eamil;
	private String avatarUrl;
	// ..省略
	private String province; // 省
	private String city; // 市
	private String detailAddress;  // 区
}

一种观点是,UserInfo包含的都是跟用户相关的信息,所有的方法和属性都隶属于用户这样一个业务模型,满足单一职责原则。
另一种观点是:地址信息在UserInfo类中,所占的比重比较高,可以继续拆分出独立的Address类,UserInfo只保留除了Address之外的其他信息,拆分后两个类的职责更加单一。
哪一种说法更加准确呢?
实际上有一句话,脱离了业务谈设计就是耍流氓,事实上脱离了业务谈什么都是耍流氓,技术服务于业务这是亘古不变的道理
上边两种情况,我们不能脱离具体的应用场景,如果这个社交产品中,用户的地址信息和其他信息一样,只是单纯地用来做展示,那么UserInfo现在的设计就是合理的。但是。如果这个社交产品发展的比较好,之后又在产品中添加了电商的模块,用户的地址信息还会用在电商的五六种,那我们最好将地址信息从UserInfo中拆分出来,独立成用户的物流信息(或者地址信息、收货信息等)。
按照上述所讲继续延伸,如果这个社交产品的公司发展的越来越好。公司内部又开发出了很多其他的产品。公司希望支持统一账号系统,也就是用户一个账号可以在公司内部的所有产品中登录。这个时候,就需要对UserInfo再次进行拆分,将跟身份认证相关的信息(比如email、telephone等)抽取成独立的类。
从上述所说可以看出,不同应用场景,不同阶段的需求背景,对同一个类的职责是否单一的判定,可能都是不一样的,可能在某种应用场景下,一个类的设计已经满足单一职责原则了,但是如果换一个应用场景,可能就不满足了,需要拆分成力度更细的类。
综上所述,评价一个类的职责是否足够单一,并没有一个非常明确的、可以量化的标准。实际上,在进行设计的时候,也没有必要过于未雨绸缪,过度设计。所以,可以先写一个粗粒度的类,满足业务需求,随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,就可以将这个粗粒度的类,拆分成几个更细粒度的类,这就是所谓的持续重构
以下这几条原则,可以比较主管的去思考类是否职责单一:

  • 类中的代码行数,函数或属性过多,会影响代码的可读性和可维护性,这时候需要考虑对类进行拆分
  • 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,考虑对类进行拆分
  • 私有方法过多,就要考虑能否将私有方法独立到新的类中,设置为public方法,供更多的类使用,提高代码的重用性
  • 比较难给类起一个合适的名字,很难用一个业务名词概括,或者只能用一些比较笼统的Manager、Context等等这些词语来命名那个,这就说明类的 职责可能不够清晰
  • 类中的大量方法都是集中操作类中的某几个属性,比如,在UserInfo这个例子中,如果一半的方法都是在操作address的信息,就可以考虑将这几个属性和对应的方法拆分出来
    可能上边所讲的这些,还会存在一些疑问,这些问题不好定量的去回答,要多少才算是多?要多少是少?我觉得这部分的判断需要用在职业生涯中,接触的项目和代码越来越多,自然就会知道一个量的标准。

3、类的职责是否设计得越单一越好?

答案并不是,拆分的越细,就说明后续的维护会更加复杂,虽然提高了扩展性,但是代码的可维护性就变差了。
实际上,不管是应用设计原则还是设计模式,最终目的都是提高代码的可读性、可扩展性、复用性、可维护性,当在衡量一个设计原则是否合理的时候,也可以以此作为最终的考量标准。

二、开闭原则

1、原理概述

开闭原则的英文全称是 Open Closed Principle,简称OCP,软件实体(模块、类、方法等)应该“对扩展开放,对修改关闭”。
通俗一点讲就是,当需要新增一个功能时,应该是在已有代码的基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等),下面用一个简化的电商平台的订单折扣策略来说明:

Class Order{
	private double totalAmount;
	
	public Order(double totalAmount){
		this.totalAmount = totalAmount;
	}
	
	// 计算打折后的金额
	public double getDiscountedAmount(String discountType){
		double discountedAmount = totalAmount;
		if("FESTIVAL".equals(discountType)){
			discountedAmount = totalAmountl * 0.9; // 节日打九折
		} else if ("SEASONAL".equals(discountType)) {
			discountAmount = totalAMount * 0.8; // 季节打八折
		}
		
		return discountedAmount;
	}
}

上述代码,Order类中包含了一个计算折扣金额的方法,当需要添加新的折扣类型的时候,就需要去修改 getDiscountedAmount() 方法的代码,显然是不合理的,违反了开闭原则
以下是遵循开闭原则的代码:

// 抽象折扣策略接口
interface DisocuntStrategy {
	double getDiscountAmount(double totalAmount);
}

// 节日折扣策略
class FestivalDiscount implements DiscountStrategy {
	@Override
	public double getDiscountedAmount(double totalAmount) {
		return totalAmount * 0.9; // 节日九折
	}
}

// 季节折扣策略
class SeasonalDiscount implements DiscountStrategy {
	@Override
	public double getDiscountedAmount(double totalAmount) {
		return totalAmount * 0.8; // 季节八折
	}
}

Class Order{
	private double totalAmount;
	private DiscountStrategy discountStrategy;
	
	public Order(double totalAmount, DiscountStrategy discountStrate){
		this.totalAmount = totalAmount;
		this.discountStrategy = discountStrategy;
	}

	// 形参是一个接口,具体传递可以传递任意一个实现了该接口的类对象 FestivalDiscount SeasonalDiscount
	public void setDiscountStrategy(DiscountStrategy discountStrate){
		this.discountStrategy = discountStrategy;
	}
	
	// 计算打折后的金额
	public double getDiscountedAmount(){
		return discountStrategy.getDiscountAmount(totalAmount);
	}
}

2、修改代码就意味着违背开闭原则吗?

开闭原则的核心思想就是尽量减少对现有代码的修改,以降低修改带来的风险和影响,在实际开发过程中,完全不修改代码是不现实的,当需求变更或发现代码中的错误时,修改代码是正常的。然而,开闭原则鼓励通过设计更好的代码结构,使得在添加新功能或者扩展系统时,尽量减少现有代码的修改。
下面是一个简化日志记录器的实例,展示了在适当情况修改该代码,也不违背开闭原则。在这个例子中,应用程序支持将日志输出到控制台和文件,假设需要添加一个新功能,以便输出日志时同时添加一个时间戳。

interface Logger {
	void log(String message);
}

class ConsoleLogger implements Logger {
	@Override
	public void log(String message){
		System.out.println("Console:" + message);
	}
}

Class FileLogger implements Logger(){
	@Override
	public void log(String message){
		System.out.println("File:" + message);
		// 将日志写入文件的实现省略...
	}
}

为了添加时间戳的功能,需要修改现有的 ConsoleLogger 和 FileLogger 类,虽然需要修改代码,但是这是对现有功能的改进,而不是添加新的功能,所以这种修改是可以接受的,不违背开闭原则。
当遵循开闭原则的时候,其目的是为了让代码更容易进行维护,更具有复用性,同时降低了引入新缺陷的风险。但是,在某些情况下,遵循开闭原则可能会导致过度设计,增加代码的复杂性。因此在实际开发中,应该根据实际需求和预期的变化来平衡遵循开闭原则的程度,写代码不是为了设计而设计,脱离需求谈设计都是耍流氓,有些场景,比如项目的使用频率不高,修改的可能性很低,或者代码本来就很简单,使用了设计模式反而会增加开发难度,提高开发成本,得不偿失。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宫野琦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值