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 来实现。所有的……
阅读全文
2010-08-20 12:17:37
摘要:策略模式: 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户而变化。
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
另外策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
最有一点就是,在我们开始编程的时候不得不在客户端的代码中为了判断用哪一个算法计算而使用switch条件分支,这也是很正常的。因为,当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
示例
假设有这样一个需求,希望写一个小程序,实现对一个文件进行排序的功能。文件中只包含整型数,并且,相邻的数字通过逗号来区隔。如果由你来编写这样一个小程序,你会如何来实现呢?
你可能会说,这不是很简单嘛,只需要将文件中的内容读取出来,并且通过逗号分割成一个一个的数字,放到内存数组中,然后编写某种排序算法(比如快排),或者直接使用编程语言提供的排序函数,对数组进行排序,最后再将数组中的数据写入文件就可以了。
但是,如果文件很大呢?比如有 10GB 大小,因为内存有限(比如只有 8GB 大小),我们没办法一次性加载文件中的所有数据到内存中,这个时候,我们就要利用外部排序算法。
如果文件更大,比如有 100GB 大小,我们为了利用 CPU 多核的优势,可以在外部排序的基础之上进行优化,加入多线程并发排序的功能。
如果文件非常大,比如有 1TB 大小,即便是单机多线程排序,这也算很慢了。这个时候,我们可以使用真正的 MapReduce 框架,利用多机的处理能力,提高排序的效率。
解决思路讲完了,不难理解。接下来,我们看一下,如何将解决思路翻译成代码实现。
abstract class SortAlg
{
……
阅读全文
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)
{
……
阅读全文
2010-08-11 11:18:07
摘要:外观模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式是一种很简单的设计模式,但是它却非常常用。对于面向对象有一定基础的朋友,即使没有听说过外观模式,也完全有可能在很多时候使用它,因为它完美地体现了依赖倒置原则和迪米特法则的思想。对于一个复杂的系统它的底层逻辑可能相互之间充满依赖,前端程序调用起来可能会出现逻辑不清晰,类和类之间相互耦合。比如我们给学校图书馆开发一套系统,系统可以为用户提供续借图书、预借图书、提交罚款等功能,然后每一个功能的实现逻辑可能会很复杂。我们那预借图书这个功能来举例:用户在线对某本书籍发出预借请求,系统收到请求后。首先需要判断该用户是否拥有预借权限,然后此本书是否可以被预计,是否达到最大预计次数,如果全部通过则预借成功。对于最底层的逻辑来说,判断预借权限、是否在允许的预借次数下以及处理预借请求可能在不同的类的不同方法中实现,想想如果对于客户程序,它处理预借请求需要调用多个方法,这样前台代码和后台逻辑就紧密的耦合在一起,如果底层逻辑发生变化,前台代码可能也要跟着发生变化,这样的设计将是很脆弱的。但是如果我们在前台代码与后台逻辑中间引入一个外观类,这个外观类帮助我们调用底层的逻辑步骤,而对于前台代码来说它只需要调用外观类中对应的一个续借方法得到返回值就可以了,这样前后台的代码就没有耦合在一起了,底层逻辑的变化也不会影响到前台客户的代码调用。
此模式使用起来比较简单,只需要多添加一个类就可以了。这里我就不再列出详细代码了。
那在什么时候使用外观模式呢?我们将系统设计分3个阶段来说明。首先在设计初级阶段,应该要有意识的将不同的两个层分离,比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层与表示层的层与层之间建立外观,这样可以为复杂的子系统提供一个简单的接口,使得耦合大大降低。其次,在开发阶段,子系统往往因为不断的重构而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但外部调用它们的用户程序却带来了困难,增加一个外观可以提供一个简单的接口,减少它们之间的依赖。第三,在维护一个遗留的大型系统时,这个系统可能已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖于它,此时使用外观模式也是非常合适的。
结构图
几个要点
从……
阅读全文
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 =……
阅读全文
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]……
阅读全文
2010-07-28 11:08:36
摘要:适配器模式:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
// 类适配器: 基于继承
public interface ITarget
{
void F1();
void F2();
void Fc();
}
public class Adaptee
{
public void Fa()
{
//...
}
public void Fb()
{
//...
}
public void Fc()
{
//...
}
}
public class Adaptor : Adaptee, ITarget
{
public void F1()
{
base.Fa();
}
public void F2()
{
//...重新实现F2()...
}
// 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}
// 对象适配器:基于组合
public interface ITarget
{
void F1();
void F2();
void Fc();
}
public class Adaptee
{
public void Fa()
{
//...
}
public void Fb()
{
//...
}
public void Fc()
{
//...
}
}
public class Adaptor : ITarget
{
private Adaptee adaptee;
public Adaptor(Adaptee adaptee)
{
this.adaptee = adaptee;
}
pub……
阅读全文
2010-07-24 15:18:34
摘要:装饰模式:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
Component是定义一个对象接口,可以给这些对象动态地添加职责。ComcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator,装饰抽象类,继承了Component,从外部来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。至于ComponentDecorator就是具体的装饰对象,起到给Component添加职责的功能。
装饰模式是结构型模式的巅峰设计,之所以这么说,是因为它不仅使用了继承,而且还使用了组合。这是一种比较烧脑的设计模式。我们来看一下装饰模式的代码实现:
abstract class Component
{
public abstract void Operation();
}
class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine(具体对象的操作);
}
}
abstract class Decorator : Component //继承Component
{
protected Component component; //组合Component
public void SetComponent(Component component) //设置Component
{
this.component = component;
}
public override void Operation() //重写Operation(),实际执行的是Component的Operation()
{
if (component != null)
{
component.Operation();
}
}
}
class ConcreteDecoratorA : Decorator
{
private string adde……
阅读全文
2010-07-17 21:39:53
摘要:桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern。这个模式可以说是 23 种设计模式中最难理解的模式之一了。
当然,这其中“最纯正”的理解方式,当属 GoF 的《设计模式》一书中对桥接模式的定义。毕竟,这 23 种经典的设计模式,最初就是由这本书总结出来的。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”
关于桥接模式,很多书籍、资料中,还有另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于“组合优于继承”设计原则。
桥接模式的应用举例
比如一款手机可以安装不同的应用,而手机又有多种不同的品牌。如果将手机按品牌分类,其结构图如下:
按软件分类实现结构图
由于实现的方式有多种,桥接模式的核心意图就是把这些实现独立出来,让它们各自地变化。这就使得每种实现的变化不会影响其他实现,从而达到应对变化的目的。
代码实现:
//手机软件
abstract class HandsetSoft
{
public abstract void Run();
}
//手机游戏
class HandsetGame : HandsetSoft
{
public override void Run()
{
Console.WriteLine(运行手机游戏);
}
}
//手机通讯录
class HandsetAddressList : HandsetSoft
{
public override void Run()
{
Console.WriteLine(运行手机通讯录);
}
}
//手机品牌
abstract class HandsetBrand
{
protected HandsetSoft soft;
//设置手机软件
public void S……
阅读全文
2010-07-13 13:13:36
摘要:代理模式是很常用的一种结构性模式,我们先来看一下它的描述和机构图:
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
代理模式的原理与实现
在不改变原始类(或叫被代理类)的情况下,通过引入代理类来给原始类附加功能。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。
动态代理的原理与实现
静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了维护成本和开发成本。对于静态代理存在的问题,我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
代理模式的应用场景
业务系统的非功能性需求开发
代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。
代理模式在RPC中的应用
实际上,RPC 框架也可以看作一种代理模式,GoF 的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
代理模式在缓存中的应用
如果是基于框架来开发的话,那就可以在 AOP 切面中完成接口缓存的功能。
阅读全文