设计原则之-开闭原则(Open Close Principle, OCP)

定义

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

核心思想

开闭原则的核心思想就是抽象。开闭原则的主要目的就是为了让我们在不修改源码的情况下来扩展系统的功能。

可能有同学会问,这怎么可能?需求增加,不添加方法,不修改类怎么能实现功能。咳咳,这位同学,请注意,是扩展系统的功能,而不是新增,这两个概念是非常容易混淆的。其次这个修改是针对模块的,模块的修改有时会带来引用该模块的高层模块的修改,这种修改是不可避免的。

什么是扩展?比如我们系统中有一个活动,现在需要新加一个活动,对于需求来说是新增,但是对于活动这个功能或者模块来说就是扩展。而且实现扩展功能并不是不写代码就实现,而是不修改原来的代码实现新功能。

什么是新增?比如系统中有一个购物车模块,现在需要增加一个签到模块。这种才算是新增。

武功的修炼分为内功心法和外功招式,心法练的是内力,而招式是将内力外放,好的心法和招式的结合能够打出成吨的伤害。心法的修炼和招式的修炼是分开的,但是两者又相互关联影响,这就是松耦合。有了心法之后就可以修炼各种招式,这就是开闭原则,不用为了新学习一种招式而重修心法。

开闭原则的使用

开闭原则的目的是使我们在增加同一类型的功能时,不用修改原来的代码。比如现在有一个需求,要在系统中增加一个发送短信的功能。原始设计如下。

设计原则之-开闭原则(Open Close Principle, OCP)

  • MessageSendService:负责通过消息模板获取消息内容。
  • SmsSender:仅负责短信的发送,不处理任何业务逻辑。
// 伪代码
public class SmsSender {
	public void send(String userId, String  content) {
		UserInfo userInfo = this.getUserInfo(userId);
		String phoneNumber = UserInfo.getPhoneNumber();
		// 调用第三方短信发送接口发送短信。
	}

	private UserInfo getUserInfo(String userId) {
		UserInfo userInfo = ...;
		return userInfo;
	}
}

public class MessageSendService {
	public void sendBySms(String userId, String templateId) {
		SmsSender sender = new SmsSender();
		String content = this.getContent(templateId);
		sender.send(userId, content);
	}
	
	private String getContent(String templateId) {
		String content = ...;
		return content;
	}
}

public class Client {

	public static void main(String[] args) {
		MessageSendService sendService = new MessageSendService();
		sendService.sendBySms("001", "TP_01");
	}
}

很快新的需求又来了,要再增加一个发送Email的功能。于是又修改如下

设计原则之-开闭原则(Open Close Principle, OCP)

// 伪代码
public class EmailSender {
	public void send(String userId, String  content) {
		UserInfo userInfo = this.getUserInfo(userId);
		String email = UserInfo.getEmail();
		// 调用第三方短信发送接口发送短信。
	}

	private UserInfo getUserInfo(String userId) {
		UserInfo userInfo = ...;
		return userInfo;
	}
}

public class MessageSendService {
	...

	// 新增
	public void sendByEmail(String userId, String templateId) {
		EmailSender sender = new EmailSender();
		String content = this.getContent(templateId);
		sender.send(userId, content);
	}

}

public class Client {

	public static void main(String[] args) {
		MessageSendService sendService = new MessageSendService();
		sendService.sendBySms("001", "TP_01");
		sendService.sendByEmail("001", "TP_01");
	}
}

这种设计方式会导致一个问题,就是随着需求的不断增加,MessageSendService需要不断的修改,并且方法也不断的增加。而SmsSender中和EmailSender中的公共方法没有办法通用。

针对上述需求,我们使用OCP原则进行重构。

设计原则之-开闭原则(Open Close Principle, OCP)

public interface Sender {
	void send(String userId, String templateId); // 接口中的方法都是public的,所以不用加public修饰符。
}

public abstract class AbstractSender implements Sender {
	protected UserInfo getUserInfo(String userId) {
		UserInfo userInfo = ...;
		return userInfo;
	}
}

public class SmsSender extends AbstractSender {
	public void send(String userId, String  content) {
		UserInfo userInfo = this.getUserInfo(userId);
		String phoneNumber = UserInfo.getPhoneNumber();
		// 调用第三方短信发送接口发送短信。
	}
}

... // EmailSender & DingtalkSender

public class Client {

	public static void main(String[] args) {
		MessageSendService sendService = new MessageSendService();
		sendService.send(new SmsEnder(), "001", "TP_01");
		sendService.send(new EmailSender(), "001", "TP_01");
	}
}

这样设计就可以随意增加发送类型。而无惧需求变更了。当然现实中的例子比这个要复杂的多,此处只是一个抛砖引玉的作用。

在实际使用中,我们一般是借助Spring的依赖注入,所以可以将MessageSendService中的逻辑放到AbstractSender中,然后在客户端注入不同的Sender来达到同样的效果。