系列文章的目录:https://blog.csdn.net/hjkl950217/article/details/89490709
记住:设计模式重理解,轻照搬
业务背景
想象一下你在做一个简单的电商系统,电商系统中核心一定会有商品、订单这2个东西,而商品必然会有数量、品牌、商品描述信息这些方面。而商品上架之后才会有订单这个东西,那么我们第一件事就是进行商品管理。一个简单的商品创建模型如下:
随着业务变化,你发现商品的不同品牌之间有着关联。比如AMD(中国)和AMD(美国)可以看做一个公司,但在具体商品上又要区分开,记录上主公司与分公司的关系。那么我们的模型发生了一点变化:
设计
在开始之前,建议先停下来想想,如果是你,你会如何设计? 然后再向下看。
观察上面整理出来的业务模型发现特点:一环扣一环,审批处如果审批不过会退回到起始点。
我们这里简单对比下传统思路与责任链模式:
- 如果用传统的业务思想去开发:可能会是一个方法中调用不同的子方法,然后判断每个子方法的返回结果并做出不同的处理方式。随着业务变化,这个方法会变的越来越庞大,修改一处都有可能影响整套代码。
- 如果使用责任链模式:把每一环拆开进行开发,然后在使用处组装成我们的业务处理链。这样在开发时我们并不关心整体如何,而是我们这一处地方应该如何。并且业务变化时,中断整体调用,由具体的环节给出对应的方案。
下面是实现的代码,你也可以下载查看 1。
首先是我们的实体类:
/// <summary>
/// 商品信息
/// </summary>
public class ItemInfo
{
/// <summary>
/// 商品名
/// </summary>
public string Name { get; set; }
}
设计一个处理者接口(也可以使用父类):
/// <summary>
/// 商品创建处理者
/// </summary>
public interface IItemCreateHandler
{
/// <summary>
/// 下一个处理者
/// </summary>
IItemCreateHandler NextHandler { get; }
/// <summary>
/// 设置下一个处理者
/// </summary>
/// <param name="handler"></param>
void SetNextHandler(IItemCreateHandler handler);
/// <summary>
/// 处理商品创建
/// </summary>
/// <param name="itemInfo"></param>
bool HandleItemCreate(ItemInfo itemInfo);
}
实现的代码就很简单了:
- 处理录入的信息
- 审批
- 持久化到数据库
代码:
/// <summary>
/// 商品信息录入处理器
/// </summary>
public class ItemInfoEntryHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
itemInfo = itemInfo ?? this.GetItemInfoFromUI();
return this.NextHandler.HandleItemCreate(itemInfo);
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
//模拟从UI上获取数据
private ItemInfo GetItemInfoFromUI()
{
return new ItemInfo() { };
}
}
/// <summary>
/// 商品创建审批处理者
/// </summary>
public class ItemCreateApproveHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
//做一些检查,比如名字、信息是否匹配等
//实际中,这些检查是由管理者在UI上审核的,这里只需要接受审核结果即可
if (string.IsNullOrWhiteSpace(itemInfo.Name))
{
Console.WriteLine("商品名不能为空,请重新输入");
return false;
}
else
{
return this.NextHandler.HandleItemCreate(itemInfo);
}
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
}
/// <summary>
/// 商品信息持久化处理者
/// </summary>
public class ItemInfoPersistenceHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
//把数据写到数据库中,并且不再调用NextHandler
return true;
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
}
然后由调用者组装:
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("==开始创建商品==");
IItemCreateHandler handler1 = new ItemInfoEntryHandler();
IItemCreateHandler handler2 = new ItemCreateApproveHandler();
IItemCreateHandler handler3 = new ItemInfoPersistenceHandler();
handler1.SetNextHandler(handler2);
handler2.SetNextHandler(handler3);
bool createSusess = false;
while (!createSusess)
{
Console.Write("请输入商品名:");
string itemName = Console.ReadLine();
createSusess = handler1.HandleItemCreate(new ItemInfo() { Name = itemName });
}
Console.WriteLine("==商品创建结束==");
Console.ReadLine();
}
}
至此,我们的第一阶段已经设计完成。
应对增加处理环节
上面我们看到,业务变化后还增加了一个映射环节。只需要加一个类,并且简单修改下调用处的代码即可搞定:
增加映射代码:
/// <summary>
/// 商品信息映射处理者
/// </summary>
internal class ItemInfoMapHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
//在这个类中记录下子公司与分司的关系
return this.NextHandler.HandleItemCreate(itemInfo);
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
}
修改调用代码:
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("==开始创建商品==");
IItemCreateHandler handler1 = new ItemInfoEntryHandler();
IItemCreateHandler handler2 = new ItemCreateApproveHandler();
IItemCreateHandler handler3 = new ItemInfoPersistenceHandler();
IItemCreateHandler newHandler = new ItemInfoMapHandler();//新加代码
handler1.SetNextHandler(newHandler);//修改代码
newHandler.SetNextHandler(handler2);//修改代码
handler2.SetNextHandler(handler3);
bool createSusess = false;
while (!createSusess)
{
Console.Write("请输入商品名:");
string itemName = Console.ReadLine();
createSusess = handler1.HandleItemCreate(new ItemInfo() { Name = itemName });
}
Console.WriteLine("==商品创建结束==");
Console.ReadLine();
}
}
优缺点分析
代码写了一些了,我们来分析下目前的优点和缺点吧!
优点:
- 通过代码能很好的看出我们的业务模型。
- 代码耦合度低
缺点:
- 需要使用者自己组装并调用,不清真。
- 中间环节出现异常时,可以中断时还好。但如果是返回到非起始点,怎么办呢?
针对缺点中的第1点,可以使用一个帮助类或简单工厂封装一下就好了。
针对第2点,当A环节需要跳到B环节时,这里的对象也只有2个。 那么我们这里可以在A环节的代码中手动new一个B环节的代码来搞定。如果你觉得这种比较Low,也可以使用其它方式,核心就在于A环节调用B环节而已。
结合建造者模式
上面说了这么多,都是在讲责任链模式和它的应用,在某些层面可以与建造者模式很好的结合。
看下面的分析:
发现有3个固定步骤:
- 获取数据
- 检查
- 持久化 ,并且顺序也是固定的。
这是不是可以改造成建造者模式呢? 但现在的这个业务不一定会改造成建造者模式,因为需求变化还不足以让我们添加一个新的模式进来!
但这种场景就适合了:
- 针对不同的商品、有不同的审批环节。比如:普通的商品由操作员查看、虚拟类商品由系统查看卖家的资格自动审批、奢侈品需要主管审批等等。
- 获取数据的来源有多种。比如:从文件中批量获取、用爬虫在网络上爬、UI上手动输入等等
- 持久化到其它的数据库
- 其它…
不管场景怎么变,但我们的核心业务模型没有变化,只有具体的实现变了。当你有这种需求的时候,就可以结合建造者模式去快速应对需求变化了。
如果你想详细了解建造者模式,可去系列教程目录查找。
总结
责任链模式强调:环节或模块之间的顺序调用关系。.net core中的中间件思想、http处理管道都可以看成这种思想的一种延伸。
建造者模式强调:执行的代码是固定顺序,固定步骤,但实现的细节多变。工厂模式就可以看成一个单步骤的建造者模式。