Creational:创建型
Abstract Factory:抽象工厂 Kit 重要
- 封装变化:产品对象家族
- 意图:提供一个创建一些列相关或相互依赖对象的接口,而无需指定它们具体的类
- 适用性:
- 一个系统要独立它的产品的创建、组合和表示
- 一个系统要由多个产品系列中的一个来配置
- 要强调一个系列相关的产品对象的设计以便进行联合使用
- 提供一个产品库,但只想显示它们的接口而不是实现
- 参与者职责:
- AbstractFactory:声明一个创建抽象产品对象的操作接口。
- ConcreteFactory:实现创建具体产品对象的操作。
- AbstractProduct:为一类产品对象声明一个接口
- ConcreteProduct:定义一个将被相应的具体工厂创建的产品对象
- Client:仅使用AbstractFactory和AbstractProduct类声明的接口。
- 协作:
- 在运行时创建ConcreteFactory。具体工厂具有特定实现的产品,创建不同的产品对象,应使用不同的具体工厂
- AbstractFactory将产品对象的创建延迟到ConcreteFactory子类
- 优势劣势:
- 具体工厂一般是单例模式。
- 如果要增加一个产品需要改变抽象接口。解决方案是给创建对象增加一个参数,根据参数指定被创建对象的种类。
- 如果客户需要和特定的子类进行交互可能使得要向下转换,有可能导致转换失败
- 类图:
- 举例:
通过泛型将具体工厂传入,然后调用:
public void Go()
{
// 创建Africa非洲动物链并运行
var africa = new AnimalWorld<Africa>();
africa.RunFoodChain();
// 创建America美国动物链并运行
var america = new AnimalWorld<America>();
america.RunFoodChain();
// Wait for user input
// Console.ReadKey();
//结果
//Lion eats Wildebeest
//Wolf eats Bison
}
Builder:建造者
- 封装变化:如何创建一个组合对象
- 意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 适用性:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
- 参与者职责:
- Builder:为创建一个Product对象的各个部件指定抽象接口。
- ConcreteBuilder:
- 实现Builder的接口以构造和装配该产品的各个部件。
- 定义并跟踪它所创建的表示
- 提供一个检索产品的接口
- Director:构造一个使用Builder接口的对象
- Product:
- 表示被构造的复杂对象,ConcreteBuilder创建该产品的内部表示并定义它的装配过程。
- 包含定义组成部件的类,包括将这些部件装配成最终产品的接口
- 协作:
- 客户创建Director对象,并用它所想要的Builder对象进行配置。
- 一旦生成产品部件,导向器就会通知生成器
- 生成器处理导向器的请求,并将部件添加到该产品中
- 客户从生成器中检索产品
- 优势和劣势
- 将产品的构造代码和表示代码分离
- Builder一步步构造复杂的对象,Director封装了产品构造逻辑,而Builder是构造部分的具体实现。
- 类图
- 举例:
Shop:Director
Vehicle:Product
Factory Method:工厂方法
- 封装变化:被实例化的子类
- 意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类,将一个类的实例化延迟到子类
- 适用性:
- 一个类不知道它所必须创建的对象的类的时候
- 一个类希望由它的子类来指定所创建的对象
- 当类将创建对象的职责委托给多个帮助的子类中的某一个
- 参与者职责:
- Product:定义工厂方法所创建的对象接口
- ConcreteProduct:实现Product接口
- Creator:声明工厂方法,该方法返回Product类型的对象
- ConcreteCreator:重定义工厂方法以返回一个ConcreteProduct实例
- 协作
- Creator依赖于它的子类来定义工厂方法。
- 优势和劣势:
- 为子类提供钩子Hook.
- 连接平行的类的层次
- 类图:
- 例子:
Prototype:原型
- 封装变化:被实例化的类
- 意图:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象
- 适用性:
- 当一个系统应该独立于它的产品创建,构成和表示时
- 当要实例化的类是在运行时指定,
- 避免创建一个与产品层次平行的工厂层次时
- 当一个类的实例只能有几个不同状态组合中的一种,
- 参与者职责
- Prototype:声明克隆自身的接口
- ConcretePrototype:实现一个克隆自身的接口
- Client:让原型克隆自身从而创建一个新的对象
- 优势和劣势:
- Abstract Factory存储一个被克隆对象的原型集合,并返回对象产品
- 类图:
Singleton:单例
-
封装变化:一个类的唯一实例
-
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点
-
适用情况:
- 当类只能有一个实例而且客户可以从一个总所周知的访问点访问它
- 唯一实例应该通过子类化可扩展的。
-
参与者职责:
- Singleton:
-
协作:
-
类图:
- 现在尽量不用单例模式,交给容器管理生命周期,帮助类用单例,或则静态
MonoState:单态模式
public class Monostate
{
private static int itsX;
public int X
{
get { return itsX; }
set { itsX = value; }
}
}
总结
抽象工厂:创造一系列对象
工厂模式:创建一种对象
建造者:创建复杂对象
原型:复制自己
单例:全局唯一
Strutural:结构型
Adapter:适配器 Wrapper
-
封装变化:对象的接口
-
意图:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作
-
适用情况:
- 已经存在一个类,接口不符合要求
- 创建一个可以复用的类,该类可以与其他不想关的类或不可预见的类协同工作
- 已经存在的子类,不可能对每一个都进行子类化以匹配它们的接口
-
参与者
- Target:定义Client使用的与特定领域相关的接口
- Client:符合Target接口的对象协同
- Adaptee:需要适配的接口
- Adapter:适配器
-
优势和劣势
- 和桥接实现方式差不多但是理念不一样:桥接是将接口和实现部分分离,可以对它们进行相对独立地加以改变。Adapter意味着改变一个已有对象的接口
- Decorator:模式增强了其他对象的功能而同时又不改变它的接口。
- Decorator对应用程序的透明度要比适配器更好,Decorator支持递归组合,
- Proxy在不改变它的接口的条件下,为另一个对象定义一个代理。
-
类图:
Bridge:桥接
- 封装变化:对象的实现
- 意图:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- 适用情况:
- 抽象和实现解绑
- 抽象和实现通过子类的方法加以扩充
- 参与者职责:
- Abstraction:抽象类的接口,维护一个指向Implementor类型对象的指针
- RefinedAbstraction:扩充由Abstraction定义的接口。
- Implementor:定义实现类的接口,该接口不一定要与Abstraction完全一致。
- ConcreteImplementor:实现Implementor接口。
- 协作
- Abstraction将Client请求转发给Implementor对象。
- 优势和劣势:
- 仅有一个Implemenetor,不需要使用该模式
- AbstractFactory模式可以创建和配置一个特定的Bridge模式。
- Adapter模式用来帮助无关类协同工作,在设计完成后才使用,而桥接模式是在系统开始的时候被使用。使得抽象接口和实现可以独立进行。
- 类图:
Composite:组合
- 封装变化:一个对象的结构和组成
- 意图:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得客户对单个对象和组合对象的使用具有一致性。
- 适用情况:对象的部分与整体层次结构
- 参与则职责:
- Component:
- 为组合中的对象声明接口
- 适当的情况下,实现所有类共有接口的缺省行为
- 一个接口用于访问和管理Component的子组件
- 在递归接口中定义一个接口,用于访问父部件。
- Leaf:
- 在组合中表示叶结点对象,叶结点没有子结点
- 在组合中定于图元对象的行为
- Composite:
- 定义有子部件的有哪些行为
- 存储子部件
- 在Component接口中实现与子部件有关的操作
- Client
- 通过Component接口操纵组合部件的对象
- Component:
- 协作:
- 用户使用Component类接口与组合接口中的对象进行交互。如果接收者是叶节点,就直接处理请求,如果是Composite,就将请求发送到子部件中。
- 优势和劣势:
- 关键就是一个抽象类,既可以代表图元又可以代表图元的容器。
- 部件-父部件连接用于Chain模式,
- Decorator模式与Composite模式一起使用,通常由一个公共的父类,装饰必须支持具有Add,Remove,GetChild操作的Component接口。
- Flyweight让你共享组件,但不能引用其父部件
- Interator可以遍历Composite
- Visitor,将本来应该分布在Composite和Leaf类中的操作和行为局部化。
- 类图
Decorator:装饰 wrapper 重要
- 封装变化:对象的职责,不生成子类
- 意图:动态地给一个对象添加一些额外的职责,就扩展功能而言,Decorator模式比生成子类的方式更加灵活
- 适用情况:
- 不影响其他类的情况下以动态透明的方式给单个对象添加职责
- 处理那些可以撤销的职责
- 不能采用生成子类的方法进行扩充,
- 参与者职责:
- Component:定义一个对象接口,可以给这些对象动态地添加职责。
- ConpreteComponent:定义一个具体对象
- Decorator:装饰者
- ConcreteDecorator
- 协作:
- Decorator将请求转发给它的Component对象。
- 优势和劣势:
- 装饰仅改变对象的职责而不改变接口
- Composite:可以将装饰视为退化的,仅由一个组件的组合。
- Strategy可以改变对象的内核。
- 类图:
- 例子
/// <summary>
/// The 'Component' abstract class
/// </summary>
abstract class LibraryItem<T>
{
// Each T has its own NumCopies
public static int NumCopies { get; set; }
public abstract void Display();
}
/// <summary>
/// The 'ConcreteComponent' class
/// </summary>
class Book : LibraryItem<Book>
{
string author;
string title;
// Constructor
public Book(string author, string title, int numCopies)
{
this.author = author;
this.title = title;
NumCopies = numCopies;
}
public override void Display()
{
Console.WriteLine("
Book ------ ");
Console.WriteLine(" Author: {0}", author);
Console.WriteLine(" Title: {0}", title);
Console.WriteLine(" # Copies: {0}", NumCopies);
}
}
var borrow = new Borrowable<Video>(video);
borrow.BorrowItem("Customer #1");
borrow.BorrowItem("Customer #2");
这边是Video,传入其实就是libraryItem<Video>,这个泛型真的是太棒了
所有的子类都用泛型实现是一个不错的想法
通过泛型将展示项传进入,这个是比较常见的作法,
Facade:外观
- 封装变化:一个子系统的接口
- 意图:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 适用情况:为一个复杂系统提供一个简单接口时,
- 参与者职责:
- Facade:知道哪些子系统负责处理请求,将客户的请求代理给适当的子系统对象
- Subsystem:实现子系统的功能。
- 协作:客户端发送请求给Fcada的方式与子系统通信,
- 优势和劣势:
- 降低客户子系统之间的耦合度
- AbstractFactory,可以与Facade一起提供一个接口,
- Mediator的目的是对同事之间的任意通信进行抽象
- 类图:
Flyweight:享元
- 封装变化:对象的存储开销
- 意图:运用共享技术有效地支持大量细颗粒度的对象。
- 适用情况:
- 应用程序适用了大量的对象,
- 完全由于适用大量对象造成很大的存储开销
- 对象的大多数状态都是外部状态
- 如果删除对象的外部状态,可以用较少的共享对象取代很多组对象
- 应用程序不依赖于对象标识,由于Flyweight对象可以被共享,
- 参与者职责:
- Flyweight:描述一个接口,通过这个接口flyweight可以接受并作用于外部状态
- ConcreteFlyweight:实现Flyweight接口,并为内部状态增加存储空间,
- UnsharedConcreteFlyweight:不共享
- FlyweightFactory创建Flyweight对象
- Client维持flyweight的引用,维持多个flyweight的外部状态
- 协作:
- flyweight执行时所需的状态必定是内部或外部,
- 不能对ConcreteFlyweight类进行实例化,只能从FlyweightFactory对象到ConcreteFlyweight对象。
- flyweight通常和composite模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。
- 最好用flyweight实现State和Strategy对象。
- 类图:
namespace DesignPatterns.Structural.Flyweight.RealWorld
{
[TestFixture]
class RealWorld
{
[Test]
public void Go()
{
// Build a document with text
string document = "AAZZBBZB";
char[] chars = document.ToCharArray();
CharacterFactory factory = new CharacterFactory();
// extrinsic state
int pointSize = 10;
// For each character use a flyweight object
foreach (char c in chars)
{
pointSize++;
Character character = factory.GetCharacter(c);
character.Display(pointSize);
}
// A (pointsize 11)
// A (pointsize 12)
// Z (pointsize 13)
// Z (pointsize 14)
// B (pointsize 15)
// B (pointsize 16)
// Z (pointsize 17)
// B (pointsize 18)
}
}
/// <summary>
/// The 'FlyweightFactory' class
/// </summary>
class CharacterFactory
{
private Dictionary<char, Character> characters = new Dictionary<char, Character>();
public Character GetCharacter(char key)
{
// Uses "lazy initialization"
Character character = null;
if (characters.ContainsKey(key))
{
character = characters[key];
}
else
{
switch (key)
{
case 'A': character = new CharacterA(); break;
case 'B': character = new CharacterB(); break;
//...
case 'Z': character = new CharacterZ(); break;
}
characters.Add(key, character);
}
return character;
}
}
/// <summary>
/// The 'Flyweight' abstract class
/// </summary>
abstract class Character
{
protected char symbol;
protected int width;
protected int height;
protected int ascent;
protected int descent;
protected int pointSize;
public abstract void Display(int pointSize);
}
/// <summary>
/// A 'ConcreteFlyweight' class
/// </summary>
class CharacterA : Character
{
// Constructor
public CharacterA()
{
this.symbol = 'A';
this.height = 100;
this.width = 120;
this.ascent = 70;
this.descent = 0;
}
public override void Display(int pointSize)
{
this.pointSize = pointSize;
Console.WriteLine(this.symbol +
" (pointsize " + this.pointSize + ")");
}
}
/// <summary>
/// A 'ConcreteFlyweight' class
/// </summary>
class CharacterB : Character
{
// Constructor
public CharacterB()
{
this.symbol = 'B';
this.height = 100;
this.width = 140;
this.ascent = 72;
this.descent = 0;
}
public override void Display(int pointSize)
{
this.pointSize = pointSize;
Console.WriteLine(this.symbol +
" (pointsize " + this.pointSize + ")");
}
}
// ... C, D, E, etc.
/// <summary>
/// A 'ConcreteFlyweight' class
/// </summary>
class CharacterZ : Character
{
// Constructor
public CharacterZ()
{
this.symbol = 'Z';
this.height = 100;
this.width = 100;
this.ascent = 68;
this.descent = 0;
}
public override void Display(int pointSize)
{
this.pointSize = pointSize;
Console.WriteLine(this.symbol +
" (pointsize " + this.pointSize + ")");
}
}
}
Proxy:代理 Surrogate
- 封装变化:如何访问一个对象,对象的位置
- 意图:为其他对象提供一个代理以控制这个对象的访问
- 适用情况:
- 远程代理Remote Proxy:为一个对象在不同的地址空间提供局部代表,
- 虚代理Virtual Proxy:需要创建开销很大的对象。
- 保护代理Protection Proxy:控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
- 参与者职责:
- Proxy:
- 保存一个引用使得代理可以访问实体,控制对实体的存取,并可能负责创建和删除它。
- RemoteProxy:负责对请求及其参数进行编码,并向不同地址空间的实体发送已编码的请求
- VirtualProxy:可以缓存实体的附加信息,以便延迟对它的访问,例如ImageProxy缓存图像实体的尺寸。
- ProtectionProxy:检查调用者是否具有实现一个请求所必须的访问权限。
- Subject:接口
- RealSubject:代理实体。
- Proxy:
- 优势和劣势:
- Adapter为适配的对象提供一个不同的接口。
- Decorator:装饰是为对象添加一个或多个功能,代理则控制对对象的访问。
- 类图
总结
适配器:把设计好的接口换成另一种接口
桥接:接口编排/逻辑不能改变。
组合:树形图,集合
装饰:动态的给类添加职责,在原有职责的基础上,接口不变
外观:提供统一的API入口,将众多细颗粒度封装成粗颗粒度
享元:共享颗粒度对象,集合
代理:控制对象的访问
适配器和桥接的区别
适配器模型
我们不想,也不能修改这个接口及其实现。同时也不可能控制其演化,只要相关的对象能与系统定义的接口协同工作即可。适配器模式经常被用在与第三方产品的功能集成上,采用该模式适应新类型的增加的方式是开发针对这个类型的适配器,
桥接模式则不同,参与桥接的接口是稳定的,用户可以扩展和修改桥接中的类,但是不能改变接口。桥接模式通过接口继承实现或者类继承实现功能扩展。
按照GOF的说法,桥接模式和适配器模式用于设计的不同阶段,桥接模式用于设计的前期,即在设计类时将类规划为逻辑和实现两个大类,是他们可以分别精心演化;而适配器模式用于设计完成之后,当发现设计完成的类无法协同工作时,可以采用适配器模式。然而很多情况下在设计初期就要考虑适配器模式的使用,如涉及到大量第三方应用接口的情况。
联合
一个典型的例子是工业控制中的数据采集。不同工控厂家提供的底层数据采集接口通常不同,因此在做上层软件设计无法预知可能遇到何种接口。为此需要定义一个通用的采集接口,然后针对具体的数据采集系统开发相应的适配器。数据存储需要调用数据采集接口获得的数据,而数据可以保存到关系数据库、实时数据库或者文件中,。数据存储接口和数据采集结构成了桥接,
同样的结构也经常出现在报表相关的应用中,报表本身结构和报表输出方式完全可以分开
适配器和外观
两种模式的意图完全不同,前者使现存系统与正在设计的系统协同工作,而后者则为显存系统提供一个更为方便的访问接口。简单的说,适配器模式为事后设计,而外观模式则必须事前设计,因为系统依赖于外观。总之,适配器模式没有引入新的接口,而外观模式则定义了一个全新的接口。
适配器模式用于粒度较小的功能集成,如使用权威部门所固定的无法修改并替换的现有算法模块,将来也可能升级,这时可以使用适配器模式。
外观模式的使用有时比较难把握,外观接口的定义与设计人员对业务的理解程度有很大的关系。如果接口设计过于复杂,则不如直接调用原系统简单;如果接口设计过于简单,有些功能需要调用原有系统才能实现,同样达不到分装的目的。在这种情况下,首先要考虑被封装系统的稳定程度。如果系统处于演化阶段,那么接口定义需要复杂一些,以暴露更多的接口。这时,外观模式更像一个大粒度的适配器。被封装的系统发生演化时,需要新的外观对象,而这个外观对象起到了适配器的作用,
组合和装饰
代理,适配器和外观
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,使得原本接口不兼容而不能一起工作的那些类可以一起工作。
外观模式(Facade):为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
代理模式和适配器模式应该说很相像,但是他们的区别也很明显,代理模式和被代理者的接口是同一个,只是使用中客户访问不到被代理者,所以利用代理间接的访问,而适配器模式,是因为接口不同,为了让用户使用到统一的接口,把原先的对象通过适配器让用户统一的使用,大多数运用在代码维护的后期,或者借用第三方库的情况下 ,而外观模式,是大家经常无意中使用的,就是把错综复杂的子系统关系封装起来,然后提供一个简单的接口给客户使用,就类似于一个转接口,可以想象成一个漏斗,中间细的那一段,越细耦合度越低,外观模式就是为了降低耦合度。
代理模式,代理者保存一个被代理的一个对象;适配器模式,保存了一个被适配的对象;而外观模式,就保存了各个子系统对象,然后根据实际逻辑组合。
Behavioral:行为型
Chain of Responsibility:责任链 重要
-
封装变化:满足一个请求的对象
-
意图:解除请求的发送者和接收者之间的耦合,使得多个对象都有机会处理这个请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
-
适用情况
- 多个对象可以处理一个请求,哪个对象处理该请求运行时确定
- 不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 处理一个请求的对象集合应被动态指定
-
参与者职责
- Handle:一个处理请求的接口
- ConcreteHandle:处理它所负责的请求,访问它的后继者,
- Client:链上的具体处理者对象提交请求
-
类图
- 例子
Approver:批准者
Purchase:采购
Director:主任
VicePresident:副总统
President:总统
Successor:接班人
在Handler中实现一个事件和一个抽象函数,在构造函数中订阅事件。这样任何基类都会自动订阅事件,完成链式处理
namespace DesignPatterns.Behavioral.ChainofResponsibility.RealWorld
{
[TestFixture]
class RealWorld
{
[Test]
public void Go()
{
// Setup Chain of Responsibility
Approver larry = new Director();
Approver sam = new VicePresident();
Approver tammy = new President();
larry.SetSuccessor(sam);
sam.SetSuccessor(tammy);
// Generate and process purchase requests
Purchase p = new Purchase(2034, 350.00, "Supplies");
larry.ProcessRequest(p);
p = new Purchase(2035, 32590.10, "Project X");
larry.ProcessRequest(p);
p = new Purchase(2036, 122100.00, "Project Y");
larry.ProcessRequest(p);
// Director approved request# 2034
// President approved request# 2035
// Request# 2036 requires an executive meeting!
}
}
/// <summary>
/// The 'Handler' abstract class
/// 批准者
/// </summary>
abstract class Approver
{
protected Approver successor;
public void SetSuccessor(Approver successor)
{
this.successor = successor;
}
public abstract void ProcessRequest(Purchase purchase);
}
/// <summary>
/// The 'ConcreteHandler' class
/// 总经理
/// </summary>
class Director : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 10000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else if (successor != null)
{
successor.ProcessRequest(purchase);
}
}
}
/// <summary>
/// The 'ConcreteHandler' class
/// 副总
/// </summary>
class VicePresident : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 25000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else if (successor != null)
{
successor.ProcessRequest(purchase);
}
}
}
/// <summary>
/// The 'ConcreteHandler' class
/// 总统
/// </summary>
class President : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 100000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else
{
Console.WriteLine(
"Request# {0} requires an executive meeting!",
purchase.Number);
}
}
}
/// <summary>
/// Class holding request details
/// </summary>
class Purchase
{
int number;
double amount;
string purpose;
// Constructor
public Purchase(int number, double amount, string purpose)
{
this.number = number;
this.amount = amount;
this.purpose = purpose;
}
// Gets or sets purchase number
public int Number
{
get { return number; }
set { number = value; }
}
// Gets or sets purchase amount
public double Amount
{
get { return amount; }
set { amount = value; }
}
// Gets or sets purchase purpose
public string Purpose
{
get { return purpose; }
set { purpose = value; }
}
}
责任链经常与Composite一起适用,一个构件的父构件可作为它的后续。
Command:命令 action transaction
-
封装变化:合适,怎样满足一个请求
-
意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
-
适用条件
- 抽象出执行动作以参数化某对象。Command模式是回调机制的一个替代品
- 在不同的时刻指定、排列和执行请求,
- 支持取消操作
- 支持修改日志
- 用构建在原语操作上的高层操作构造一个系统。
-
参与者职责:
- Command:声明执行操作的接口
- ConcreteCommand:将一个接收者对象绑定于一个动作,调用接收者相应的操作
- Invoker:要求该命令执行这个请求,集合保存命令,排队,请求,具体执行
- Receiver:如何实时与执行一个请求相关的操作,任何类都可以作为一个接收者。
-
协作
- client创建一个ConcreteCommand,并指定Receiver对象。
- Invoker存储ConcreteCommand对象
- Invoker调用Command的Execute来提交请求。
-
优势和劣势
- Composite可用来实现宏命令
- Memento用来保持某个状态,命令用这一状态来取消它的效果。
-
类图
Active Object:主动对象
namespace DesignPatterns.Behavioral.ActiveObject
{
public interface Command
{
void Execute();
}
public class ActiveObjectEngine
{
ArrayList itsCommands=new ArrayList();
public void AddCommand(Command c)
{
itsCommands.Add(c);
}
public void Run()
{
while (itsCommands.Count>0)
{
Command c = (Command) itsCommands[0];
itsCommands.RemoveAt(0);
c.Execute();
}
}
}
public class SleepCommand : Command
{
private Command wakeupCommand = null;
private ActiveObjectEngine engine = null;
private long sleepTime = 0;
private DateTime startTime;
private bool started = false;
public SleepCommand(long milliseconds, ActiveObjectEngine e, Command wakeupCommand)
{
sleepTime = milliseconds;
engine = e;
this.wakeupCommand = wakeupCommand;
}
public void Execute()
{
DateTime currentTime=DateTime.Now;
if (!started)
{
started = true;
startTime = currentTime;
engine.AddCommand(this);
}
else
{
TimeSpan elapsedTime = currentTime - startTime;
if (elapsedTime.TotalMilliseconds < sleepTime)
{
engine.AddCommand(this);
}
else
{
engine.AddCommand(wakeupCommand);
}
}
}
}
[TestFixture]
public class DelayedTyper : Command
{
private long itsDelay;
private char itsChar;
private static bool stop = false;
private static ActiveObjectEngine engine=new ActiveObjectEngine();
public DelayedTyper()
{ }
private class StopCommand : Command
{
public void Execute()
{
DelayedTyper.stop = true;
}
}
public DelayedTyper(long delay, char c)
{
itsDelay =delay;
itsChar = c;
}
public void Execute()
{
Console.Write(itsChar);
if (!stop)
DelayAndRepeat();
}
public void DelayAndRepeat()
{
engine.AddCommand(new SleepCommand(itsDelay,engine,this));
}
[Test]
public void TestDelayed()
{
engine.AddCommand(new DelayedTyper(100, '1'));
engine.AddCommand(new DelayedTyper(300, '3'));
engine.AddCommand(new DelayedTyper(500, '5'));
engine.AddCommand(new DelayedTyper(700, '7'));
Command stopCommand = new StopCommand();
engine.AddCommand(new SleepCommand(20000, engine, stopCommand));
engine.Run();
//135711311513171131511311715311131151731113151131711531113117513111315117311153111317151311131517131115311173115131113175113111531171311513111731511311153171131151311713151131117531113115131711315113117153111311517311131511317115311131175131113151173111531113171513111315171311153111731151311131751131115311713115131117315113111531711311537
}
}
}
Interpreter:解释器
-
封装变化:一个语言的文法及解释
-
意图:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
-
适用条件:当一个语言需要解释执行,并且可将该语言中的句子标识为一个抽象的语法树时,可使用解释器模式,
- 文法简单,对于复杂的文法,文法的类层次变得庞大而无法管理。
-
参与者职责
- AbstractExpression:声明一个抽象的解释操作
- TerminalExpression:实现与文法中的终结符相关联的解释操作,一个句子中的每个终结符需要该类的实例
- NonterminalExpression:对文法中的每一个规则都需要一个该实例
- Context:上下文
- Client:客户
-
协作:
- Client构建一个句子,然后初始上下文并调用解释器操作
- 每一非终结符表达式结点定义相应子表达式解释操作,而各终结符表达式的解释操作构成递归。
- 每一结点的解释操作用上下文来存储和访问解释器的状态。
-
优势和劣势:
- Composite:抽象语法树是一个组合模式的实例
- Flyweight:说明了如何在抽象语法树中共享终结符
- Interator:解释器可用一个迭代器遍历该结构
- Visitor:可用来在一个类中维护抽象语法树中个节点的行为。
-
类图
- 实例
处理逻辑放在基类中,只是把变化的部分,对context的判别函数放在了子类中。
namespace DesignPatterns.Behavioral.Interpreter.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Construct the 'parse tree'
var tree = new List<Expression>
{
new ThousandExpression(),
new HundredExpression(),
new TenExpression(),
new OneExpression()
};
// Create the context (i.e. roman value)
//创建上下文
string roman = "MCMXXVIII";
var context = new Context { Input = roman };
// Interpret
//每个解析器走一遍解析
tree.ForEach(e => e.Interpret(context));
Console.WriteLine("{0} = {1}", roman, context.Output);
//MCMXXVIII = 1928
}
}
/// <summary>
/// The 'Context' class
/// </summary>
class Context
{
public string Input { get; set; }
public int Output { get; set; }
}
/// <summary>
/// The 'AbstractExpression' abstract class
/// </summary>
abstract class Expression
{
/// <summary>
/// 解析
/// </summary>
/// <param name="context"></param>
public void Interpret(Context context)
{
if (context.Input.Length == 0)
return;
if (context.Input.StartsWith(Nine()))//如果匹配9,9*乘数
{
context.Output += (9 * Multiplier());
context.Input = context.Input.Substring(2);
}
else if (context.Input.StartsWith(Four()))
{
context.Output += (4 * Multiplier());
context.Input = context.Input.Substring(2);
}
else if (context.Input.StartsWith(Five()))
{
context.Output += (5 * Multiplier());
context.Input = context.Input.Substring(1);
}
while (context.Input.StartsWith(One()))
{
context.Output += (1 * Multiplier());
context.Input = context.Input.Substring(1);
}
}
public abstract string One();
public abstract string Four();
public abstract string Five();
public abstract string Nine();
public abstract int Multiplier();
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// Thousand checks for the Roman Numeral M
/// </remarks>
/// </summary>
class ThousandExpression : Expression
{
public override string One() { return "M"; }
public override string Four() { return " "; }
public override string Five() { return " "; }
public override string Nine() { return " "; }
public override int Multiplier() { return 1000; }
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// Hundred checks C, CD, D or CM
/// </remarks>
/// </summary>
class HundredExpression : Expression
{
public override string One() { return "C"; }
public override string Four() { return "CD"; }
public override string Five() { return "D"; }
public override string Nine() { return "CM"; }
public override int Multiplier() { return 100; }
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// Ten checks for X, XL, L and XC
/// </remarks>
/// </summary>
class TenExpression : Expression
{
public override string One() { return "X"; }
public override string Four() { return "XL"; }
public override string Five() { return "L"; }
public override string Nine() { return "XC"; }
public override int Multiplier() { return 10; }
}
/// <summary>
/// A 'TerminalExpression' class
/// <remarks>
/// One checks for I, II, III, IV, V, VI, VI, VII, VIII, IX
/// </remarks>
/// </summary>
class OneExpression : Expression
{
public override string One() { return "I"; }
public override string Four() { return "IV"; }
public override string Five() { return "V"; }
public override string Nine() { return "IX"; }
public override int Multiplier() { return 1; }
}
}
Interator:迭代 cursor
-
封装变化:如何遍历,访问一个聚合的各元素
-
意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
-
适用条件:
- 访问一个聚合对象的内容而无须暴露它的内部表示
- 支持对聚合对象的多种遍历
- 为遍历不同的聚合结构提供一个统一的接口,支持多态迭代。
-
参与者职责:
- Iterator:迭代器定义访问和遍历元素的接口
- ConcreteInterator:具体迭代器实现迭代器接口,对该聚合遍历时跟踪当前位置
- Aggregate:聚合定义创建相应迭代器对象的接口
- ConcreteAggregate:聚合实现创建相应迭代器的接口
-
协作:
- ConcreteIterator跟踪聚合中的当前对象,并能够计算出待遍历的后续对象
-
优势和劣势:
- Composite:迭代器常被应用到组合这样的递归结构上
- Factory Method:多态迭代器靠FactoryMethod来实例化适当的迭代器子类。
- Memento:常与迭代器模式一起适用,迭代器可适用Memento来捕获一个迭代的状态
-
类图
- 例子
而在Net中可以更加的简便
/// <summary>
/// The 'ConcreteAggregate' class
/// </summary>
/// <typeparam name="T">Collection item type</typeparam>
class ItemCollection<T> : IEnumerable<T>
{
List<T> items = new List<T>();
public void Add(T t)
{
items.Add(t);
}
// The 'ConcreteIterator'
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return items[i];
}
}
public IEnumerable<T> FrontToBack
{
get { return this; }
}
public IEnumerable<T> BackToFront
{
get
{
for (int i = Count - 1; i >= 0; i--)
{
yield return items[i];
}
}
}
public IEnumerable<T> FromToStep(int from, int to, int step)
{
for (int i = from; i <= to; i = i + step)
{
yield return items[i];
}
}
// Gets number of items
public int Count
{
get { return items.Count; }
}
// System.Collections.IEnumerable member implementation
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
使用了return yield return
Mediator:中介
- 封装变化:对象间怎样交互,和谁交互
- 意图:用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 适用条件:
- 一组对象以定义良好但复杂的方式进行通通信,产生的相互依赖关系结构混乱且蓝衣理解
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
- 参与者职责:
- Mediator:中介者定义一个接口用于与各同事(Colleague)对象通信
- ConcreteMediator:具体中介者通过协调各同事对象实现协作行为,了解并维护它的各个同事。
- Colleague Class:同事类,每一个同事类都知道它的中介者对象,每一个同事对象在需要与其他同事通信的时候与中介者通信。
- 协作:
- 同事向一个中介者对象发送和接受请求,中介者在各同事间适当地转发请求以实现协作行为。
- 优势和劣势:
- Facade与Mediator不同之处在于:Facade是对一个对象子系统进行抽象,从而提供了一个更为方便的接口,协议是单项的,Facade对象对这个子系统类提出请求,但反之则不行,相反,Mediator提供了Colleague对象不支持或不能支持的协作行为,而且协作是多向。
- Colleague可适用Observer模式与Mediator通信。
- 类图
- 例子
聊天室
每组对象都形成对Mediator的依赖
namespace DesignPatterns.Behavioral.Mediator.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Create chatroom participants
var George = new Beatle { Name = "George" };
var Paul = new Beatle { Name = "Paul" };
var Ringo = new Beatle { Name = "Ringo" };
var John = new Beatle { Name = "John" };
var Yoko = new NonBeatle { Name = "Yoko" };
// Create chatroom and register participants
var chatroom = new Chatroom();
chatroom.Register(George);
chatroom.Register(Paul);
chatroom.Register(Ringo);
chatroom.Register(John);
chatroom.Register(Yoko);
// Chatting participants
Yoko.Send("John", "Hi John!");
Paul.Send("Ringo", "All you need is love");
Ringo.Send("George", "My sweet Lord");
Paul.Send("John", "Can't buy me love");
John.Send("Yoko", "My sweet love");
}
}
/// <summary>
/// The 'Mediator' interface
/// </summary>
interface IChatroom
{
void Register(Participant participant);
void Send(string from, string to, string message);
}
/// <summary>
/// The 'ConcreteMediator' class
/// </summary>
class Chatroom : IChatroom
{
Dictionary<string, Participant> participants = new Dictionary<string, Participant>();
public void Register(Participant participant)
{
if (!participants.ContainsKey(participant.Name))
{
participants.Add(participant.Name, participant);
}
participant.Chatroom = this;
}
public void Send(string from, string to, string message)
{
var participant = participants[to];
if (participant != null)
{
participant.Receive(from, message);
}
}
}
/// <summary>
/// The 'AbstractColleague' class
/// </summary>
class Participant
{
// Gets or sets participant name
public string Name { get; set; }
// Gets or sets chatroom
public Chatroom Chatroom { get; set; }
// Send a message to given participant
public void Send(string to, string message)
{
Chatroom.Send(Name, to, message);
}
// Receive message from participant
public virtual void Receive(
string from, string message)
{
Console.WriteLine("{0} to {1}: '{2}'",
from, Name, message);
}
}
/// <summary>
/// A 'ConcreteColleague' class
/// </summary>
class Beatle : Participant
{
public override void Receive(string from, string message)
{
Console.Write("To a Beatle: ");
base.Receive(from, message);
}
}
/// <summary>
/// A 'ConcreteColleague' class
/// </summary>
class NonBeatle : Participant
{
public override void Receive(string from, string message)
{
Console.Write("To a non-Beatle: ");
base.Receive(from, message);
}
}
}
Memento:备忘录 Token
-
封装变化:一个对象中哪些私有信息存放在该对象之外,以及什么时候进行存储
-
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到保存的状态。
-
适用条件:
- 必须保证一个对象在某个时刻的(部分)状态,以后需要时它能恢复到先前的状态
- 如果一个接口让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性
-
参与者职责:
- Memento:
- 备忘录存储原发器对象的内部状态,原发器根据需要决定备忘录存储原发器的哪些内部状态
- 防止原发器以外的其他对象访问备忘录,备忘录实际上由两个接口,管理者(Caretaker)只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。相反,原生发生器能够看到一个宽接口,允许访问返回到先前状态所需的所有数据,理想的情况是只允许生成备忘录的那个原生发生器访问本备忘录的内部状态
- Originator:
- 原生发生器创建一个备忘录,用以记录当前时刻它的内部状态。
- 使用备忘录恢复内部状态。
- Caretaker:
- 管理者负责保存好备忘录。
- 不能对备忘录的内容进行操作或检查。
- Memento:
-
协作:
- 管理者向原发器请求一个备忘录,保留一段时间后,将其送回原发器,也有可能不送回
- 备忘录是被动的,只有创建备忘录的发生器会对它的状态进行赋值和检索。
-
优势和劣势:
- Command:命令可使用备忘录来为可撤销的操作维护状态
- Iterator:备忘录可用于迭代。
-
类图
Caretaker:看守人
originator:鼻祖
- 例子
其实可以通过序列化和反序列化创建Memento。
namespace DesignPatterns.Behavioral.Memento.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Init sales prospect through object initialization
var s = new SalesProspect //Originator
{
Name = "Joel van Halen",
Phone = "(412) 256-0990",
Budget = 25000.0
};
// Store internal state
var m = new ProspectMemory();//Caretaker
m.Memento = s.SaveMemento();
// Change originator
s.Name = "Leo Welch";
s.Phone = "(310) 209-7111";
s.Budget = 1000000.0;
// Restore saved state
s.RestoreMemento(m.Memento);
}
}
/// <summary>
/// The 'Originator' class
/// 鼻祖
/// </summary>
[Serializable]
class SalesProspect
{
string name;
string phone;
double budget;
// Gets or sets name
public string Name
{
get { return name; }
set
{
name = value;
Console.WriteLine("Name: " + name);
}
}
// Gets or sets phone
public string Phone
{
get { return phone; }
set
{
phone = value;
Console.WriteLine("Phone: " + phone);
}
}
// Gets or sets budget
public double Budget
{
get { return budget; }
set
{
budget = value;
Console.WriteLine("Budget: " + budget);
}
}
// Stores (serializes) memento
public Memento SaveMemento()
{
Console.WriteLine("
Saving state --
");
var memento = new Memento();
return memento.Serialize(this);
}
// Restores (deserializes) memento
public void RestoreMemento(Memento memento)
{
Console.WriteLine("
Restoring state --
");
var s = (SalesProspect)memento.Deserialize();
this.Name = s.Name;
this.Phone = s.Phone;
this.Budget = s.Budget;
}
}
/// <summary>
/// The 'Memento' class
/// </summary>
class Memento
{
MemoryStream stream = new MemoryStream();
SoapFormatter formatter = new SoapFormatter();
public Memento Serialize(object o)
{
formatter.Serialize(stream, o);
return this;
}
public object Deserialize()
{
stream.Seek(0, SeekOrigin.Begin);
object o = formatter.Deserialize(stream);
stream.Close();
return o;
}
}
/// <summary>
/// The 'Caretaker' class
/// Prospect:展望
/// CareTaker:看守人
/// </summary>
class ProspectMemory
{
public Memento Memento { get; set; }
}
}
Observer:观察者 dependent public-subscrible
- 封装变化:对各对象依赖于另一个对象,而这些对象又如何保持一致
- 意图:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
- 适用条件:
- 一个抽象模型由两个方面,一个方面依赖于另一个方面,将这二者封装在独立的对象中,以使它们可以各自独立地改变和复用。
- 一个对象的改变需要同时改变其他对象,而不知道具体多少对象有待改变。
- 一个对象必须通知其他对象,而它又不能假定其他对象是谁,不希望其他对象是紧耦合的。
- 参与职责:
- Subject:目标
- 目标知道它的观察者,可以有任意多个观察则观察同一个目标
- 提供注册和删除观察者对象的接口
- Observer:观察者
- 为那些在目标发生改变时需要获得通知的对象定义一个更新接口。
- ConcreteSubject:具体目标
- 将有关状态存入各ConcreteObserver对象,当状态发生改变时,向各个观察者发出通知
- ConcreteObserver:具体观察则
- 维护一个指向ConcreteSubject对象的引用
- 存储有关的状态,状态和目标状态保持一致。
- 实现Observer的更新接口,以使自身状态与目标的状态保持一致。
- Subject:目标
- 协作:
- ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
- 在得到一个具体目标的改变通知后,ConcreteObserver对象可向目标对象查询信息。ConcreteObserver适用这些信息使它的状态与目标对象的状态一致。
- 优势和劣势
- Mediator:通过封装复杂的更新语义,ChangeManager充当目标和观察者之间的中介者
- Singleton:ChangeManager可使用Sinleton模式来保证它是唯一的并且是可全局访问的
- 类图
例子
namespace DesignPatterns.Behavioral.Observer
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Create IBM stock and attach investors
var ibm = new IBM(120.00);
// Attach 'listeners', i.e. Investors
ibm.Attach(new Investor { Name = "Sorros" });
ibm.Attach(new Investor { Name = "Berkshire" });
// Fluctuating prices will notify listening investors
ibm.Price = 120.10;
ibm.Price = 121.00;
ibm.Price = 120.50;
ibm.Price = 120.75;
}
}
// Custom event arguments
public class ChangeEventArgs : EventArgs
{
// Gets or sets symbol
public string Symbol { get; set; }
// Gets or sets price
public double Price { get; set; }
}
/// <summary>
/// The 'Subject' abstract class
/// </summary>
abstract class Stock
{
protected string symbol;
protected double price;
// Constructor
public Stock(string symbol, double price)
{
this.symbol = symbol;
this.price = price;
}
// Event
public event EventHandler<ChangeEventArgs> Change;
// Invoke the Change event
public virtual void OnChange(ChangeEventArgs e)
{
if (Change != null)
{
Change(this, e);
}
}
public void Attach(IInvestor investor)
{
Change += investor.Update;
}
public void Detach(IInvestor investor)
{
Change -= investor.Update;
}
// Gets or sets the price
public double Price
{
get { return price; }
set
{
if (price != value)
{
price = value;
OnChange(new ChangeEventArgs { Symbol = symbol, Price = price });
Console.WriteLine("");
}
}
}
}
/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
class IBM : Stock
{
// Constructor - symbol for IBM is always same
public IBM(double price)
: base("IBM", price)
{
}
}
/// <summary>
/// The 'Observer' interface
/// 观察者
/// </summary>
interface IInvestor
{
void Update(object sender, ChangeEventArgs e);
}
/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
class Investor : IInvestor
{
// Gets or sets the investor name
public string Name { get; set; }
// Gets or sets the stock
public Stock Stock { get; set; }
public void Update(object sender, ChangeEventArgs e)
{
Console.WriteLine("Notified {0} of {1}'s " +
"change to {2:C}", Name, e.Symbol, e.Price);
}
}
}
State:状态
- 封装变化:对象的状态
- 自悟:一个实例有不同的状态,但是不同状态的对外接口都是相同的,内部实现不一样
- 意图:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
- 适用条件:
- 一个对象的行为取决于它的状态,并且它必须在运行时更具状态改变它的行为。
- 一个操作中含有庞大的多分分支条件语句,且这些分支依赖于该对象的状态,这个状态通常用一个或多个枚举常量表示,多个操作包含这一相同的条件结构,State模式将每一个条件分支放入一个独立的类中。
- 参与者职责:
- Context:环境
- 定义客户感兴趣的接口
- 维护一个ConcreteState子类的实例,这个实例定义当前状态
- State:状态
- 定义一个接口以封装与Context的一个特定状态相关的行为
- ConcreteState subclasses:具体状态类
- 每一子类实现一个与Context的一个状态相关的行为。
- Context:环境
- 协作
- Context将与状态相关的请求委托给当前ConcreteState对象处理。
- Context可将自身作为一个参数传递给处理该请求的状态对象,使得状态对象在必要时可访问Context
- Context是客户适用的主要接口,客户可用状态对象来配置一个Context,一旦一个Context配置完毕,客户不再需要直接与状态对象打交道。
- Context和ConcreteState子类都可决定那个状态是另外一个的后继者
- 优势和劣势
- Flyweight解释何时以及怎样共享状态对象。
- 类图
- 举例
Account将不同状态的职责委托给State来做
namespace DesignPatterns.Behavioral.State.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Open a new account
var account = new Account("Jim Johnson");
// Apply financial transactions
account.Deposit(500.0);
account.Deposit(300.0);
account.Deposit(550.0);
account.PayInterest();
account.Withdraw(2000.00);
account.Withdraw(1100.00);
}
}
/// <summary>
/// The 'State' abstract class
/// </summary>
abstract class State
{
protected double interest;
protected double lowerLimit;
protected double upperLimit;
// Gets or sets the account
public Account Account { get; set; }
// Gets or sets the balance
public double Balance { get; set; }
/// <summary>
/// 存款
/// </summary>
/// <param name="amount"></param>
public abstract void Deposit(double amount);
/// <summary>
/// 撤销
/// 退回
/// </summary>
/// <param name="amount"></param>
public abstract void Withdraw(double amount);
/// <summary>
/// 支付利息
/// </summary>
public abstract void PayInterest();
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Red state indicates account is overdrawn
/// </remarks>
/// </summary>
class RedState : State
{
double serviceFee;
// Constructor
public RedState(State state)
{
Balance = state.Balance;
Account = state.Account;
Initialize();
}
private void Initialize()
{
// Should come from a datasource
interest = 0.0;
lowerLimit = -100.0;
upperLimit = 0.0;
serviceFee = 15.00;
}
/// 存款
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
amount = amount - serviceFee;
Console.WriteLine("No funds available for withdrawal!");
}
public override void PayInterest()
{
// No interest is paid
}
private void StateChangeCheck()
{
if (Balance > upperLimit)
{
Account.State = new SilverState(this);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// 银级账户
/// <remarks>
/// Silver state is non-interest bearing state
/// </remarks>
/// </summary>
class SilverState : State
{
// Overloaded constructors
public SilverState(State state) :
this(state.Balance, state.Account)
{
}
/// <summary>
/// 银级账户
/// </summary>
/// <param name="balance"></param>
/// <param name="account"></param>
public SilverState(double balance, Account account)
{
Balance = balance;
Account = account;
Initialize();
}
private void Initialize()
{
// Should come from a datasource
interest = 0.0;
lowerLimit = 0.0;
upperLimit = 1000.0;
}
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
Balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
Balance += interest * Balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (Balance < lowerLimit)
{
Account.State = new RedState(this);
}
else if (Balance > upperLimit)
{
Account.State = new GoldState(this);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Gold incidates interest bearing state
/// </remarks>
/// </summary>
class GoldState : State
{
// Overloaded constructors
public GoldState(State state)
: this(state.Balance, state.Account)
{
}
public GoldState(double balance, Account account)
{
Balance = balance;
Account = account;
Initialize();
}
private void Initialize()
{
// Should come from a database
interest = 0.05;
lowerLimit = 1000.0;
upperLimit = 10000000.0;
}
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
Balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
Balance += interest * Balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (Balance < 0.0)
{
Account.State = new RedState(this);
}
else if (Balance < lowerLimit)
{
Account.State = new SilverState(this);
}
}
}
/// <summary>
/// The 'Context' class
/// </summary>
class Account
{
string owner;
// Constructor
public Account(string owner)
{
// New accounts are 'Silver' by default
this.owner = owner;
this.State = new SilverState(0.0, this);
}
// Gets the balance
public double Balance
{
get { return State.Balance; }
}
// Gets or sets state
public State State { get; set; }
public void Deposit(double amount)
{
State.Deposit(amount);
Console.WriteLine("Deposited {0:C} --- ", amount);
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}", this.State.GetType().Name);
Console.WriteLine("");
}
public void Withdraw(double amount)
{
State.Withdraw(amount);
Console.WriteLine("Withdrew {0:C} --- ", amount);
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}
", this.State.GetType().Name);
}
public void PayInterest()
{
State.PayInterest();
Console.WriteLine("Interest Paid --- ");
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}
", this.State.GetType().Name);
}
}
}
Strategy:策略 Policy
-
封装变化:算法
-
意图:定有一系列的算法,把它们一个个封装起来,并且使它们可相互替换,本模式使得算法的变化独立于使用它的客户。
-
适用条件:
- 许多相关的类仅仅是行为有异,策略提供了一种用多个行为中的一个行为来配置一个类的方法。
- 需要适用一个算法的不同变体,可能会定义一些反映不同的空间/时间权衡的算法,这些变体实现为一个算法的类层次时,可以适用策略模式。
- 算法适用客户不应该知道的数据,可使用策略模式以避免暴露复杂的,与算法相关的数据结构
- 一个类定义多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将条件分支移入它们各自的Strategy类中以代替这些条件语句。
-
参与者职责:
- Strategy:定义所有支持的算法的公共接口,Context使用这个接口来调用ConcreteStrategy定义的算法。
- ConcreteStrategy:实现接口
- Context:用一个ConcreteStrategy对象来配置,维护一个对Strategy对象的引用,定义一个接口来让strategy访问它的数据。
-
协作
- strategy和context相互协作,当算法被调用的时候,Context可以将算法所需的数据传递给Strategy。
- client只跟context进行交互。
-
优势和劣势
- Flyweight:strategy对象经常是很好的轻量级对象。
-
类图:
namespace DesignPatterns.Behavioral.Strategy.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Two contexts following different strategies
var studentRecords = new SortedList()
{
new Student{ Name = "Samual", Ssn = "154-33-2009" },
new Student{ Name = "Jimmy", Ssn = "487-43-1665" },
new Student{ Name = "Sandra", Ssn = "655-00-2944" },
new Student{ Name = "Vivek", Ssn = "133-98-8399" },
new Student{ Name = "Anna", Ssn = "760-94-9844" },
};
studentRecords.SortStrategy = new QuickSort();
studentRecords.SortStudents();
studentRecords.SortStrategy = new ShellSort();
studentRecords.SortStudents();
studentRecords.SortStrategy = new MergeSort();
studentRecords.SortStudents();
}
}
/// <summary>
/// The 'Strategy' interface
/// </summary>
interface ISortStrategy
{
void Sort(List<Student> list);
}
/// <summary>
/// A 'ConcreteStrategy' class
/// </summary>
class QuickSort : ISortStrategy
{
public void Sort(List<Student> list)
{
// Call overloaded Sort
Sort(list, 0, list.Count - 1);
Console.WriteLine("QuickSorted list ");
}
// Recursively sort
void Sort(List<Student> list, int left, int right)
{
int lhold = left;
int rhold = right;
// Use a random pivot
var random = new Random();
int pivot = random.Next(left, right);
Swap(list, pivot, left);
pivot = left;
left++;
while (right >= left)
{
int compareleft = list[left].Name.CompareTo(list[pivot].Name);
int compareright = list[right].Name.CompareTo(list[pivot].Name);
if ((compareleft >= 0) && (compareright < 0))
{
Swap(list, left, right);
}
else
{
if (compareleft >= 0)
{
right--;
}
else
{
if (compareright < 0)
{
left++;
}
else
{
right--;
left++;
}
}
}
}
Swap(list, pivot, right);
pivot = right;
if (pivot > lhold) Sort(list, lhold, pivot);
if (rhold > pivot + 1) Sort(list, pivot + 1, rhold);
}
// Swap helper function
private void Swap(List<Student> list, int left, int right)
{
var temp = list[right];
list[right] = list[left];
list[left] = temp;
}
}
/// <summary>
/// A 'ConcreteStrategy' class
/// </summary>
class ShellSort : ISortStrategy
{
public void Sort(List<Student> list)
{
// ShellSort(); not-implemented
Console.WriteLine("ShellSorted list ");
}
}
/// <summary>
/// A 'ConcreteStrategy' class
/// </summary>
class MergeSort : ISortStrategy
{
public void Sort(List<Student> list)
{
// MergeSort(); not-implemented
Console.WriteLine("MergeSorted list ");
}
}
/// <summary>
/// The 'Context' class
/// </summary>
class SortedList : List<Student>
{
// Sets sort strategy
public ISortStrategy SortStrategy { get; set; }
// Perform sort
public void SortStudents()
{
SortStrategy.Sort(this);
// Display sort results
foreach (var student in this)
{
Console.WriteLine(" " + student.Name);
}
Console.WriteLine();
}
}
/// <summary>
/// Represents a student
/// </summary>
class Student
{
// Gets or sets student name
public string Name { get; set; }
// Gets or sets student social security number
public string Ssn { get; set; }
}
}
Template Method
- 封装变化:算法的某些步骤
- 意图:定义一个操作中的算法骨架,而将一些步骤延迟到子类中,Template Method 使得子类不改变一个算法的接口即可重新定义该算法的某些特定步骤。
- 适用条件:
- 一次性实现一个算法的不变部分,可变部分留给子类实现。
- 各子类中公共的行为应被提取出来并几种到一个公共父类中以避免代码重复。
- 参与者职责:
- AbstractClass:抽象类,定于抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤
- ConcreteClass具体类,实现原语操作以完成算法中与特定子类相关的步骤
- 协作:
- ConcreteClass靠abstractclass来实现算法中不变的步骤。
- 优势和劣势
- FactoryMethod:常被模板方法调用
- Strategy:模板方法适用继承来改变算法的一部分
- 类图:
- 实例
public virtual void Connect()
{
// Make sure mdb is available to app
// 访问数据文件
//connectionString =
// "provider=Microsoft.JET.OLEDB.4.0; " +
// "data source=..\..\db1.mdb";
connectionString =
"provider=Microsoft.JET.OLEDB.4.0; " +
"data source=C:\Users\DuJinfeng\Desktop\Learn\0CSharpSummary\DCSharpPractises\TestDesignPatterns\db1.mdb";
}
/// <summary>
/// A 'ConcreteClass' class
/// </summary>
class Categories : DataAccessObject
{
public override void Select()
{
string sql = "SELECT CategoryName FROM Categories";
var dataAdapter = new OleDbDataAdapter(sql, connectionString);
dataSet = new DataSet();
dataAdapter.Fill(dataSet, "Categories");
}
public override void Process()
{
Console.WriteLine("Categories ---- ");
var dataTable = dataSet.Tables["Categories"];
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine(row["CategoryName"]);
}
Console.WriteLine();
}
}
Visitor:访问
-
封装变化:某些可作用于一个(组)对象上的操作,但不修改这些对象的类
-
意图:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
-
适用条件
- Visitor必须定义两个类层次,一个对应于接受操作的元素,另一个对应于定义对元素操作的访问者。
- 一个对象结构包含很多类对象,它们有不同的接口,如果想对这些对象实时一些依赖于其具体类的操作
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作。而且想避免这些操作污染这些对象的类,
- 定义对象结构的类很少改变,但是需要经常在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这需要很大的代价,所以封装这部分操作。
-
参与者职责:
- Visitor:访问者,为该对象结构中ConcreteElement的每个类声明一个Visit操作。操作的名字和特征标识发送Visit请求给访问者的类,使得访问者可以确定正被访问元素的具体类,这样访问者可以通过该元素的特定接口直接访问它。
- ConcreteVisitor:具体访问者,实现Visitor声明的操作,每个操作实现算法的一部分,concretevisitor为算法提供上下文并存储它的局部状态,这一状态常在遍历该结构的过程中累积。
- Element:定义一个Accept操作,以一个访问者为参数。
- ConcreteElement:具体元素,实现Accept操作,该操作以一个访问者为参数
- ObjectStructure:对象结构,能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素。,可以是一个组合或集合列表
-
协作
- 一个适用visitor模式的客户必须创建一个concretevisitor对象,然后遍历该对象结构,并用该访问者访问每一个元素。
- 当一个元素被访问时,调用对应于它的类visitor的操作。
-
优势和劣势
- composite:访问者可以用于对一个Composite模式定义的对象结构进行操作
- Interpreter:访问者可以用于解释
- strategy封装算法
- state封装有关状态的行为
- mediator:封装对象间的协议
- iterator对象封装访问和遍历一个集合对象中的各个构件的方法。
-
类图
Clerk:店员
Director:经理
president:总裁
namespace DesignPatterns.Behavioral.Visitor.NETOptimized
{
[TestFixture]
class NETOptimized
{
[Test]
public void Go()
{
// Setup employee collection
var employee = new Employees();
employee.Attach(new Clerk());
employee.Attach(new Director());
employee.Attach(new President());
// Employees are 'visited'
employee.Accept(new IncomeVisitor());
employee.Accept(new VacationVisitor());
// 结果
// Clerk Hank's new income: ¥27,500.00
// Director Elly's new income: ¥38,500.00
// Director Elly's new vacation days: 19
}
}
/// <summary>
/// The 'Visitor' abstract class
/// </summary>
public abstract class Visitor
{
// Use reflection to see if the Visitor has a method
// named Visit with the appropriate parameter type
// (i.e. a specific Employee). If so, invoke it.
public void ReflectiveVisit(IElement element)
{
var types = new Type[] { element.GetType() };
var mi = this.GetType().GetMethod("Visit", types);
if (mi != null)
{
mi.Invoke(this, new object[] { element });
}
}
}
/// <summary>
/// A 'ConcreteVisitor' class
/// </summary>
class IncomeVisitor : Visitor
{
// Visit clerk
public void Visit(Clerk clerk)
{
DoVisit(clerk);
}
// Visit director
public void Visit(Director director)
{
DoVisit(director);
}
private void DoVisit(IElement element)
{
var employee = element as Employee;
// Provide 10% pay raise
employee.Income *= 1.10;
Console.WriteLine("{0} {1}'s new income: {2:C}",
employee.GetType().Name, employee.Name,
employee.Income);
}
}
/// <summary>
/// A 'ConcreteVisitor' class
/// </summary>
class VacationVisitor : Visitor
{
// Visit director
public void Visit(Director director)
{
DoVisit(director);
}
private void DoVisit(IElement element)
{
var employee = element as Employee;
// Provide 3 extra vacation days
employee.VacationDays += 3;
Console.WriteLine("{0} {1}'s new vacation days: {2}",
employee.GetType().Name, employee.Name,
employee.VacationDays);
}
}
/// <summary>
/// The 'Element' interface
/// </summary>
public interface IElement
{
void Accept(Visitor visitor);
}
/// <summary>
/// The 'ConcreteElement' class
/// </summary>
class Employee : IElement
{
// Constructor
public Employee(string name, double income,
int vacationDays)
{
this.Name = name;
this.Income = income;
this.VacationDays = vacationDays;
}
// Gets or sets name
public string Name { get; set; }
// Gets or set income
public double Income { get; set; }
// Gets or sets vacation days
public int VacationDays { get; set; }
public virtual void Accept(Visitor visitor)
{
visitor.ReflectiveVisit(this);
}
}
/// <summary>
/// The 'ObjectStructure' class
/// </summary>
class Employees : List<Employee>
{
public void Attach(Employee employee)
{
Add(employee);
}
public void Detach(Employee employee)
{
Remove(employee);
}
public void Accept(Visitor visitor)
{
// Iterate over all employees and accept visitor
this.ForEach(employee => employee.Accept(visitor));
Console.WriteLine();
}
}
// Three employee types
class Clerk : Employee
{
// Constructor
public Clerk()
: base("Hank", 25000.0, 14)
{
}
}
class Director : Employee
{
// Constructor
public Director()
: base("Elly", 35000.0, 16)
{
}
}
class President : Employee
{
// Constructor
public President()
: base("Dick", 45000.0, 21)
{
}
}
}
总结
责任链:多个对象处理一个请求。集合
命令:请求就是需求,将一个需求封装成一个命令,集合
解释器:解析某段文本或编码,需要一个结合来存放所有解释器,集合
迭代:实现不同对象的遍历,集合
中介:公共聊天室,一个对象对一组对象的订阅,集合
备忘录:保存对象某一个时刻状态、
观察者:事件订阅
状态:一个对象有不同的状态,接口相同内部实现不一样。
策略:将类的行为封装,
模板方法:将算法的骨架做为父类,在子类定义算法细节
访问者:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
Additional
Filter Pattern:过滤器模式
criteria:准则规范
这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。
Null Object Pattern
在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。
MVC模式
业务代表模式
业务代表模式(Business Delegate Pattern)用于对表示层和业务层解耦。它基本上是用来减少通信或对表示层代码中的业务层代码的远程查询功能。在业务层中我们有以下实体。
- 客户端(Client) - 表示层代码可以是 JSP、servlet 或 UI java 代码。
- 业务代表(Business Delegate) - 一个为客户端实体提供的入口类,它提供了对业务服务方法的访问。
- 查询服务(LookUp Service) - 查找服务对象负责获取相关的业务实现,并提供业务对象对业务代表对象的访问。
- 业务服务(Business Service) - 业务服务接口。实现了该业务服务的实体类,提供了实际的业务实现逻辑。
组合实体模式
数据访问对象模式
前端控制器模式
拦截过滤模式
ServiceLocator服务定位模式
Class A依赖ServiceA和ServiceB
有以下缺点
-
尝试替换或跟新依赖项,必须更改类的源代码并重新编译。
-
依赖项的具体实现必须在编译时可用
-
测试类非常困难,类对引用直接依赖,不能使用stub或mock
-
该类包含创建、定位和管理依赖项的重复代码
![img](ReadMe.assets/Mon, 29 Jun 2020 081813.png)
解决方案:
- 把类与依赖项解耦,从而使这些依赖项可被替换或者更新。
- 类在编译时并不知道依赖项的具体实现。
- 类的隔离性和可测试性非常好。
- 类无需负责依赖项的创建、定位和管理逻辑。
- 通过将应用程序分解为松耦合的模块,达成模块间的无依赖开发、测试、版本控制和部署。
![img](ReadMe.assets/Mon, 29 Jun 2020 082952.png)
Service Locator 模式并不描述如何实例化服务,其描述了一种注册和定位服务的方式。通常情况下,Service Locator 模式与工厂模式(Factory Pattern)和依赖注入模式(Dependency Injection Pattern)等结合使用。
服务定位器应该能够在不知道抽象类的具体类型的情况下定位到服务。例如,它可能会使用字符串或服务接口类型来影射服务,这允许在无需修改类的条件下替换依赖项的具体实现。
在使用 Service Locator 模式之前,请考虑以下几点:
-
有很多程序中的元素需要管理。
-
在使用之前必须编写额外的代码将服务的引用添加到服务定位器。
-
类将对服务定位器有依赖关系。
-
源代码变的更加复杂和难以理解。
-
可以使用配置数据来定义运行时的关系。
-
必须提供服务的实现。因为服务定位器模式将服务消费者与服务提供者解耦,它可能需要提供额外的逻辑。这种逻辑将保证在服务消费者尝试定位服务之前,服务提供者已被安装和注册。
![img](ReadMe.assets/Mon, 29 Jun 2020 084138.webp)
服务(Service) - 实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。
Context / 初始的 Context - JNDI Context 带有对要查找的服务的引用,实际是工厂。
服务定位器(Service Locator) - 服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。
缓存(Cache) - 缓存存储服务的引用,以便复用它们。
客户端(Client) - Client 是通过 ServiceLocator 调用服务的对象。
public interface Service { public String getName(); public void execute(); } public class Service1 implements Service { public void execute(){ System.out.println("Executing Service1"); } @Override public String getName() { return "Service1"; } } public class Service2 implements Service { public void execute(){ System.out.println("Executing Service2"); } @Override public String getName() { return "Service2"; } } public class InitialContext { public Object lookup(String jndiName){ if(jndiName.equalsIgnoreCase("SERVICE1")){ System.out.println("Looking up and creating a new Service1 object"); return new Service1(); }else if (jndiName.equalsIgnoreCase("SERVICE2")){ System.out.println("Looking up and creating a new Service2 object"); return new Service2(); } return null; } } import java.util.ArrayList; import java.util.List; public class Cache { private List<Service> services; public Cache(){ services = new ArrayList<Service>(); } public Service getService(String serviceName){ for (Service service : services) { if(service.getName().equalsIgnoreCase(serviceName)){ System.out.println("Returning cached "+serviceName+" object"); return service; } } return null; } public void addService(Service newService){ boolean exists = false; for (Service service : services) { if(service.getName().equalsIgnoreCase(newService.getName())){ exists = true; } } if(!exists){ services.add(newService); } } } public class ServiceLocator { private static Cache cache; static { cache = new Cache(); } public static Service getService(String jndiName){ Service service = cache.getService(jndiName); if(service != null){ return service; } InitialContext context = new InitialContext();//上下文相当于工厂 Service service1 = (Service)context.lookup(jndiName); cache.addService(service1); return service1; } } public class ServiceLocatorPatternDemo { public static void main(String[] args) { Service service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); } } /*结果 Looking up and creating a new Service1 object Executing Service1 Looking up and creating a new Service2 object Executing Service2 Returning cached Service1 object Executing Service1 Returning cached Service2 object Executing Service2 */
传输对象模式
设计原则(SOLID)
单一职责原则(single responsibilities principle,SRP)
- 原理:一个类应该只有一个变化
- 分离职责:如果不耦合的职责那么很简单,如果两个职责耦合,将两个职责抽象为接口,通过继承两个接口将依赖关系抽离处理啊
开放封闭原则(open close principle,OCP)
- 软件实体(类,模块,函数等)应该是可以扩展的,但是不可修改
- 对扩展开放:当需求改变时,对模块可以扩展。
- 对修改封闭:对模块进行扩展时,不必改动模块的源代码或则二进制代码,
- 仅仅抽象出容易变化的部分。
里氏替换原则(liskov substitution principle,LSP)
- 子类型必须能够替换掉它的基类型。
接口隔离原则(interface segregation principle,ISP)
接口会被污染:
当借口的职责不再单一时,接口就很容易受到污染。
一个常见的例子:一个门,可能是关着也可能是开着,而且门类知道只是是开着还是关着。
常见的接口设计,现在需要实现自动报警,当门开着超过一定的时间就进行报警。常见的方法是关联timer类,实现报警。
这种方案就造成了接口污染,所有的门都必须依赖timeclient,同时还会出现门检测到时间超时,还未报警时,门关闭了,然后又被打开了,门变成了错误的报警
通过增加一个报警id,来区别每一次报警和客户端。
- 接口隔离原则:不应该强迫客户程序依赖并未使用的方法。
隔离接口
- 通过适配器原则,实现timer类对door类的引用隔离doorclient.这样仅仅增加了一个类,而将引用关系倒置。
创建一个派生自timer的timerclient对象,并把该对象请求委托给timerdoor。
这样就实现了timer和door的隔离,即使对timer进行更改也不会影响到door。timerdoor也不需要和timerclient一样的接口,
- 另一种方法是使用timerdoor来多重继承,door和timerclient,
这种方案没有多余的开销,只有当doortimeradapter对象所做的转换是必须的时候或则不同的时候需要不同的转换的时候,才需要使用适配器方法。
例子:
AMT的一个例子,输出信息要转换成不同的语言,输出信息要显示在桌面上,
把不同的事务处理方法封装在transaction上,这样每个事务的修改都会造成UI的修改,如果把接口分解成不通的单独接口,就可以避免
依赖倒置原则(dependence inversion principle,DIP)
- 高层模块不应该依赖于底层模块,二者都应该依赖抽象
- 抽象不应该依赖细节,细节应该依赖抽象
为什么叫倒置,在传统软件开发中,总倾向于创建一些高层模块依赖底层模块,策略依赖细节的软件结构。一个良好的面向对象程序,对传统设计结构而言就被倒置了。
其实就是都依赖接口编程,高层依赖接口,细节依赖接口,这样模块的改动不会影响其他模块。比较好的模块设计:
模块和模块间的依赖都是依赖接口。
- 倒置不仅仅是依赖关系的倒置,也是接口所有权的倒置,通常会认为工具库应该拥有自己的接口,但其实应该是客户拥有接口,而它们的服务者应该是接口的派生。著名的 holly wood原则:“Don't call us, we'll call you”不要调用我们,我们会调用你,低层模块实现在高层模块中声明并被高层模块调用的接口
- 程序所有的依赖关系都应该终止与抽象
- 任何变量都不应该持有一个指向具体类的引用
- 任何类都不应该从具体类派生
- 任何方法都不应该重写它的任何基类中已经实现的方法。
打包原则
大型系统的设计非常依赖于好的组件设计,这样每个团队只要关注于单个组件而不需要关注整个系统。
但类经常会和其他类发生依赖关系,这些依赖关系也会跨越组件的边界。
- 在向组件中分配类时应该依据什么原则
- 应该使用什么设计原则来管理组件之间的关系
- 组件的设计应该先于类(自顶而下),还是设计应该先于组件(自底而上)
- 组件的实体以什么方式存在
- 组件创建好后,用于何种目的
组件和组件间的依赖关系:不能依赖具体类。只能是具体依赖抽象,抽象依赖抽象。这样就可以将影响将至最低。
前三个原则来指导如何将类划分到包中,后三个原则来管理包之间的耦合(稳定)。组件之间的耦合越稳定就越好
重用发布等价原则(reuse release equivalence principle,REP)
重用粒度就是发布粒度:一个组件的重用粒度和发布粒度一样大,重用的任何东西必须被同时发布和跟踪,
重用性必然是基于组件的,所以可重用的组件必须包含可重用的类,因至少某些组件应该由一组可重用的类组成
一个类中的组件要么都是可重用的,要么都是不可重用的。
共同重用原则(common reuse principle , CRP)
一个组件中所有的类都应该是共同重用的,如果重用了组件中的一个类,那么就要重用组件中的所有类。
这个原则可以帮助我们确定哪些类应该在一个组件中,相互之间没有紧密联系的类不应该在同一个组件中。
共同封闭原则(common closure principle,CCP)
组件中所有的类对同一种性质的变化应该是共同封闭的,一个变化若对一个封闭的组件产生影响,则将对该组件中所有的类产生影响,而对其他组件不产生影响。类似于单一职责原则。
无环依赖原则(Acyclic-Dependencies Principle,ADP)
在组件的关系图中不允许存在环。
解除依赖环的方法:提取抽象接口,通过实现接口来替换关联。关联和实现的依赖关系相反。
每周构建是错误,前4天项目成员各自构建,周五集成,随着项目的规模增长会导致集成时间越来越长。最终会超过半周时间在集成
解除依赖环,使用依赖倒置
稳定依赖原则(Stable-Dependencies Principle,SIP)
朝着稳定的方向进行依赖。
被依赖的越多,该组件就越不可能改动,则越稳定。
稳定性度量:
稳定抽象原则(Stable-Abstractions Principle,SAP)
组件的抽象程度与其稳定性一致。
中间连接线称为主序列。
到主序列的距离:
越为0 越好
有了度量和标准就让我们划分组件吧!!!
如何设计
- 写出问题的描述:挑出动词和名词,进而创建相应的类和操作
- 关注系统的协作和职责关系建模
- 对现实世界建模,再将分析时的对象转化至设计中。
如何选择设计模式
- 考虑问题,
- 匹配设计模式的意图以及相互之间的关联,
- 选择目的相似的模式,
- 考虑我的设计中哪些是可变的
怎样适用设计模式
-
大致浏览一遍模式
-
研究相关模式的结构,参与者职责及协作
-
子类都使用泛型,泛型来包装,这样的方式
咖啡机
做一个使用咖啡机的软件,驱动接口已经被写好。
咖啡机的硬件包括:
- 加热器加热棒(开关)Boiler
- 保温盘加热棒(开关)Warmer
- 保温盘传感器(保温盘空,杯子空,杯子不空)WarmerPlate
- 加热器传感器(有水,没水)Boiler
- 冲煮按钮(开关)
- 指示灯(开关)Indicater
- 减压阀门(开关)Relief
咖啡机的冲煮流程:
- 咖啡机一次煮12杯咖啡,
- 咖啡加入过滤器,过滤器加入支架,支架放到咖啡机。
- 倒入12杯水到滤水器,按下冲煮,水杯加热至沸腾,水蒸气碰洒到过滤器,形成水滴到咖啡壶,咖啡壶发现有水保温,
- 拿走水杯,停止工作。
硬件工程师提供的接口:
namespace CoffeeMakerApps.Api
{
/// <summary>
/// 保温板状态
/// </summary>
public enum WarmerPlateStatus {
WARMER_EMPTY,
POT_EMPTY,
POT_NOT_EMPTY
};
/// <summary>
/// 加热器状态
/// </summary>
public enum BoilerStatus {
EMPTY,
NOT_EMPTY
};
/// <summary>
/// 冲泡按钮状态
/// </summary>
public enum BrewButtonStatus {
PUSHED,
NOT_PUSHED
};
/// <summary>
/// 加热器运行状态
/// </summary>
public enum BoilerState {
ON,
OFF
};
/// <summary>
/// 保温板运行状态
/// </summary>
public enum WarmerState {
ON,
OFF
};
/// <summary>
/// 指示灯状态
/// </summary>
public enum IndicatorState {
ON,
OFF
};
/// <summary>
/// 泄压阀状态
/// </summary>
public enum ReliefValveState {
OPEN,
CLOSED
}
public interface CoffeeMakerApi {
/// <summary>
/// 返回加热器的状态。 该传感器检测壶的存在以及壶中是否有咖啡。
/// </summary>
/// <returns></returns>
WarmerPlateStatus GetWarmerPlateStatus();
/// <summary>
/// 该功能返回加热器开关的状态。 加热器开关是一个浮动开关,可检测锅炉中是否有超过1/2杯水。
/// </summary>
/// <returns></returns>
BoilerStatus GetBoilerStatus();
/// <summary>
/// 此功能返回冲泡按钮的状态。 冲泡按钮是一个瞬时开关,可以记住其状态。 每次对该函数的调用都将返回记忆状态,然后将该状态重置为NOT_PUSHED。 因此,即使以非常慢的速度轮询此功能,它也将检测到何时按下冲煮按钮。
/// </summary>
/// <returns></returns>
BrewButtonStatus GetBrewButtonStatus();
/// <summary>
/// 开关加热器中的加热元件
/// </summary>
/// <param name="s"></param>
void SetBoilerState(BoilerState s);
/// <summary>
/// 开关保温元件
/// </summary>
/// <param name="boilerState"></param>
void SetWarmerState(WarmerState boilerState);
/// <summary>
/// 此功能可打开或关闭指示灯。 在冲泡周期结束时,指示灯应亮起。 用户按下冲煮按钮时应将其关闭。
/// </summary>
/// <param name="s"></param>
void SetIndicatorState(IndicatorState s);
/// <summary>
/// 此功能打开和关闭泄压阀。 当该阀关闭时,锅炉中的蒸汽压力将迫使热水喷洒在咖啡过滤器上。 当阀门打开,锅炉中的水不会喷洒到过滤器上。
/// </summary>
/// <param name="s"></param>
void SetReliefValveState(ReliefValveState s);
}
}
方案一
建立一个咖啡机超类,关联各个硬件类。这个方案是非常丑陋的,这不是根据行为划分的,有些类,比如light没有任何变量,仅仅调用了驱动接口,这种类叫水蒸气类。没有意义
方案二
按照行为来划分,要确定一些类有没有意义,只需要确定谁使用他们,而且要到业务底层去看。把问题的本质和细节分离,忘掉一切,最根本的问题是如何煮咖啡。如何煮咖啡,将热水倒到咖啡上,把冲泡好的咖啡收集起来。水从哪来?咖啡放到哪里?那么就有两个类:热水类和支架类,大大多数人会考虑热水流到支架中,这是比较错误的,软件的行为应该按照软件的行为给出而不是基于一些物理关系。还需要考虑用户界面,这样就有三个类。
- 谁使用水蒸气类,来确定基类
- 最原始的需求
- 软件行为而不是物理行为,
左边是物理关系,右边是软件行为,也就是ContainmentVessel依赖HotWaterSource。
用例
- 按下冲煮,询问HotWaterSource,ContainmentVessel做好准备,都准备好就启动HotWaterSource,a
- 正在煮咖啡的过程中,咖啡壶被拿走要暂停HotWaterSource,咖啡放回继续HotWaterSource。b
- 冲煮完成,水流用完,容器满了,c,d
- 咖啡喝完,空容器,通知用户喝完,e
Containment Vessel:支架和保温壶
Resume:恢复
a,b,c,d表示四种逻辑:
- a 表示:用户按下冲煮,询问支架中有咖啡壶,热水器中有水,然后才开始煮咖啡
- b 表示:如果正在煮咖啡的过程中咖啡壶被拿走,则必须中断水流,停止送热水,再次放回咖啡壶继续水流。
- c 表示:热水器中传感器告诉我们水用完了就停止煮咖啡,同时告诉用户和容器已经停止煮咖啡
- d表示:容器已经满了,咖啡煮好,停止水流,向用户发送完成
- e 表示:咖啡喝完,一个空的咖啡壶放在支架上(保温盘),热水器应该知道这个消息,同时用户也应该知道这个消息
这样整个咖啡机的抽象就完成了,按职责划分,各司其职。这三个抽象类不能知道任何关于咖啡机的任何信息。这就是依赖倒置原则。
系统的控制流如何检测传感器呢?是选择线程还是轮询。最好的总是假设消息都是可以异步发送的,就像存在有独立的线程一样,把使用轮询还是线程的决策推迟到最后一刻。
这样设置了一个接口,main()程序就待在一个循环中,不停地一遍遍调用这个方法实现轮询。
using CoffeeMakerApps.Api;
using CoffeeMakerApps.M4CoffeeMaker;
using System;
namespace CoffeeMakerApps
{
class M4CoffeeMakerRun
{
static void Main(string[] args) {
CoffeeMakerApi api = new M4CoffeeMakerApi();
UserInterface ui = new M4UserInterface(api);
HotWaterSource hws = new M4HotWaterSource(api);
ContainmentVessel cv = new M4ContainmentVessel(api);
ui.Init(hws,cv);
hws.Init(ui,cv);
cv.Init(ui,hws);
bool isGo=true;
while (isGo) {
((Pollable)ui).Poll();//poll:轮询
((Pollable)hws).Poll();
((Pollable)cv).Poll();
}
Console.ReadKey();
}
}
}
依赖倒置,不允许高层的咖啡制作中依赖底层实现。
用例实现
用例一:
namespace CoffeeMakerApps.M4CoffeeMaker {
public abstract class UserInterface {
private HotWaterSource hws;
private ContainmentVessel cv;
protected bool isComplete;
protected UserInterface( bool isComplete) {
this.isComplete = isComplete;
}
public UserInterface() {
isComplete = true;
}
public void Init(HotWaterSource hws, ContainmentVessel cv)
{
this.hws = hws;
this.cv = cv;
}
public abstract void Done();
public abstract void ComleteCycle();
public void Complete() {
isComplete = true;
ComleteCycle();
}
protected void StartBrewing() {
if (hws.IsReady() && cv.IsReady()) {
isComplete = false;
hws.Start();
cv.Start();
}
}
}
}
using CoffeeMakerApps.Api;
namespace CoffeeMakerApps.M4CoffeeMaker {
public class M4UserInterface: UserInterface, Pollable {
private CoffeeMakerApi api;
public M4UserInterface(CoffeeMakerApi api) {
this.api = api;
}
public void Poll() {
BrewButtonStatus buttonStatus = api.GetBrewButtonStatus();
if(buttonStatus==BrewButtonStatus.PUSHED) StartBrewing();//Brewing酿造
}
public override void Done() {
api.SetIndicatorState(IndicatorState.ON);//设置指示灯状态
}
public override void ComleteCycle() {
api.SetIndicatorState(IndicatorState.OFF);
}
}
}
namespace CoffeeMakerApps.M4CoffeeMaker {
public abstract class HotWaterSource {
private UserInterface ui;
private ContainmentVessel cv;
protected bool isBrewing;
public HotWaterSource() {
isBrewing = false;
}
public void Init(UserInterface ui, ContainmentVessel cv) {
this.ui = ui;
this.cv = cv;
}
public void Start() {
isBrewing = true;
StartBrewing();
}
public void Done() {
isBrewing = false;
}
protected void DeclareDone() {
ui.Done();
cv.Done();
isBrewing = false;
}
public abstract bool IsReady();
public abstract void StartBrewing();
public abstract void Pause();
public abstract void Resume();
}
}
using CoffeeMakerApps.Api;
namespace CoffeeMakerApps.M4CoffeeMaker {
public class M4HotWaterSource :HotWaterSource, Pollable {
private CoffeeMakerApi api;
public M4HotWaterSource(CoffeeMakerApi api)
{
this.api = api;
}
public override bool IsReady()
{
BoilerStatus boilerStatus = api.GetBoilerStatus();
return boilerStatus == BoilerStatus.NOT_EMPTY;
}
public override void StartBrewing()
{
api.SetReliefValveState(ReliefValveState.CLOSED);
api.SetBoilerState(BoilerState.ON);
}
public void Poll()
{
BoilerStatus boilerStatus = api.GetBoilerStatus();
if (isBrewing)
{
if (boilerStatus == BoilerStatus.EMPTY)
{
api.SetBoilerState(BoilerState.OFF);
api.SetReliefValveState(ReliefValveState.CLOSED);
DeclareDone();
}
}
}
public override void Pause()
{
api.SetBoilerState(BoilerState.OFF);
api.SetReliefValveState(ReliefValveState.OPEN);
}
public override void Resume()
{
api.SetBoilerState(BoilerState.ON);
api.SetReliefValveState(ReliefValveState.CLOSED);
}
}
}
namespace CoffeeMakerApps.M4CoffeeMaker {
public abstract class ContainmentVessel {
private UserInterface ui;
private HotWaterSource hws;
protected bool isBrewing;
protected bool isComplete;
public ContainmentVessel()
{
isBrewing = false;
isComplete = true;
}
public void Init(UserInterface ui, HotWaterSource hws)
{
this.ui = ui;
this.hws = hws;
}
public void Start()
{
isBrewing = true;
isComplete = false;
}
public void Done()
{
isBrewing = false;
}
protected void DeclareComplete()
{
isComplete = true;
ui.Complete();
}
protected void ContainerAvailable()
{
hws.Resume();
}
protected void ContainerUnavailable()
{
hws.Pause();
}
public abstract bool IsReady();
}
}
using CoffeeMakerApps.Api;
namespace CoffeeMakerApps.M4CoffeeMaker {
public class M4ContainmentVessel : ContainmentVessel, Pollable
{
private CoffeeMakerApi api;
private WarmerPlateStatus lastPotStatus;
public M4ContainmentVessel(CoffeeMakerApi api)
{
this.api = api;
lastPotStatus = WarmerPlateStatus.POT_EMPTY;
}
public override bool IsReady()
{
WarmerPlateStatus plateStatus =
api.GetWarmerPlateStatus();
return plateStatus == WarmerPlateStatus.POT_EMPTY;
}
public void Poll()
{
WarmerPlateStatus potStatus = api.GetWarmerPlateStatus();
if (potStatus != lastPotStatus)
{
if (isBrewing)
{
HandleBrewingEvent(potStatus);
}
else if (isComplete == false)
{
HandleIncompleteEvent(potStatus);
}
lastPotStatus = potStatus;
}
}
private void
HandleBrewingEvent(WarmerPlateStatus potStatus)
{
if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)
{
ContainerAvailable();
api.SetWarmerState(WarmerState.ON);
}
else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)
{
ContainerUnavailable();
api.SetWarmerState(WarmerState.OFF);
}
else
{ // potStatus == POT_EMPTY
ContainerAvailable();
api.SetWarmerState(WarmerState.OFF);
}
}
private void
HandleIncompleteEvent(WarmerPlateStatus potStatus)
{
if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)
{
api.SetWarmerState(WarmerState.ON);
}
else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)
{
api.SetWarmerState(WarmerState.OFF);
}
else
{ // potStatus == POT_EMPTY
api.SetWarmerState(WarmerState.OFF);
DeclareComplete();
}
}
}
}
轮询咖啡机
namespace CoffeeMakerApps.M4CoffeeMaker {
public interface Pollable {
void Poll();
}
}
这个设计的好处
圈出来的三个类是系统的高层策略,圈内的类没有依赖任何圈外的类,抽象和细节完全分离
测试
测试优先的原则:
using CoffeeMakerApps.Api;
using CoffeeMakerApps.M4CoffeeMaker;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestCoffeeMakerApps
{
internal class CoffeeMakerStub : CoffeeMakerApi {
public bool buttonPressed;
public bool lightOn;
public bool boilerOn;
public bool valveClosed;
public bool plateOn;
public bool boilerEmpty;
public bool potPresent;
public bool potNotEmpty;
public CoffeeMakerStub() {
buttonPressed = false;
lightOn = false;
boilerOn = false;
valveClosed = true;
plateOn = false;
boilerEmpty = true;
potPresent = true;
potNotEmpty = false;
}
public WarmerPlateStatus GetWarmerPlateStatus() {
if (!potPresent) return WarmerPlateStatus.WARMER_EMPTY;
else if (potNotEmpty) {
return WarmerPlateStatus.POT_NOT_EMPTY;
}
else {
return WarmerPlateStatus.POT_EMPTY;
}
}
public BoilerStatus GetBoilerStatus() {
return boilerEmpty ? BoilerStatus.EMPTY : BoilerStatus.NOT_EMPTY;
}
public BrewButtonStatus GetBrewButtonStatus() {
if (buttonPressed) {
buttonPressed = false;
return BrewButtonStatus.PUSHED;
}
else {
return BrewButtonStatus.NOT_PUSHED;
}
}
public void SetBoilerState(BoilerState boilerState) {
boilerOn = boilerState == BoilerState.ON;
}
public void SetWarmerState(WarmerState boilerState) {
plateOn = boilerState == WarmerState.ON;
}
public void SetIndicatorState(IndicatorState indicatorState) {
lightOn = indicatorState == IndicatorState.ON;
}
public void SetReliefValveState(ReliefValveState reliefValveState) {
valveClosed = reliefValveState == ReliefValveState.CLOSED;
}
}
[TestClass]
public class TestCoffeeMaker {
private M4UserInterface ui;
private M4HotWaterSource hws;
private M4ContainmentVessel cv;
private CoffeeMakerStub api;
[TestInitialize]
public void SetUp()
{
api = new CoffeeMakerStub();
ui = new M4UserInterface(api);
hws = new M4HotWaterSource(api);
cv = new M4ContainmentVessel(api);
ui.Init(hws, cv);
hws.Init(ui, cv);
cv.Init(ui, hws);
}
private void Poll()
{
ui.Poll();
hws.Poll();
cv.Poll();
}
[TestMethod]
public void InitialConditions()
{
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void StartNoPot()
{
Poll();
api.buttonPressed = true;
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void StartNoWater()
{
Poll();
api.buttonPressed = true;
api.boilerEmpty = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void GoodStart()
{
NormalStart();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalStart()
{
Poll();
api.boilerEmpty = false;
api.buttonPressed = true;
Poll();
}
[TestMethod]
public void StartedPotNotEmpty()
{
NormalStart();
api.potNotEmpty = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void PotRemovedAndReplacedWhileEmpty()
{
NormalStart();
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsFalse(api.valveClosed);
api.potPresent = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void PotRemovedWhileNotEmptyAndReplacedEmpty()
{
NormalFill();
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsFalse(api.valveClosed);
api.potPresent = true;
api.potNotEmpty = false;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalFill()
{
NormalStart();
api.potNotEmpty = true;
Poll();
}
[TestMethod]
public void PotRemovedWhileNotEmptyAndReplacedNotEmpty()
{
NormalFill();
api.potPresent = false;
Poll();
api.potPresent = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void BoilerEmptyPotNotEmpty()
{
NormalBrew();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalBrew()
{
NormalFill();
api.boilerEmpty = true;
Poll();
}
[TestMethod]
public void BoilerEmptiesWhilePotRemoved()
{
NormalFill();
api.potPresent = false;
Poll();
api.boilerEmpty = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
api.potPresent = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[TestMethod]
public void EmptyPotReturnedAfter()
{
NormalBrew();
api.
potNotEmpty = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
}
}
薪水支付实例
该案例主要有做一个薪水支付系统,主要有三类员工
- 临时工:基本时薪(Hourly),超过8小时加班时间1.5倍工资,每天有考勤卡,每周5结算。
- 正式员工:月薪(Salaried ),每个月最后一天结算。
- 经理:月薪,每月最后一天结算,有项目提成(Commission),每隔一周的周五结算,加入公会扣会费。
- 公会会费分服务费和会费:会费每周都有从薪水中扣除,服务费从下个月薪水中扣除。
- 薪水支付方式:可以选择支票邮寄到家,支票保存自取,直接存入银行账号。
- 薪水支付每天运行一次,在当天为相应的雇员进行支付,上一次支付到本次支付应付的数额。
用例:
- 新增雇员:雇员号,姓名,地址。(零时工,正式员工,经理)
- 删除雇员:雇员号
- 登记考勤卡:雇员号,日期,小时
- 登记销售凭条:雇员号,日期,销售记录
- 登记公会服务费:公会成员,服务费用
- 更改雇员细则:更改姓名,更改地址,更改每小时报酬,更改薪水,更改提成,更改支付方式,加入公会,离开公会
设计类和结构:
通过用例来推导出应该有哪些类,不要陷入过多细节。
拿到一个新的项目,先分析项目有几个模块,每个模块的用例,不要陷入细节,分而治之。
通过迭代的方式进行实现。数据库是实现细节,应该尽推迟数据库的设计。
从用例的角度设计
- 新增雇员
命令模式:
Hourly:小时工
Commissioned:正式员工
Balarid:经理
- 删除雇员
把每一项工作划分为自己的类中。这样有可能会创建三个雇员类,但是分析一下就会发现变化的东西太多了,正式由于雇员变化的东西引发雇员类型的改变,只需要将变化的东西抽象出来,在更改雇员细则时改变这些变化的东西就可以改变雇员类型。
- 登记考勤卡
- 登记销售凭条
- 登机工会服务费
- 更改雇员细则
这是由多个更改策略组合而成。
Affiliation:隶属
- 发薪日
反思找到抽象
- 支付类别抽象
- 支付时间抽象
- 支付方式
- 从属关系
实现
- 事务
事务是使用命令模式。
- 增加雇员事务,雇员有三个类型,所以使用模板模式来实现增加雇员,此处模板模式的唯一任务就是创建对象
使用模板方法来增加雇员。
using System;
namespace Payroll
{
public abstract class AddEmployeeTransaction : Transaction
{
private readonly int empid;
private readonly string name;
private readonly string address;
public AddEmployeeTransaction(int empid,
string name, string address, PayrollDatabase database)
: base (database)
{
this.empid = empid;
this.name = name;
this.address = address;
}
protected abstract
PaymentClassification MakeClassification();
protected abstract
PaymentSchedule MakeSchedule();
public override void Execute()
{
PaymentClassification pc = MakeClassification();
PaymentSchedule ps = MakeSchedule();
PaymentMethod pm = new HoldMethod();
Employee e = new Employee(empid, name, address);
e.Classification = pc;
e.Schedule = ps;
e.Method = pm;
database.AddEmployee(e);
}
public override string ToString()
{
return String.Format("{0} id:{1} name:{2} address:{3}", GetType().Name, empid, name,address);
}
}
}
namespace Payroll
{
public class AddSalariedEmployee : AddEmployeeTransaction
{
private readonly double salary;
public AddSalariedEmployee(int id, string name, string address, double salary, PayrollDatabase database)
: base(id, name, address, database)
{
this.salary = salary;
}
protected override
PaymentClassification MakeClassification()
{
return new SalariedClassification(salary);
}
protected override PaymentSchedule MakeSchedule()
{
return new MonthlySchedule();
}
}
}
- 删除雇员
namespace Payroll
{
public class DeleteEmployeeTransaction : Transaction
{
private readonly int id;
public DeleteEmployeeTransaction(int id, PayrollDatabase database)
: base (database)
{
this.id = id;
}
public override void Execute()
{
database.DeleteEmployee(id);
}
}
}
提供雇员id,去数据库删除雇员,没啥好说的。
-
考勤卡、销售凭条、服务费
考勤卡:需要参数,雇员id,日期,工作时间
public class TimeCard
{
private readonly DateTime date;
private readonly double hours;
public TimeCard(DateTime date, double hours)
{
this.date = date;
this.hours = hours;
}
public double Hours
{
get { return hours; }
}
public DateTime Date
{
get { return date; }
}
}
using System;
namespace Payroll
{
public class TimeCardTransaction : Transaction
{
private readonly DateTime date;
private readonly double hours;
private readonly int empId;
public TimeCardTransaction(DateTime date, double hours, int empId, PayrollDatabase database)
: base(database)
{
this.date = date;
this.hours = hours;
this.empId = empId;
}
public override void Execute()
{
Employee e = database.GetEmployee(empId);
if (e != null)
{
HourlyClassification hc =
e.Classification as HourlyClassification;
if (hc != null)
hc.AddTimeCard(new TimeCard(date, hours));
else
throw new ApplicationException(
"Tried to add timecard to" +
"non-hourly employee");
}
else
throw new ApplicationException(
"No such employee.");
}
}
}
其他两种与这类似
- 更改雇员属性
更改雇员属性由多个事务集合而成
改名字事务:
using System;
namespace Payroll
{
public abstract class ChangeEmployeeTransaction : Transaction
{
private readonly int empId;
public ChangeEmployeeTransaction(int empId, PayrollDatabase database)
: base (database)
{
this.empId = empId;
}
public override void Execute()
{
Employee e = database.GetEmployee(empId);
if(e != null)
Change(e);
else
throw new ApplicationException(
"No such employee.");
}
protected abstract void Change(Employee e);
}
}
namespace Payroll
{
public class ChangeNameTransaction
: ChangeEmployeeTransaction
{
private readonly string newName;
public ChangeNameTransaction(int id, string newName, PayrollDatabase database)
: base(id, database)
{
this.newName = newName;
}
protected override void Change(Employee e)
{
e.Name = newName;
}
}
}
更改雇员类别
namespace Payroll
{
public abstract class ChangeClassificationTransaction
: ChangeEmployeeTransaction
{
public ChangeClassificationTransaction(int id, PayrollDatabase database)
: base (id, database)
{}
protected override void Change(Employee e)
{
e.Classification = Classification;
e.Schedule = Schedule;
}
protected abstract
PaymentClassification Classification { get; }
protected abstract PaymentSchedule Schedule { get; }
}
}
namespace Payroll
{
public class ChangeHourlyTransaction
: ChangeClassificationTransaction
{
private readonly double hourlyRate;
public ChangeHourlyTransaction(int id, double hourlyRate, PayrollDatabase database)
: base(id, database)
{
this.hourlyRate = hourlyRate;
}
protected override PaymentClassification Classification
{
get { return new HourlyClassification(hourlyRate); }
}
protected override PaymentSchedule Schedule
{
get { return new WeeklySchedule(); }
}
}
}
public abstract class ChangeClassificationTransaction
: ChangeEmployeeTransaction
{
public ChangeClassificationTransaction(int id, PayrollDatabase database)
: base (id, database)
{}
protected override void Change(Employee e)
{
e.Classification = Classification;
e.Schedule = Schedule;
}
protected abstract
PaymentClassification Classification { get; }
protected abstract PaymentSchedule Schedule { get; }
}
public class ChangeHourlyTransaction
: ChangeClassificationTransaction
{
private readonly double hourlyRate;
public ChangeHourlyTransaction(int id, double hourlyRate, PayrollDatabase database)
: base(id, database)
{
this.hourlyRate = hourlyRate;
}
protected override PaymentClassification Classification
{
get { return new HourlyClassification(hourlyRate); }
}
protected override PaymentSchedule Schedule
{
get { return new WeeklySchedule(); }
}
}
public class ChangeSalariedTransaction : ChangeClassificationTransaction
{
private readonly double salary;
public ChangeSalariedTransaction(int id, double salary, PayrollDatabase database)
: base(id, database)
{
this.salary = salary;
}
protected override PaymentClassification Classification
{
get { return new SalariedClassification(salary); }
}
protected override PaymentSchedule Schedule
{
get { return new MonthlySchedule(); }
}
}
public class ChangeCommissionedTransaction
: ChangeClassificationTransaction
{
private readonly double baseSalary;
private readonly double commissionRate;
public ChangeCommissionedTransaction(int id, double baseSalary, double commissionRate, PayrollDatabase database)
: base(id, database)
{
this.baseSalary = baseSalary;
this.commissionRate = commissionRate;
}
protected override PaymentClassification Classification
{
get { return new CommissionClassification(baseSalary, commissionRate); }
}
protected override PaymentSchedule Schedule
{
get { return new BiWeeklySchedule(); }
}
}
改变方法和改变工会实现方式基本与改变雇佣类别相似
改变支付方法:
public abstract class ChangeMethodTransaction : ChangeEmployeeTransaction
{
public ChangeMethodTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{}
protected override void Change(Employee e)
{
PaymentMethod method = Method;
e.Method = method;
}
protected abstract PaymentMethod Method { get; }
}
public class ChangeMailTransaction : ChangeMethodTransaction
{
public ChangeMailTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
}
protected override PaymentMethod Method
{
get { return new MailMethod("3.14 Pi St"); }
}
}
public class ChangeHoldTransaction : ChangeMethodTransaction
{
public ChangeHoldTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
}
protected override PaymentMethod Method
{
get { return new HoldMethod(); }
}
}
public class ChangeDirectTransaction : ChangeMethodTransaction
{
public ChangeDirectTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
}
protected override PaymentMethod Method
{
get { return new DirectDepositMethod("Bank -1", "123"); }
}
}
改变工会:
public abstract class ChangeAffiliationTransaction : ChangeEmployeeTransaction
{
public ChangeAffiliationTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{}
protected override void Change(Employee e)
{
RecordMembership(e);
Affiliation affiliation = Affiliation;
e.Affiliation = affiliation;
}
protected abstract Affiliation Affiliation { get; }
protected abstract void RecordMembership(Employee e);
}
public class ChangeUnaffiliatedTransaction : ChangeAffiliationTransaction
{
public ChangeUnaffiliatedTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{}
protected override Affiliation Affiliation
{
get { return new NoAffiliation(); }
}
protected override void RecordMembership(Employee e)
{
Affiliation affiliation = e.Affiliation;
if(affiliation is UnionAffiliation)
{
UnionAffiliation unionAffiliation =
affiliation as UnionAffiliation;
int memberId = unionAffiliation.MemberId;
database.RemoveUnionMember(memberId);
}
}
}
public class ChangeMemberTransaction : ChangeAffiliationTransaction
{
private readonly int memberId;
private readonly double dues;
public ChangeMemberTransaction(int empId, int memberId, double dues, PayrollDatabase database)
: base(empId, database)
{
this.memberId = memberId;
this.dues = dues;
}
protected override Affiliation Affiliation
{
get { return new UnionAffiliation(memberId, dues); }
}
protected override void RecordMembership(Employee e)
{
database.AddUnionMember(memberId, e);
}
}
- 支付薪水
支付月薪:
public class PaydayTransaction : Transaction
{
private readonly DateTime payDate;
private Hashtable paychecks = new Hashtable();
public PaydayTransaction(DateTime payDate, PayrollDatabase database)
: base (database)
{
this.payDate = payDate;
}
public override void Execute()
{
ArrayList empIds = database.GetAllEmployeeIds();
foreach(int empId in empIds)
{
Employee employee = database.GetEmployee(empId);
if (employee.IsPayDate(payDate))
{
DateTime startDate =
employee.GetPayPeriodStartDate(payDate);
Paycheck pc = new Paycheck(startDate, payDate);
paychecks[empId] = pc;
employee.Payday(pc);
}
}
}
public Paycheck GetPaycheck(int empId)
{
return paychecks[empId] as Paycheck;
}
}
public class MonthlySchedule : PaymentSchedule
{
private bool IsLastDayOfMonth(DateTime date)
{
int m1 = date.Month;
int m2 = date.AddDays(1).Month;
return (m1 != m2);
}
public bool IsPayDate(DateTime payDate)
{
return IsLastDayOfMonth(payDate);
}
public DateTime GetPayPeriodStartDate(DateTime date)
{
int days = 0;
while(date.AddDays(days - 1).Month == date.Month)
days--;
return date.AddDays(days);
}
public override string ToString()
{
return "monthly";
}
}
其中有paycheck类。该类有日期,总薪酬,服务扣费,实际薪酬
- 临时工
public class HourlyClassification : PaymentClassification
{
private double hourlyRate;
private Hashtable timeCards = new Hashtable();
public HourlyClassification(double rate)
{
this.hourlyRate = rate;
}
public double HourlyRate
{
get { return hourlyRate; }
}
public TimeCard GetTimeCard(DateTime date)
{
return timeCards[date] as TimeCard;
}
public void AddTimeCard(TimeCard card)
{
timeCards[card.Date] = card;
}
public override double CalculatePay(Paycheck paycheck)
{
double totalPay = 0.0;
foreach(TimeCard timeCard in timeCards.Values)
{
if(DateUtil.IsInPayPeriod(timeCard.Date,
paycheck.PayPeriodStartDate,
paycheck.PayPeriodEndDate))
totalPay += CalculatePayForTimeCard(timeCard);
}
return totalPay;
}
private double CalculatePayForTimeCard(TimeCard card)
{
double overtimeHours = Math.Max(0.0, card.Hours - 8);
double normalHours = card.Hours - overtimeHours;
return hourlyRate * normalHours +
hourlyRate * 1.5 * overtimeHours;
}
public override string ToString()
{
return String.Format("${0}/hr", hourlyRate);
}
}
public class WeeklySchedule : PaymentSchedule
{
public bool IsPayDate(DateTime payDate)
{
return payDate.DayOfWeek == DayOfWeek.Friday;
}
public DateTime GetPayPeriodStartDate(DateTime date)
{
return date.AddDays(-6);
}
public override string ToString()
{
return "weekly";
}
}
public class Employee
{
private readonly int empid;
private string name;
private readonly string address;
private PaymentClassification classification;
private PaymentSchedule schedule;
private PaymentMethod method;
private Affiliation affiliation = new NoAffiliation();
public Employee(int empid, string name, string address)
{
this.empid = empid;
this.name = name;
this.address = address;
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Address
{
get { return address; }
}
public PaymentClassification Classification
{
get { return classification; }
set { classification = value; }
}
public PaymentSchedule Schedule
{
get { return schedule; }
set { schedule = value; }
}
public PaymentMethod Method
{
get { return method; }
set { method = value; }
}
public Affiliation Affiliation
{
get { return affiliation; }
set { affiliation = value; }
}
public bool IsPayDate(DateTime date)
{
return schedule.IsPayDate(date);
}
public void Payday(Paycheck paycheck)
{
//计算总的薪资
double grossPay = classification.CalculatePay(paycheck);
//计算扣除薪资
double deductions = affiliation.CalculateDeductions(paycheck);
//计算实际薪资
double netPay = grossPay - deductions;
paycheck.GrossPay = grossPay;
paycheck.Deductions = deductions;
paycheck.NetPay = netPay;
//通过支付方式支付
method.Pay(paycheck);
}
public DateTime GetPayPeriodStartDate(DateTime date)
{
return schedule.GetPayPeriodStartDate(date);
}
public int EmpId
{
get { return empid; }
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append("Emp#: ").Append(empid).Append(" ");
builder.Append(name).Append(" ");
builder.Append(address).Append(" ");
builder.Append("Paid ").Append(classification).Append(" ");
builder.Append(schedule);
builder.Append(" by ").Append(method);
return builder.ToString();
}
}
public class UnionAffiliation : Affiliation
{
private Hashtable charges = new Hashtable();
private int memberId;
private readonly double dues;
public UnionAffiliation(int memberId, double dues)
{
this.memberId = memberId;
this.dues = dues;
}
public UnionAffiliation()
: this(-1, 0.0)
{}
public ServiceCharge GetServiceCharge(DateTime time)
{
return charges[time] as ServiceCharge;
}
public void AddServiceCharge(ServiceCharge sc)
{
charges[sc.Time] = sc;
}
public double Dues
{
get { return dues; }
}
public int MemberId
{
get { return memberId; }
}
public double CalculateDeductions(Paycheck paycheck)
{
double totalDues = 0;
int fridays = NumberOfFridaysInPayPeriod(
paycheck.PayPeriodStartDate, paycheck.PayPeriodEndDate);
totalDues = dues * fridays;
foreach(ServiceCharge charge in charges.Values)
{
if(DateUtil.IsInPayPeriod(charge.Time,
paycheck.PayPeriodStartDate,
paycheck.PayPeriodEndDate))
totalDues += charge.Amount;
}
return totalDues;
}
private int NumberOfFridaysInPayPeriod(
DateTime payPeriodStart, DateTime payPeriodEnd)
{
int fridays = 0;
for (DateTime day = payPeriodStart;
day <= payPeriodEnd; day = day.AddDays(1))
{
if (day.DayOfWeek == DayOfWeek.Friday)
fridays++;
}
return fridays;
}
}
- 主程序
包分析
应用共同封闭原则CCP
Payroll Domain:包含了模型中所有的抽象。
最终编写的大部分细节都被具有很少依赖者或者没有依赖者的组件中。有依赖的组件都是抽象的
应用重用发布等价原则(REP)
组件中的类应该是内聚的,它们之间相互依赖。类应该一同封闭,也应该一同重用。
事务类和它们要操作的元素分离
耦合封装
TimeCard和SalesReceipe非常适合内部类
度量
对象工厂
Classifications和ClassificationTransactions之所以
通过工厂缓解过度依赖