设计模式之行为类模式01

设计模式の行为型模式(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个怎么办?

总结以下:什么时候我们会用到命令方法?

  1. 需要处理一些复杂事物。
  2. 这些复杂事物一般涉及到其他类型的事物处理。(比如上面例子中的QQ群和出纳人员)
  3. 这些复杂事物有一定的关联性。(都是行政部门的事情)
  4. 这些复杂事物的数量和内容不确定。

使用命令模式的时候要注意:

Command类型自己是不包含任何其他具体功能性代码的,即:除了上面接口中的Handle()方法以外不应该有其他开放方法。

解释器模式

略,个人印象好像没怎么用过。意思大概是我给定一种数据(可以是字符串也可以是其他自定义数据),最终通过解释器来给出一种答案。

应用场景:

  • 计算器:输入一个算式,解释出最终答案
  • 翻译软件: 输入一个语言的句子, 解释出另一种语言的意思。
  • 自定义编程语言: 输入自定义编程语言的代码, 给出最终执行结果。有些语言比如python或者ruby等都有自带命令行解释器,Chrome浏览器也自带javascript解释器。

迭代器模式

何谓迭代器模式?所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。在实际的开发过程中,我们可能需要针对不同的需求,可能需要以不同的方式来遍历整个整合对象,但是我们不希望在聚合对象的抽象接口层中充斥着各种不同的便利操作。这个时候我们就需要这样一种东西,它应该具备如下三个功能:

  1. 能够便利一个聚合对象。

  2. 我们不需要了解聚合对象的内部结构。

  3. 能够提供多种不同的遍历方式。

迭代器这玩意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完成互相之间的沟通。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

  • 如果需要原文档(因文体限制,部分表格无法呈现)请联系QQ1769090563 本文由中医仲景协会整理收集 《内经选...
    陶墨阅读 34,142评论 0 33
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,559评论 18 139
  • 作为一个南北“混血”,我自小就生活在南北文化的交汇中心。正因为这样,我有幸能拥有来自南北两个不同地域的生活体验,从...
    烟雨阙阅读 384评论 2 3
  • 翻到此时的bingo君的表情。 翻译自日本作家池井户润的小说《銀翼のイカロス》是半泽直树系列小说的第四部。伊卡洛斯...
    万事bingo君阅读 6,211评论 2 33