SOLID五大原则(二)开放关闭原则OCP
应对复杂业务利器之开闭原则,掌握并遵守该原则是应对复杂业务的强有效手段
先吹牛逼
开闭原则,英文缩写OCP,全称Open Closed Principle。
原始定义:Software entities (classes, modules, functions) should be open for extension but closed for modification。
字面翻译:软件实体(包括类、模块、功能等)应该对扩展开放,但是对修改关闭。
其实,对修改关闭,我的理解是:尽可能少的修改原有的代码。
开闭原则的意义
实践出真知,我们模拟一个业务场景:
公司决定,在员工的生日当天,以短信的形式给寿星发送“996福报快乐!”祝福语。
下面以两种方式编写代码实现业务功能
1.不遵循开闭原则,简单粗暴法
首先建了一个发送短信工具类,其中有一个发送短信方法SendMessage(string msg)
public class MessageUtil
{
public static void SendMessage(string msg)
{
Console.WriteLine(msg);
}
}
其中,我们的应用层调用
public class MessageService
{
public void Greeting(string msg)
{
MessageUtil.SendMessage(msg);
}
}
其中,业务调用处
MessageService _service = new MessageService();
/// <summary>
/// 给大家过节问候
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
//节日到了,以短信形式发送祝福
_service.Greeting("祝大家996福报快乐");
}
到目前看来还是挺好的,后续业务增加,节日祝福要增加邮件发送,好吧,说干就干
新增一个邮件工具类,这个没啥可多说的
public class EmailUtil
{
public static void SendEmail(string address, string contont)
{
//dosth
}
}
看原来的MessageService被修改为
public class MessageService
{
MessageType _type;
public MessageService(MessageType type)
{
_type = type;
}
public enum MessageType
{
Email,
Phone
}
public void Greeting(string msg)
{
switch (_type)
{
case MessageType.Email:
EmailUtil.SendEmail("alibaba@996.com", msg);
break;
case MessageType.Phone:
MessageUtil.SendMessage(msg);
break;
}
}
}
增加了消息类型的枚举,然后Greeting方法,新增了判断,这个时候原有的Greeting方法已经被修改了,哪怕是本来短信发送模块的代码并没有出任何问题,可此时,还是修改了该方法;维护过别人代码的人都知道,一个项目尤其是经过很多人手的项目,去修改一段原有的代码,风险是非常大的,你永远不知道前任会在哪个地方给你留下什么坑。
业务调用处修改为
MessageService _servicePhone = new MessageService(MessageType.Phone);
MessageService _serviceEmail = new MessageService(MessageType.Email);
/// <summary>
/// 给大家过节问候
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
//节日到了,以短信形式发送祝福
_servicePhone.Greeting("祝大家996福报快乐");
_serviceEmail.Greeting("祝大家996福报快乐");
}
分析:仅仅是增加了一项额外的节日问候的方式,我就新增了枚举类型、直接修改了原短信问候方法Greeting(),以后的实际需求往往是,还会新增邮件问候、微信问候、微博问候。。。需求是无止境的,每一次新增问候,如果按照这种方式,那势必每次新增需求都要>修改Greeting()方法,风险过大,虽然是新增的需求,按理说只应该测试新增的模块,但是由于你修改了原Greeting()方法,测试小伙伴需要回归测试节日问候这一整个大的模块,想想就很头疼
以上的编码方式,我可能有故意弄的很麻烦,可能实际代码中并不常见,这里只是为了与下面要讲的第二种方式形成鲜明的对比,从而说明开闭原则到底能为我们带来怎样的好处。
下面看看另一种设计
2、在最初的需求开始时,就考虑后续需求的变化情况,未雨绸缪。开闭原则
- 为发送服务,建立一个通用接口服务ISendService,所有发送服务都必须实现该接口
/// <summary>
/// 发送消息接口
/// 所有类型的发送服务都必须实现该接口
/// </summary>
public interface ISendService
{
public void SendMessage(string msg);
}
- 新增短信服务类,并实现ISendService
/// <summary>
/// 短信服务
/// </summary>
public class MessageService : ISendService
{
public void SendMessage(string msg)
{
MessageUtil.SendMessage(msg);
}
}
- 在业务调用处调用发送问候
ISendService _messageService = new MessageService();
/// <summary>
/// 节日问候
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
_messageService.SendMessage("996是我的福报,谢谢!");
}
- 接下来看,在如果保证不对短信服务做任何修改的情况下,新增邮件发送问候需求!
新增邮件服务类
/// <summary>
/// 邮件服务
/// </summary>
public class EmailService : ISendService
{
public void SendMessage(string msg)
{
EmailUtil.SendEmail("alibaba@996.com", msg);
}
}
- 业务调用处,修改:
新增一个邮件服务实例,完成对业务的扩展
ISendService _messageService = new MessageService();
ISendService _emailService = new EmailService();
/// <summary>
/// 节日问候
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
_messageService.SendMessage("996是我的福报,谢谢!");
_emailService.SendMessage("996是我的福报,谢谢!");
}
分析
新增邮件问候需求,是通过额外新增一个实现类EmailService来实现邮件发送,而不是在原有的MessageService上做新增或修改,这就是开闭原则,对原指的是MessageService,封闭未作修改,对扩展指的是ISendService,开放接口;以后无论是新增多少个发送服务,每次都是一样的流程,新增具体服务类,实现扩展接口ISendService
以上的伪代码只是为了体现OCP原则,其实这里违背了五大原则中另外一个依赖倒置原则,因为这里我既依赖了ISendService又依赖了MessageService和EmailService,在介绍依赖倒置的时候,我会引入MEF来摆脱对Service的依赖

浙公网安备 33010602011771号