Spiga

分类为编程思维的文章

模式开发之旅(23):状态模式

2010-09-13 17:08:46

摘要:**状态模式:**当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。 在实际的软件开发中,状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。从这一点上来看,它有点组合模式。 状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有多种,除了状态模式,比较常用的还有分支逻辑法和查表法。今天,我们就详细讲讲这几种实现方式,并且对比一下它们的优劣和应用场景。 什么是有限状态机? 有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。 “超级马里奥”游戏不知道你玩过没有?在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。 在不同的游戏情节下,各个形态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加 100 积分。 实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。 接下来写一个骨架代码,如下所示。其中,ObtainMushRoom()、ObtainCape()、ObtainFireFlower()、MeetMonster() 这几个方法,能够根据当前的状态和事件,更新状态和增减积分。 public enum State {   SMALL,   SUPER,   FIRE,   CAPE } public class MarioStateMachine {   private int score;   private State currentState;   publ…… 阅读全文

模式开发之旅(22):中介模式

2010-09-09 10:02:25

摘要:中介模式:定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。 中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。 提到中介模式,有一个比较经典的例子不得不说,那就是航空管制。 为了让飞机在飞行的时候互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,这就需要时刻跟其他飞机通信。飞机通信形成的通信网络就会无比复杂。这个时候,我们通过引入“塔台”这样一个中介,让每架飞机只跟塔台来通信,发送自己的位置给塔台,由塔台来负责每架飞机的航线调度。这样就大大简化了通信网络。 刚刚举的是生活中的例子,我们再举一个跟编程开发相关的例子。这个例子与 UI 控件有关,算是中介模式比较经典的应用,很多书籍在讲到中介模式的时候,都会拿它来举例。 假设我们有一个比较复杂的对话框,对话框中有很多控件,比如按钮、文本框、下拉框等。当我们对某个控件进行操作的时候,其他控件会做出相应的反应,比如,我们在下拉框中选择“注册”,注册相关的控件就会显示在对话框中。如果我们在下拉框中选择“登陆”,登陆相关的控件就会显示在对话框中。 我们按照中介模式,各个控件只跟中介对象交互,中介对象负责所有业务逻辑的处理。 public interface Mediator { void HandleEvent(Component component, string @event); } public class LandingPageDialog : Mediator { private Button loginButton; private Button regButton; private Selection selection; private Input usernameInput; private Input passwordInput; private Input repeatedPswdInput; private Text hin…… 阅读全文

模式开发之旅(21):迭代器模式

2010-09-04 19:52:34

摘要:迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。 迭代器模式,也叫游标模式。它用来遍历集合对象。这里说的“集合对象”,我们也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如,数组、链表、树、图、跳表。 一个完整的迭代器模式,一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。容器中需要定义 Iterator() 方法,用来创建迭代器。迭代器接口中需要定义 HasNext()、CurrentItem()、Next() 三个最基本的方法。容器对象通过依赖注入传递到迭代器类中。 遍历集合一般有三种方式:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。相对于 for 循环遍历,利用迭代器来遍历有下面三个优势: 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可; 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一; 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。 我们看来一下迭代器模式的实现: abstract class Iterator { public abstract object First(); public abstract object Next(); public abstract bool HasNext(); public abstract object CurrentItem(); } abstract class Aggregate { public abstract Iterator CreateIterator(); } class ConcreteIterator : Iterator { private ConcreteAggregate aggregate; private int current = 0; public ConcreteIterator(ConcreteAggregate agg…… 阅读全文

模式开发之旅(20):职责链模式

2010-08-30 23:26:16

摘要:之前介绍了模板模式、策略模式。今天来介绍职责链模式,这三种模式具有相同的作用:复用和扩展,在实际的项目开发中比较常用,特别是框架开发中,我们可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源码的情况下,基于扩展点定制化框架的功能。 职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 关于职责链模式,我们先来看看它的代码实现。结合代码实现,你会更容易理解它的定义。职责链模式有多种实现方式,比较常用的2种: 1. 利用链表实现 实现方式如下所示。其中,Handler 是所有处理器类的抽象父类,Handle() 是抽象方法。每个具体的处理器类(HandlerA、HandlerB)的 Handle() 方法的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用 successor.Handle())。HandlerChain 是处理器链,从数据结构的角度来看,它就是一个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。 public abstract class Handler { protected Handler successor = null; public void SetSuccessor(Handler successor) { this.successor = successor; } public abstract void Handle(); } public class HandlerA : Handler { public override void Handle() { bool handled = false; //... if (!handled successor != null) { successor.Handle(); } } } public class HandlerB : Handler { public override void H…… 阅读全文

模式开发之旅(19):模板方法模式

2010-08-24 22:07:31

摘要:**模板方法模式:**在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。 这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。 原理很简单,代码实现就更加简单,下面是一个示例代码: public abstract class AbstractClass {   public void TemplateMethod()   {       //...       method1();       //...       method2();       //...   }   protected abstract void Method1();   protected abstract void Method2(); } public class ConcreteClass1 : AbstractClass {   protected override void Method1()   {       //...   }   protected override void Method2()   {       //...   } } ​ public class ConcreteClass2 : AbstractClass {   protected override void Method1()   {       //...   }   protected override void Method2()   {       //...   } } ​ AbstractClass demo = new ConcreteClass1(); demo.TemplateMethod(); 模板模式作用 1. 复用 模板模式把一个算法中不变的流程抽象到父类的模板方法 TemplateMethod() 中,将可变的部分 Method1()、Method2() 留给子类 ContreteClass1 和 ContreteClass2 来实现。所有的…… 阅读全文

模式开发之旅(18):策略模式

2010-08-20 12:17:37

摘要:策略模式: 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户而变化。 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。 另外策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。 最有一点就是,在我们开始编程的时候不得不在客户端的代码中为了判断用哪一个算法计算而使用switch条件分支,这也是很正常的。因为,当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。 策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。 示例 假设有这样一个需求,希望写一个小程序,实现对一个文件进行排序的功能。文件中只包含整型数,并且,相邻的数字通过逗号来区隔。如果由你来编写这样一个小程序,你会如何来实现呢? 你可能会说,这不是很简单嘛,只需要将文件中的内容读取出来,并且通过逗号分割成一个一个的数字,放到内存数组中,然后编写某种排序算法(比如快排),或者直接使用编程语言提供的排序函数,对数组进行排序,最后再将数组中的数据写入文件就可以了。 但是,如果文件很大呢?比如有 10GB 大小,因为内存有限(比如只有 8GB 大小),我们没办法一次性加载文件中的所有数据到内存中,这个时候,我们就要利用外部排序算法。 如果文件更大,比如有 100GB 大小,我们为了利用 CPU 多核的优势,可以在外部排序的基础之上进行优化,加入多线程并发排序的功能。 如果文件非常大,比如有 1TB 大小,即便是单机多线程排序,这也算很慢了。这个时候,我们可以使用真正的 MapReduce 框架,利用多机的处理能力,提高排序的效率。 解决思路讲完了,不难理解。接下来,我们看一下,如何将解决思路翻译成代码实现。 abstract class SortAlg { …… 阅读全文

模式开发之旅(17):观察者模式

2010-08-16 15:30:51

摘要:观察者模式:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。 观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。 一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。 观察者模式是一个比较抽象的模式,根据不同的应用场景和需求,有完全不同的实现方式。现在,我们来看其中最经典的一种实现方式。 public interface Subject { void RegisterObserver(Observer observer); void RemoveObserver(Observer observer); void NotifyObservers(Message message); } public interface Observer { void Update(Message message); } public class ConcreteSubject : Subject { private IListObserver observers = new ListObserver(); public void RegisterObserver(Observer observer) { observers.Add(observer); } public void RemoveObserver(Observer observer) { …… 阅读全文

模式开发之旅(16):外观模式

2010-08-11 11:18:07

摘要:外观模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 外观模式是一种很简单的设计模式,但是它却非常常用。对于面向对象有一定基础的朋友,即使没有听说过外观模式,也完全有可能在很多时候使用它,因为它完美地体现了依赖倒置原则和迪米特法则的思想。对于一个复杂的系统它的底层逻辑可能相互之间充满依赖,前端程序调用起来可能会出现逻辑不清晰,类和类之间相互耦合。比如我们给学校图书馆开发一套系统,系统可以为用户提供续借图书、预借图书、提交罚款等功能,然后每一个功能的实现逻辑可能会很复杂。我们那预借图书这个功能来举例:用户在线对某本书籍发出预借请求,系统收到请求后。首先需要判断该用户是否拥有预借权限,然后此本书是否可以被预计,是否达到最大预计次数,如果全部通过则预借成功。对于最底层的逻辑来说,判断预借权限、是否在允许的预借次数下以及处理预借请求可能在不同的类的不同方法中实现,想想如果对于客户程序,它处理预借请求需要调用多个方法,这样前台代码和后台逻辑就紧密的耦合在一起,如果底层逻辑发生变化,前台代码可能也要跟着发生变化,这样的设计将是很脆弱的。但是如果我们在前台代码与后台逻辑中间引入一个外观类,这个外观类帮助我们调用底层的逻辑步骤,而对于前台代码来说它只需要调用外观类中对应的一个续借方法得到返回值就可以了,这样前后台的代码就没有耦合在一起了,底层逻辑的变化也不会影响到前台客户的代码调用。 此模式使用起来比较简单,只需要多添加一个类就可以了。这里我就不再列出详细代码了。 那在什么时候使用外观模式呢?我们将系统设计分3个阶段来说明。首先在设计初级阶段,应该要有意识的将不同的两个层分离,比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层与表示层的层与层之间建立外观,这样可以为复杂的子系统提供一个简单的接口,使得耦合大大降低。其次,在开发阶段,子系统往往因为不断的重构而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但外部调用它们的用户程序却带来了困难,增加一个外观可以提供一个简单的接口,减少它们之间的依赖。第三,在维护一个遗留的大型系统时,这个系统可能已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖于它,此时使用外观模式也是非常合适的。 结构图    几个要点 从…… 阅读全文

模式开发之旅(15):组合模式

2010-08-06 15:24:29

摘要:组合模式: 将对象组合成树型结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。 组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现。使用组合模式的前提在于,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景也比较局限,它并不是一种很常用的设计模式。 利用组合模式实现DDD中的规约 //规约基类 public interface ISpecificationTEntity where TEntity : class { ExpressionFuncTEntity, bool SatisfiedBy(); } //组合模式基类 public abstract class CompositeSpecificationTEntity where TEntity : class { // Check if this specification is satisfied by a specific expression lambda public abstract ExpressionFuncTEntity, bool SatisfiedBy(); public abstract ISpecificationTEntity LeftSideSpecification { get; } public abstract ISpecificationTEntity RightSideSpecification { get; } } public sealed class AndSpecificationT : CompositeSpecificationT where T : class { private ISpecificationT _RightSideSpecification = null; private ISpecificationT _LeftSideSpecification =…… 阅读全文

模式开发之旅(14):享元模式

2010-07-31 11:55:43

摘要:享元模式的原理 所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。具体来讲,当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段),提取出来设计成享元,让这些大量相似对象引用这些享元。 享元模式: 运用共享技术有效地支持大量细颗度的对象。 享元模式的实现 享元模式的代码实现非常简单,主要是通过工厂模式,在工厂类中,通过一个 Map 或者 List 来缓存已经创建好的享元对象,以达到复用的目的。 比如我们要实现一个文本编辑器,假设这个编辑器只支持26个大写英文字母的输入。编辑器可以编写成千上万的内容,如果这些内容都保存在内存中就会造成内存过多的分配。而当前整个文本编辑器是由26个大写字母的任意组成的,这时我们就可以用享元模式,最多分配26个字母的内存,编辑器共享分配的内存既可。 public class FlyweightFactory { private static DictionaryWordType, BaseWord _BaseWordDictionary = new DictionaryWordType, BaseWord(); private static object GetWord_Lock = new object(); public static BaseWord GetWord(WordType wordType) { BaseWord baseWord = null; if (_BaseWordDictionary.ContainsKey(wordType))//双if+lock { baseWord = _BaseWordDictionary[wordType]; } else { lock (GetWord_Lock) { if (_BaseWordDictionary.ContainsKey(wordType)) { baseWord = _BaseWordDictionary[wordType]…… 阅读全文