设计模式の行为型模式(1)
我们经历了创建型模式,结构型模式,最终来到了设计模式的终点站:行为型模式.
以下我们讲的一些模式主要关注的是对象或类之间的行为和职责,避免导致混乱的代码逻辑徒增维护成本.
责任链模式
责任链根本上就是个链表结构,我们从链条的顶端一节一节往下找应该负责的节点,如果当前节点处理事物完毕,我们就不再继续寻找了。
比如一个斗地主游戏,我们要判断玩家选择的几张牌是不是符合规则,我们就要做一个卡牌规则责任链,一个一个规则判断下去。
public class Card
{
public int Number { get; set; }
}
public enum CardSuit
{
Spade, Heart, Club, Diamond
}
public abstract class CardRuleDetectionBase
{
public abstracct bool Handle(Card[] cards);
public CardRuleDetectionBase Next { get; set; }
}
现在我们创建一个纸牌的规则责任链
public class SingleCardRule : CardRuleDetection
{
public override bool Handle(Card[] cards)
{
if(cards.Length == 1)
return true;
return false;
}
}
public class DoubleCardRule : CardRuleDetection
{
public override bool Handle(Card[] cards)
{
if(cards.Length == 2 && cards[0].Number == cards[1].Number)
return true;
return false;
}
}
public class TripleCardRule : CardRuleDetection
{
public override bool Handle(Card[] cards)
{
if(cards.Length == e && cards[0].Number == cards[1].Number && cards[1].Number == cards[2].Number)
return true;
return false;
}
}
最后是这么使用的.
public class Program
{
public static void Main(string[] args)
{
var sr = new SingleCardRule();
sr.Next = new DoubleCardRule();
sr.Next.Next = new TripleCardRule();
var cards = new []
{
new Card() { Number = 2 },
new Card() { Number = 2 },
new Card() { Number = 2 }
};
Console.WriteLine(IsCardValid(cards, sr)); // true
cards = new []
{
new Card() { Number = 13 }
};
Console.WriteLine(IsCardValid(cards, sr)); // true
cards = new []
{
new Card() { Number = 3 },
new Card() { Number = 4 },
new Card() { Number = 5 },
new Card() { Number = 6 },
new Card() { Number = 7 }
};
Console.WriteLine(IsCardValid(cards, sr)); // false
}
public static void IsCardValid(Card[] cards, CardRuleDetection rule)
{
do
{
if(rule.Handle(cards))
return true;
rule = rule.Next;
}
while(rule != null);
return false;
}
}
这种链的模式我并不是很喜欢, 数组不也行嘛..
public class Program
{
public static void Main(string[] args)
{
var rules = new CardRuleDetection[]
{
new SingleCardRule(),
new DoubleCardRule(),
new TripleCardRule()
};
var cards = new []
{
new Card() { Number = 2 },
new Card() { Number = 2 },
new Card() { Number = 2 }
};
Console.WriteLine(IsCardValid(rules, cards)); // true
}
public static void IsCardValid(CardRuleDetection[] rules, Card[] cards)
{
foreach(var r in rules)
{
if(r.Handle(cards))
return true;
}
return false;
}
}
命令模式
命令模式可以解决我们应对复杂多变的请求的情况.比如一个公司的行政部要处理的是非常的繁杂,如果行政部门只有一个人,并且他做的事情都很不确定,请假报销流程等等流程变化之后会让那个人很头痛.
理想状态下我们应该是这样做的:每件事情由一个熟练工干,如果这件事的流程变化的话,就把这个人开掉,再招熟悉新的流程的人来做.然后行政部门以订单形式处理事务,每个事物包含一个请求内容.这样就能高效的处理任何事情了.
不过这种理念在现实中肯定是不可能实现的了,但是在程序世界里是完全可以实现的.
首先我们定义一个订单处理流程:
public interface IOrderCommand<T> where T : Order
{
string OrderName { get; }
void Handle(T request);
}
public class Order
{
public Order(string orderName)
{
OrderName = orderName;
}
public string OrderName { get; private set; }
}
public class OrderDispatch
{
private List<IOrderCommand> commands = new List<IOrderHandler>();
public void RegisterCommand(IOrderCommand h)
{
commands.Add(h);
}
public void Handle(Order order)
{
foreach(var c in commands)
{
if(o.OrderName == c.OrderName)
{
c.Handle(o);
break;
}
}
}
}
我们模拟一下平时我们公司的行政事务:
//快递通知业务
public class PackageOrder : Order
{
public PackageOrder() : base("package") { }
public Package Package { get; set; }
}
public class PackageNotifyCommand<PackageOrder> : IOrderCommand
{
public PackageNotifyCommand(QQGroup group)
{
this.group = group;
}
private QQGroup group;
public string OrderName { get { return "package"; } }
public void Handle(PackageOrder order)
{
var qqPersion = group.FindName(order.Package.Receiver);
if(qqPerson != null)
qqPerson.SendText("前台有你的快递过来拿一下~");
}
}
//活动报销业务
public class ReimbursementOrder : Order
{
public ReimbursementOrder() : base("reimbursement") { }
public Person Person { get; set; }
public String Type { get; set; }
public float Amount { get; set; }
}
public class ReimbursementCommand<ReimbursementOrder> : IOrderCommand
{
public ReimbursementCommand(Cashier cashier)
{
this.cashier = cashier;
}
private Cashier cashier;
public void Handle(ReimbursementOrder order)
{
if(order.Type == "food")
return;
var money = cashier.Request(order.Amount, order.Person.Name + ": "+order.Type);
order.Person.Give(money);
}
}
最后模拟一下应用场景
public void Main()
{
var dispatch = new OrderDispatch();
var pnc = new PackageNotifyCommand(new QQGroup("XXX公司群"));
var rc = new ReimbursementCommand(new Cashier("韩梅梅"));
dispatch.RegisterCommand(pnc);
dispatch.RegisterCommand(rc);
var pOrder = new PackageOrder();
var package = new Package() { Receiver = "李雷", content = "肥皂" };
pOrder.Package = package;
dispatch.Handle(pOrder);
var rOrder = new ReimbursementOrder();
rOrder.Person = new Person () { Name = "Jim" }
rOrder.Type = "texi";
rOrder.Amount = 20;
dispatch.Handle(rOrder);
}
如果我们回到最原始的写法应该是怎么样的?
public class AdminDepartment
{
public QQGroup group;
public Cashier cashier;
public void HandlePackage(Package p)
{
group.FindName(p.Receiver).SendText("快递!");
}
public void HandleReimbursement(Person p, string type, float amount)
{
if(type=="food" || type == "texi")
return;
var money = cashier.Request(amount / 2, p.Name + ": "+type);
order.Person.Give(money);
}
}
以上方法严重违反OCP和SRP。试想以下如果需要处理的事物超过20个怎么办?
总结以下:什么时候我们会用到命令方法?
- 需要处理一些复杂事物。
- 这些复杂事物一般涉及到其他类型的事物处理。(比如上面例子中的QQ群和出纳人员)
- 这些复杂事物有一定的关联性。(都是行政部门的事情)
- 这些复杂事物的数量和内容不确定。
使用命令模式的时候要注意:
Command类型自己是不包含任何其他具体功能性代码的,即:除了上面接口中的Handle()方法以外不应该有其他开放方法。
解释器模式
略,个人印象好像没怎么用过。意思大概是我给定一种数据(可以是字符串也可以是其他自定义数据),最终通过解释器来给出一种答案。
应用场景:
- 计算器:输入一个算式,解释出最终答案
- 翻译软件: 输入一个语言的句子, 解释出另一种语言的意思。
- 自定义编程语言: 输入自定义编程语言的代码, 给出最终执行结果。有些语言比如python或者ruby等都有自带命令行解释器,Chrome浏览器也自带javascript解释器。
迭代器模式
何谓迭代器模式?所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。在实际的开发过程中,我们可能需要针对不同的需求,可能需要以不同的方式来遍历整个整合对象,但是我们不希望在聚合对象的抽象接口层中充斥着各种不同的便利操作。这个时候我们就需要这样一种东西,它应该具备如下三个功能:
能够便利一个聚合对象。
我们不需要了解聚合对象的内部结构。
能够提供多种不同的遍历方式。
迭代器这玩意C#自带,我们看看C#迭代器的定义是什么吧:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
/// <summary>Gets the current element in the collection.</summary>
/// <returns>The current element in the collection.</returns>
object Current
{
get;
}
/// <summary>Advances the enumerator to the next element of the collection.</summary>
/// <returns>true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
bool MoveNext();
/// <summary>Sets the enumerator to its initial position, which is before the first element in the collection.</summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
void Reset();
}
可以从源码看到, IEnumerator只给了使用者顺序访问该对象所有成员的功能,仅此而已.
中介模式
中介这两个字立马让我先到了房屋中介。。程序开发中的中介和房屋中介有一种情形是类似的:未来房客和房东之间不方便见面或沟通(比如空闲事件不一致,语言不通的问题),由中介代替双方完成沟通。
程序开发中我们也经常会遇到一个问题: A大系统的中的一个子功能要和B大系统中的一个子功能需要互相沟通。
但是A大功能所在的环境和B大功能完全没有任何交集。A无法引用到B,或者说A不应该引用到B。这时候需要用到中介模式。
举个例子,雪糕工厂里面有7个大阶段,每个阶段理论上互相独立,互不影响,但是在衔接的时候难免出现一些相互都要处理的事物。
一个盘子里的碗由于各种原因导致不是直接传递给下一个阶段,而是下一个阶段直接新建一块碗。这就导致了在观感上出现了2块碗的情况。于是下一个阶段要通知上一个阶段把旧的碗给隐藏掉。
由于两个阶段是互相独立的,他们直接无法轻易地(或者说是美观地)引用到对方,于是我们使用了一个叫通知中心的类。
设:上个阶段为A,下个阶段为B,通知中心为C。A监听C未来将要发出的“隐藏碗”的消息,并在收到这个消息以后将碗给隐藏起来;B在合适的事件发送“隐藏碗”的消息给C;C收到消息后发现A需要这个消息,于是C调用了A的处理方法。A成功将碗隐藏了起来。
于是A和B之间的通讯就这么产生了。然而他们之间还是没有直接的联系。
总结: 使用中介模式的情况为A和B无法或无法优美的引用到对方,这时需要第三者插足来为A和B完成互相之间的沟通。