研磨设计模式 (陈臣 / 王斌 著)
第1章 设计模式基础 (已看)
第2章 简单工厂 (已看)
第3章 外观模式(Facade) (已看)
第4章 适配器模式(Adapter) (已看)
第5章 单例模式(Singleton) (已看)
第6章 工厂方法模式(Factory Method) (已看)
第7章 抽象工厂模式(Abstract Factory) (已看)
第8章 生成器模式(Builder) (已看)
第9章 原型模式(Prototype) (已看)
第10章 中介者模式(Mediator) (已看)
第11章 代理模式(Proxy) (已看)
第12章 观察者模式(Observer) (已看)
第13章 命令模式(Command) (已看)
第14章 迭代器模式(Iterator) (已看)
第15章 组合模式(Composite) (已看)
第16章 模板方法模式(Template Method) (已看)
第17章 策略模式(Strategy) (已看)
第18章 状态模式(State) (已看)
第19章 备忘录模式(Memento) (已看)
第20章 享元模式(Flyweight) (已看)
第21章 解释器模式(Interpreter) (已看)
第22章 装饰模式(Decorator) (已看)
第23章 职责链模式(Chain of Responsibility) (已看)
第24章 桥接模式(Bridge) (已看)
第25章 访问者模式(Visitor) (已看)
附录A 常见面向对象设计原则 (已看)
第1章 设计模式基础
1.1 设计模式是什么
1.1.1 什么是模式
1.1.2 设计模式的概念
1.1.3 设计模式的理解
1.1.4 设计模式的历史
1.2 设计模式有什么
1.2.1 设计模式的组成
1.2.2 设计模式的分类
1.3 设计模式的学习
1.3.1 为什么要学习设计模式
1.3.2 学习设计模式的层次
1.3.3 如何学习设计模式
1.4 本书的组织方式
1.4.1 本书所讲述的设计模式的提纲
1.4.2 每个模式的讲述结构
Q:什么是设计模式
A:是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案
第2章 简单工厂
2.1 场景问题
2.1.1 接口回顾
Q:接口用来干什么
A:通过使用接口,可以实现不相关类的相同行为,而不需考虑这些类之间的层次关系,接口就是实现类对外的外观
Q:什么时候选用接口?什么时候选用抽象类?
A: 1.优先使用接口
2.在既要定义子类的行为,又要为子类提供公共的功能时应选择抽象类.
2.1.2 面向接口编程
Q:什么是组件
A:从设计上讲,组件就是能完成一定功能的封装体.小到一个类,大到一个系统,都可以称为组件
2.1.3 不用模式的解决方案
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { Api api = new Implement(); api.test1("哈哈,不要紧张,只是个测试而已!"); } } public interface Api { void test1(string s); } public class Implement : Api { public void test1(string s) { Console.WriteLine("Now In Inmplement.The input s==" + s); } } }
2.1.4 有何问题
Q:有何问题
A:客户端不但知道了接口,同时还知道了具体的实现就是Implement.接口的思想是"封装隔离",而实现类Implement应该是被接口Api封装并同客户端隔离开的.
也就是说,客户端根本就不应该知道具体的实现类是Implement
2.2 解决方案
2.2.1 使用简单工厂来解决问题
Q:什么是简单工厂
A:提供一个创建对象实例的功能,而无须关心其具体实现.被创建实例的类型可以是接口,抽象类,也可以是具体的类.
2.2.2 简单工厂的结构和说明
[API]:定义客户所需要的功能接口
[Impl]:具体实现Api的实现类,可能会有多个
[Factory]:工厂,选择合适的实现类来创建Api接口对象
[Client]:客户端,通过Factory来获取Api接口对象,然后面向Api接口编程
2.2.3 简单工厂示例代码
using System; namespace Test2 { class Program { static void Main(string[] args) { Api api = Factory.createApi(1); api.operation("正在使用简单工厂"); } } public interface Api { void operation(string s); } public class ImplementA : Api { public void operation(string s) { Console.WriteLine("ImplementA s == " + s); } } public class ImplementB : Api { public void operation(string s) { Console.WriteLine("ImplementB s == " + s); } } public class Factory { public static Api createApi(int condition) { Api api = null; if (condition == 1) { api = new ImplementA(); } else if (condition == 2) { api = new ImplementB(); } return api; } } }
2.2.4 使用简单工厂重写示例
using System; namespace Test2 { class Program { static void Main(string[] args) { Api api = Factory.createApi(); api.test1("哈哈,不要紧张,只是个测试而已!"); } } public interface Api { void test1(string s); } public class Implement : Api { public void test1(string s) { Console.WriteLine(s); } } public class Factory { public static Api createApi() { return new Implement(); } } }
2.3 模式讲解
2.3.1 典型疑问
Q:把"new Implement()"这句话放到客户端和放到简单工厂里面有什么不同?
A:理解这个问题的重点就在于理解简单工厂所处的位置
2.3.2 认识简单工厂
Q:简单工厂创建对象的范围
A:虽然从理论上讲,简单工厂什么都能创建,但对于简单工厂可创建对象的范围,通常不要太大.建议控制在一个独立的组件级别或者一个模块级别,也就是一个组 件或模块简单工厂.
否则这个简单工厂类会职责不明,有点大杂烩的感觉
简单工厂的调用顺序图
2.3.3 简单工厂中方法的写法
2.3.4 可配置的简单工厂
2.3.5 简单工厂的优缺点
优点
1.帮助封装
简单工厂虽然很简单,但是非常友好地帮助我们实现了组件的封装,然后让组件外部能真正面向接口编程
2.解耦
通过简单工厂,实现了客户端和具体实现类的解耦
如果上面的例子,客户端根本就不知道具体是由谁来实现,也不知道具体是如何实现的,客户端只是通过工厂获取它需要的接口对象.
缺点
1.可能增加客户端的复杂度
如果通过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所代表的具体功能和含义,这样会增加客户端使用的难度,
也部分暴露了内部实现,这种情况可以选用可配置的方式来实现
2.不方便扩展子工厂
私有化简单工厂的构造方法,使用静态方法来创建接口,也就不能通过写简单工厂类的子类来改变创建接口的方法的行为了.不过通常情况下是不需要为简单工厂创建子类的
2.3.6 思考简单工厂
Q:简单工厂的本质
A:选择实现
注意简单工厂的重点在选择,实现是已经做好了的.就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现.简单工厂的目的在于为客户端 来选择相应的实现,
从而是的客户端和实现之间解耦.这样一来,具体实现发生了变化,就不用变动客户端了,这个变化会被简单工厂吸收和屏蔽掉
Q:何时选用简单工厂
A: 1.如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无需关心具体的实 现
2.如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的,不相关的对象,可以把对外创建的职责集中到一个简 单工厂来,从而实现集中管理和控制
2.3.7 相关模式
1.简单工厂和抽象工厂模式
简单工厂是用来选择实现的,可以选择任意接口的实现.一个简单工厂可以有多个用于选择并创建对象的方法,多个方法创建的对象可以有关系也可以没有关系
抽象工厂模式是用来选择产品簇的实现的,也就是说一般抽象工厂里面有多个用于选择并创建对象的方法,但是这些方法所创建的对象之间通常是有关系的
这些被创建的对象通常是构成一个产品簇所需要的部件对象
所以从某种意义上来说,简单工厂和抽象工厂是类似的,如果抽象工厂退化成为只有一个实现,不分层次,那么就相当于简单工厂了.
2.简单工厂和工厂方法模式
简单工厂和工厂方法模式也是非常类似的
工厂方法的本质也是用来选择实现的,跟简单工厂的区别在于工厂方法是把选择具体实现的功能延迟到子类去实现.
如果把工厂方法中选择的实现放到父类直接实现,那就等同于简单工厂
3.简单工厂和能创建对象实例的模式
简单工厂的本质是选择实现,所以它可以跟其他任何能够具体的创建对象实例的模式配合使用,比如:单例模式,原型模式,生成器模式等
第3章 外观模式(Facade)
3.1 场景问题
3.1.1 生活中的示例
外观模式在现实生活中的示例很多,比如组装电脑,通常会有两种方案
方案1:
方案2:
这个专业的装机公司就相当于本章的主角-外观模式(Facade).有了它,我们就不用自己去和众多卖配件的公司打交道,只需要跟装机公司交互就可以了,并将组装好的电脑返回给我们.
把上面的过程抽象一下,如果把电子市场看成是一个系统,而各个卖配件的公司看成是模块的话,就类似于出现了这样一种情况:客户端为了完成某个功能,需要去调用某个系统中的多个模块
把它们称为A模块,B模块和C模块.对于客户端而言,那就需要知道A,B,C这三个模块的功能,还需要知道如何组合这多个模块提供的功能来实现自己所需要的功能,非常麻烦.
3.1.2 代码生成的应用
3.1.3 不用模式的解决方案
using System; namespace Test2 { class Program { static void Main(string[] args) { new Presentation().generate(); new Business().generate(); new DAO().generate(); } } public class ConfigModel { private bool needGenPresentation = true; private bool needGenBusiness = true; private bool needGenDAO = true; public bool isNeedGenPresentation() { return needGenPresentation; } public void setNeedGenPresentation(bool needGenPresentation) { this.needGenPresentation = needGenPresentation; } public bool isNeedGenBusiness() { return needGenBusiness; } public void setNeedGenBusinees(bool needGenBusiness) { this.needGenBusiness = needGenBusiness; } public bool isNeedGenDAO() { return needGenDAO; } public void setNeedGenDAO(bool needGenDAO) { this.needGenDAO = needGenDAO; } } public class ConfigManager { private static ConfigManager manager = null; private static ConfigModel cm = null; private ConfigManager() { } public static ConfigManager getInstance() { if (manager == null) { manager = new ConfigManager(); cm = new ConfigModel(); } return manager; } public ConfigModel getConfigData() { return cm; } } public class Presentation { public void generate() { ConfigModel cm = ConfigManager.getInstance().getConfigData(); if (cm.isNeedGenPresentation()) { Console.WriteLine("正在生成表现层他代码文件"); } } } public class Business { public void generate() { ConfigModel cm = ConfigManager.getInstance().getConfigData(); if (cm.isNeedGenBusiness()) { Console.WriteLine("正在生成逻辑层代码文件"); } } } public class DAO { public void generate() { ConfigModel cm = ConfigManager.getInstance().getConfigData(); if (cm.isNeedGenDAO()) { Console.WriteLine("正在生成数据层代码文件"); } } } }
3.1.4 有何问题
Q:有何问题
A:客户端为了使用生成代码的功能,需要与生成代码子系统内部的多个模块交互
这对于客户端而言,是个麻烦,使得客户端不能简单地使用生成代码的功能.而且,如果其中的某个模块发生了变化,还可能会引起客户端也要随着变化
3.2 解决方案
3.2.1 使用外观模式来解决问题
Q:什么是外观模式
A:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
Q:什么是界面
A:这里提到的界面,主要指的是从一个组件外部来看这个组件,能够看到什么,这就是这个组件的界面,也就是所说的外观
比如,你从一个类外部来看这个类,那么这个类的public方法就是这个类的外观,因为你从类外部来看这个类,就能看到这些.
再比如,你从一个模块外部来看这个模块,那么这个模块对外的接口就是这个模块的外观,因为你只能看到这些接口,其他的模块内部实现的部分是被接口封装隔离了的.
Q:什么是接口
A:这里提到的接口,主要指的是外部和内部交互的这么一个通道,通常是指一些方法,可以是类的方法,也可以是interface的方法,也就是说,这里所说的接口,并不等价于interface,也有可能是一个类
3.2.2 外观模式的结构和说明
[Facade] 定义子系统的多个模块对外的高层接口,通常需要调用内部多个模块,从而把客户的请求代理给适当的子系统对象
[模块] 接受Facade对象的委派,真正实现功能,各个模块之间可能有交互
但是请注意,Facade对象知道各个模块,但是各个模块不应该知道Facade对象
3.2.3 外观模式示例代码
using System; namespace Test2 { class Program { static void Main(string[] args) { new Facade().test(); } } public interface AModuleApi { void testA(); } public class AModuleImplement : AModuleApi { public void testA() { Console.WriteLine("现在在A模块里面操作testA方法"); } } public interface BModuleApi { void testB(); } public class BModuleImplement : BModuleApi { public void testB() { Console.WriteLine("现在在B模块里面操作testB方法"); } } public interface CModuleApi { void testC(); } public class CModuleImplement : CModuleApi { public void testC() { Console.WriteLine("现在在C模块里面操作testC方法"); } } public class Facade { public void test() { AModuleApi a = new AModuleImplement(); a.testA(); BModuleApi b = new BModuleImplement(); b.testB(); CModuleApi c = new CModuleImplement(); c.testC(); } } }
3.2.4 使用外观模式重写示例
using System; namespace Test2 { class Program { static void Main(string[] args) { new Facade().generate(); } } public class ConfigModel { private bool needGenPresentation = true; private bool needGenBusiness = true; private bool needGenDAO = true; public bool isNeedGenPresentation() { return needGenPresentation; } public void setNeedGenPresentation(bool needGenPresentation) { this.needGenPresentation = needGenPresentation; } public bool isNeedGenBusiness() { return needGenBusiness; } public void setNeedGenBusinees(bool needGenBusiness) { this.needGenBusiness = needGenBusiness; } public bool isNeedGenDAO() { return needGenDAO; } public void setNeedGenDAO(bool needGenDAO) { this.needGenDAO = needGenDAO; } } public class ConfigManager { private static ConfigManager manager = null; private static ConfigModel cm = null; private ConfigManager() { } public static ConfigManager getInstance() { if (manager == null) { manager = new ConfigManager(); cm = new ConfigModel(); } return manager; } public ConfigModel getConfigData() { return cm; } } public class Presentation { public void generate() { ConfigModel cm = ConfigManager.getInstance().getConfigData(); if (cm.isNeedGenPresentation()) { Console.WriteLine("正在生成表现层他代码文件"); } } } public class Business { public void generate() { ConfigModel cm = ConfigManager.getInstance().getConfigData(); if (cm.isNeedGenBusiness()) { Console.WriteLine("正在生成逻辑层代码文件"); } } } public class DAO { public void generate() { ConfigModel cm = ConfigManager.getInstance().getConfigData(); if (cm.isNeedGenDAO()) { Console.WriteLine("正在生成数据层代码文件"); } } } public class Facade { public void generate() { new Presentation().generate(); new Business().generate(); new DAO().generate(); } } }
3.3 模式讲解
3.3.1 认识外观模式
Q:外观模式的目的
A:外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统
Q:使用外观和不使用外观相比有何变化
A:Facade位于由A,B,C模块组成的系统而不是客户端,相当于屏蔽了外部客户端和系统内部模块的交互
外观模式的调用顺序图
3.3.2 外观模式的实现
Q:Facade的方法实现
A:Facade的方法实现中,一般是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade的方法本身并不进行功能的处理,Facade的方法实现只是实现一个功能的组合调用
当然在Facade中实现一个逻辑处理也并不是不可以的,但是不建议这样做,因为这不是Facade的本意,也超出了Facade的边界
3.3.3 外观模式的优缺点
优点
1.松散耦合
外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护
2.简单易用
外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观交互就可以了
相当于外观类为外部客户端使用子系统提供了一站式服务
3.更好地划分访问的层次
通过合理使用Facade,可以帮助我们更好地划分访问的层次.有些方法是对系统外的,有些方法是系统内部使用的.把需要暴露给外部的功能集中到外观中
这样既方便客户端使用,也很好地隐藏了内部的细节
缺点
1.过多的或者是不合理的Facade也容易让人迷惑.到底是调用Facade好呢,还是直接调用模块好
3.3.4 思考外观模式
Q:外观模式的本质
A:封装交互,简化调用
Facade封装了子系统外部和子系统内部多个模块的交互过程,从而简化了外部的调用.通过外观,子系统为外部提供一些高层的接口,以方便它们的使用
Q:对设计原则的体现
A:外观模式很多地体现了"最少知识原则"
Q:何时选用外观模式
A: 1.如果你希望为一个复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式.使用外观对象来实现大部分客户需要的功能,从而简化客户的使用
2.如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式
3.如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样可以简化层间调用,也可以松散层次之间的依赖关系
3.3.5 相关模式
1.外观模式和中介者模式
这两个模式非常类似,但是却有本质的区别
中介者模式主要用来封装多个对象之间相互的交互,多用在系统内部的多个模块之间;而外观模式封装的是单向的交互,是从客户端访问系统的调用,没有从系统中来访问客户端的调用.
在中介者模式的实现里面,是需要实现具体的交互功能的;而外观模式的实现里面,一般是组合调用或是转调内部实现的功能,通常外观模式本身并不是实现这些功能
中介者模式的目的主要是松散多个模块之间的耦合,把这些耦合关系全部放到中介者中去实现;而外观模式的目的是简化客户端的调用,这点和中介者模式也不同
2.外观模式和单例模式
通常一个子系统只需要一个外观实例,所以外观模式可以和单例模式组合使用,把Facade类实现称为单例.
3.外观模式和抽象工厂模式
外观模式的外观类通常需要和系统内部的多个模块交互,每个模块一般都有自己的接口,所以在外观类的具体实现里面,需要获取这些接口,然后组合这些接口来完成客户端的功能
那么怎么获取这些接口呢?就可以和抽象工厂一起使用,外观类通过抽象工厂来获取所需要的接口,而抽象工厂也可以把模块内部的实现对Facade进行屏蔽,也就是说Facade也仅
仅只是知道它从模块中获取它需要的功能,模块内部的细节,Facade也不知道
第4章 适配器模式(Adapter)
4.1 场景问题
4.1.1 装配电脑的例子
4.1.2 同时支持数据库和文件的日志管理
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; namespace Test2 { class Program { static void Main(string[] args) { LogModel lml = new LogModel(); lml.setLogId("001"); lml.setOperateUser("admin"); lml.setOperateTime("2010-03-02 10:08:18"); lml.setLogContent("这是一个测试"); List<LogModel> list = new List<LogModel>(); list.Add(lml); LogFileOperateApi api = new LogFileOperate(""); api.writeLogFile(list); string readLog = api.readLogFile(); Console.WriteLine(readLog); } } public class LogModel { private string logId; private string operateUser; private string operateTime; private string logContent; public string getLogId() { return logId; } public void setLogId(string logId) { this.logId = logId; } public string getOperateUser() { return operateUser; } public void setOperateUser(string operateUser) { this.operateUser = operateUser; } public string getOperateTime() { return operateTime; } public void setOperateTime(string operateTime) { this.operateTime = operateTime; } public string getLogContent() { return logContent; } public void setLogContent(string logContent) { this.logContent = logContent; } public override string ToString() { return "logId=" + logId + ", operateUser=" + operateUser + ", operateTime=" + operateTime + ",logContent=" + logContent; } } public interface LogFileOperateApi { string readLogFile(); void writeLogFile(List<LogModel> list); } public class LogFileOperate : LogFileOperateApi { private string logFilePathName = "AdapterLog.log"; public LogFileOperate(string logFilePathName) { if (logFilePathName != null && logFilePathName.Trim().Length > 0) { this.logFilePathName = logFilePathName; } } public string readLogFile() { List<LogModel> list = null; StreamReader sr = new StreamReader(logFilePathName,Encoding.Default); string line = sr.ReadLine(); return line; } public void writeLogFile(List<LogModel> list) { FileStream fs = new FileStream(logFilePathName,FileMode.Create); StreamWriter sw = new StreamWriter(fs); foreach (LogModel logModel in list) { sw.Write(logModel.ToString()); } sw.Flush(); sw.Close(); fs.Close(); } } public interface LogDbOperateApi { void createLog(LogModel lm); void updateLog(LogModel lm); void removeLog(LogModel lm); List<LogModel> getAllLog(); } }
4.1.3 有何问题
4.2 解决方案
4.2.1 使适配器模式来解决问题
4.2.2 适配器模式的结构和说明
[Client] 客户端,调用自己需要的领域接口Target
[Target] 定义客户端需要的跟特定领域相关的接口
[Adaptee] 已经存在的接口,通常能满足客户端的相关功能,但是接口与客户端要求的特定领域接口不一致,需要被适配
[Adapter] 适配器,把Adapter适配为Client需要的Target
4.2.3 适配器模式示例代码
using System; namespace Test2 { class Program { static void Main(string[] args) { Adaptee adaptee = new Adaptee(); Target target = new Adapter(adaptee); target.request(); } } public interface Target { void request(); } public class Adapter : Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } } public class Adaptee { public void specificRequest() { } } }
4.2.4 使用适配器模式来实现示例
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; namespace Test2 { class Program { static void Main(string[] args) { LogModel lml = new LogModel(); lml.setLogId("001"); lml.setOperateUser("admin"); lml.setOperateTime("2010-03-02 10:08:18"); lml.setLogContent("这是一个测试"); List<LogModel> list = new List<LogModel>(); list.Add(lml); LogFileOperateApi logFileApi = new LogFileOperate(""); LogDbOperateApi api = new Adapter(logFileApi); api.createLog(); api.updateLog(); api.removeLog(); api.getAllLog(); } } public class LogModel { private string logId; private string operateUser; private string operateTime; private string logContent; public string getLogId() { return logId; } public void setLogId(string logId) { this.logId = logId; } public string getOperateUser() { return operateUser; } public void setOperateUser(string operateUser) { this.operateUser = operateUser; } public string getOperateTime() { return operateTime; } public void setOperateTime(string operateTime) { this.operateTime = operateTime; } public string getLogContent() { return logContent; } public void setLogContent(string logContent) { this.logContent = logContent; } public override string ToString() { return "logId=" + logId + ", operateUser=" + operateUser + ", operateTime=" + operateTime + ",logContent=" + logContent; } } public interface LogFileOperateApi { string readLogFile(); void writeLogFile(List<LogModel> list); } public class LogFileOperate : LogFileOperateApi { private string logFilePathName = "AdapterLog.log"; public LogFileOperate(string logFilePathName) { if (logFilePathName != null && logFilePathName.Trim().Length > 0) { this.logFilePathName = logFilePathName; } } public string readLogFile() { List<LogModel> list = null; StreamReader sr = new StreamReader(logFilePathName,Encoding.Default); string line = sr.ReadLine(); return line; } public void writeLogFile(List<LogModel> list) { FileStream fs = new FileStream(logFilePathName,FileMode.Create); StreamWriter sw = new StreamWriter(fs); foreach (LogModel logModel in list) { sw.Write(logModel.ToString()); } sw.Flush(); sw.Close(); fs.Close(); } } public interface LogDbOperateApi { void createLog(LogModel lm); void updateLog(LogModel lm); void removeLog(LogModel lm); List<LogModel> getAllLog(); } public class Adapter : LogDbOperateApi { private LogFileOperateApi adaptee; public Adapter(LogFileOperateApi adaptee) { this.adaptee = adaptee; } public void createLog(LogModel lm) { List<LogModel> list = new List<LogModel>(); adaptee.writeLogFile(list); } public List<LogModel> getAllLog() { return adaptee.readLogFile(); } public void removeLog(LogModel lm) { List<LogModel> list = new List<LogModel>(); list.Remove(lm); } public void updateLog(LogModel lm) { } } }
4.3 模式讲解
4.3.1 认识适配器模式
适配器模式的调用顺序示意图
4.3.2 适配器模式的实现
4.3.3 双向适配器
4.3.4 对象适配器和类适配器
4.3.5 适配器模式的优缺点
优点:
1.更好的复用性
如果功能是已经有了的,只是接口不兼容,那么通过适配器模式就可以让这些功能得到更好的复用.
2.更好的可扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能
缺点
1.过多地使用适配器,会让系统非常零乱,不容易整体进行把握
4.3.6 思考适配器模式
Q:适配器模式的本质
A:转换匹配,复用功能
适配器通过转换调用已有的实现,从而能把已有的实现匹配成需要的接口,使之能满足客户端的需要.也就是说转换匹配是手段,而复用已有的功能才是目的
在进行转换匹配的过程中,适配器还可以在转换调用的前后实现一些功能处理,也就是实现智能的适配
Q:何时选用适配器模式
A: 1.如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口
2.如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况可以使用适配器模式,到时候需要什么就适配什么
3.如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况可以选用对象适配器,直接适配这些子类的父类就可以了.
4.3.7 相关模式
1.适配器模式与桥接模式
其实这两个模式除了结构略为相似外,功能上完全不同
适配器模式是把两个或者多个接口的功能进行转换匹配,而桥接模式是让接口和实现部分相分离,以便它们可以相对独立地变化
2.适配器模式与装饰模式
两个模式有一个很大的不同:一般适配器适配过后是需要改变接口的,如果不改接口就没有必要适配了,而装饰模式是不改接口的,无论多少层装饰都是一个接口
因此装饰模式可以很容易地支持递归组合,而适配器就做不到.每次的接口不同,无法递归
3.适配器和代理模式
适配器模式可以和代理模式组合使用,在实现适配器的时候,可以通过代理来调用Adaptee,这样可以获得更大的灵活性.
4.适配器模式和抽象工厂模式
在适配器实现的时候,通常需要得到被适配的对象.如果被适配的是一个接口,那么就可以结合一些可以创造对象实例的设计模式,来得到被适配的对象示例
比如抽象工厂模式,单例模式,工厂方法模式等.
第5章 单例模式(Singleton)
5.1 场景问题
5.1.1 读取配置文件的内容
5.1.2 不用模式的解决方案
using System; namespace Test2 { class Program { static void Main(string[] args) { AppConfig config = new AppConfig(); string paramA = config.getParameterA(); string paramB = config.getParameterB(); Console.WriteLine("paramA=" + paramA + ",paramB=" + paramB); } } public class AppConfig { private string parameterA = "a"; private string parameterB = "b"; public AppConfig() { readConfig(); } public string getParameterA() { return parameterA; } public string getParameterB() { return parameterB; } private void readConfig() { Console.WriteLine("readConfig"); } } }
5.1.3 有何问题
Q:在一个系统运行期间,某个类只需要一个类实例j就可以了,那么应该怎样实现呢?
5.2 解决方案
5.2.1 使用单例模式来解决问题
Q:单例模式的定义
A:保证一个类仅有一个实例,并提供一个访问它的全局访问点
5.2.2 单例模式的结构和说明
[Singleton] 负责创建Singleton类自己的唯一实例,并提供一个getInstance方法,让外部来访问这个类的唯一实例
5.2.3 单例模式示例代码
// 懒汉 public class Singleton { private static Singleton uniqueInstance = null; private Singleton() { } public static Singleton getInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } public void singletonOperation() { } private string singletonData; public string getSingletonData() { return singletonData; } } // 饿汉 public class Singleton { private static Singleton uniqueInstance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return uniqueInstance; } public void singletonOperation() { } private string singletonData; public string getSingletonData() { return singletonData; } }
5.2.4 使用单例模式重写示例
5.3 模式讲解
5.3.1 认识单例模式
1.单例模式的功能
单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点
2.单例模式的范围
3.单例模式的命名
5.3.2 懒汉式和饿汉式实现
懒汉式调用顺序示意图
饿汉式调用顺序示意图
5.3.3 延迟加载的思想
5.3.4 缓存的思想
5.3.5 Java中缓存的基本实现
5.3.6 利用缓存来实现单例模式
5.3.7 单例模式的优缺点
优点:
1.时间和空间
懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间.当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间.
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间
2.线程安全
5.3.8 在Java中一种更好的单例实现方式
5.3.9 单例和枚举
5.3.10 思考单例模式
Q:单例模式的本质
A:控制实例数目
Q:何时选用单例模式
A:当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题
5.3.11 相关模式
很多模式都可以使用单例模式,只要这些模式中的某个类,需要控制实例为一个的时候,就可以很自然地使用上单例模式.比如抽象工厂方法中的具体工厂类就通常是一个单例
第6章 工厂方法模式(Factory Method)
6.1 场景问题
6.1.1 导出数据的应用框架
6.1.2 框架的基础知识
Q:什么是框架
A:简单点说,框架就是能完成一定功能的半成品软件
就其本质而言,框架是一个软件,而且是一个半成品的软件.所谓半成品,就是还不能完全实现用户需要的功能.框架只是实现用户需要的功能的一部分,还需要进一步加工,才能成为一个满足用户需要的,完整的软件.因此框架级的软件,它的主要客户是开发人员,而不是最终用户
Q:框架和设计模式的关系
A: 1.设计模式比框架更抽象
框架已经是实现出来的软件了,虽然只是个半成品的软件,但毕竟是已经实现出来的了;而设计模式的重心还在于解决问题的方案上,也就是还停留在思想的层面上.因此设计模式比框架更为抽象
2.设计模式是比框架更小的体系结构元素
如上所述,框架是已经实现出来的软件,并实现了一些的功能,因此一个框架通常会包含多个设计模式的应用
3.框架比设计模式更加特里化
框架是完成一定功能的半成品软件,也就是说,框架的目的很明确,就是要解决某一个领域的某些问题,那是很具体的功能.不同的领域实现出来的框架是不一样的
而设计模式还停留在思想的层面,只要相应的问题适合某个设计模式来解决,在不同的领域都可以应用
因此框架总是针对特定领域的,而设计模式更加注重从思想上,方法上来解决问题,更加通用化
6.1.3 有何问题
6.2 解决方案
6.2.1 使用工厂方法模式来解决问题
Q:工厂方法模式的定义
A:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到其子类.
6.2.2 工厂方法模式的结构和说明
[Product] 定义工厂方法所创建的对象的接口,也就是实际需要使用的对象的接口
[ConcreteProduct] 具体的Product接口的实现对象
[Creator] 创建器,声明工厂方法,工厂方法通常会返回一个Product类型的实例对象,而且多是抽象方法.也可以在Creator里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product类型的实例对象
[ConcreteCreator] 具体的创建对象,覆盖实现Creator定义的工厂方法,返回具体的Product实例
6.2.3 工厂方法模式示例代码
using System; namespace Test2 { class Program { static void Main(string[] args) { } public interface Product { } public class ConcreteProduct : Product { } public abstract class Creator { public abstract Product factoryMethod(); public void someOperation() { Product product = factoryMethod(); } } public class ConcreteCreator : Creator { public override Product factoryMethod() { return new ConcreteProduct(); } } } }
6.2.4 使用工厂方法模式来实现示例
using System; using System.Runtime.CompilerServices; namespace Test2 { class Program { static void Main(string[] args) { ExportOperate operate = new ExportDBFileOperate(); operate.export("测试数据"); } public interface ExportFileApi { bool export(string data); } public class ExportTxtFile : ExportFileApi { public bool export(string data) { Console.WriteLine("导出数据" + data + "到文本文件"); return true; } } public class ExportDBFile : ExportFileApi { public bool export(string data) { Console.WriteLine("导出数据" + data + "到数据库备份文件"); return true; } } public abstract class ExportOperate { public bool export(string data) { ExportFileApi api = factoryMethod(); return api.export(data); } protected abstract ExportFileApi factoryMethod(); } public class ExportTxtFileOperation : ExportOperate { protected override ExportFileApi factoryMethod() { return new ExportTxtFile(); } } public class ExportDBFileOperate : ExportOperate { protected override ExportFileApi factoryMethod() { return new ExportDBFile(); } } } }
6.3 模式讲解
6.3.1 认识工厂方法模式
6.3.2 工厂方法模式与IoC/DI
IoC-Inversion of Control,控制反转
DI-Dependency Injection,依赖注入
Q:参与者都有谁?
A:一般都有三方参与者,一个是某个对象,另一个是IoC/DI的容器,还有一个是某个对象的外部资源
Q:谁依赖于谁
A:当然是某个对象依赖于IoC/DI的容器.
Q:为什么需要依赖
A:对象需要IoC/DI的容器l来提供对象需要的外部资源
Q:谁注入于谁
A:很明显是IoC/DI的容器注入某个对象
Q:到底注入什么
A:就是注入某个对象所需要的外部资源
Q:谁控制谁
A:当然是IoC/DI的容器来控制对象了
Q:控制什么
A:主要是控制对象实例的创建
Q:为什么叫反转
A:反转是相对于正向而言的.
正向:在A类中主动获取所需要的外部资源C,这种情况被称为正向的.
反向:就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向地注入到A类中
Q:依赖注入和控制反转是同一概念吗?
A:依赖注入和控制反转是对同一事情的不同m描述.
依赖注入:应用程序依赖容器创建并注入它所需要的外部资源
控制反转:容器控制应用程序,由容器反向地向应用程序注入其所需要的外部资源
using System; namespace Test2 { class Program { static void Main(string[] args) { } } public interface C { void tc(); } public class A { private C c = null; public void setC(C c) { this.c = c; } public void t1() { c.tc(); } } }
6.3.3 平行的类层次结构
Q:平行的类层次结构的含义
A:简单点说,假如有两个类层次结构,其中一个类层次中的每个类在另一个类层次中都有一个对应的类的结构,就被称为平行的类层次结构
硬盘对象是一个类层次,硬盘的行为也是一个类层次,而且两个类层次中的类是对应的.台式机希捷硬盘对象就对应着硬盘行为里面的台式机希捷硬盘的行为;笔记本IBM硬盘就对应着笔记本IBM硬盘的行为,这就是一种典型的平行的类层次结构.
这种平行的类层次结构用来干什么呢?主要用来把一个类层次中的某些行为分离出来,让类层次中的类把原来属于自己的职责,委托给分离出来的类去实现,从而使得类层次本身变得更简单,更容易扩展和复用
一般来讲,分离出去的这些类的行为,会对应着类层次结构来组织,从而形成一个新的类层次结构,相当于原来对象行为的类层次结构,而这个层次结构和原来的类层次结构是存在对应关系的,因此被称为平行的类层次结构
6.3.4 参数化工厂方法
Q:什么是参数化工厂方法
A:通过给工厂方法传递参数,让工厂方法根据参数的不同来创建不同的产品对象
6.3.5 工厂方法模式的优缺点
优点:
1.可以在不知道具体实现的情况下编程
工厂方法模式可以让你在实现功能的时候,如果需要某个产品对象,只需要使用产品的接口即可,而无需关心具体的实现.选择具体实现的任务延迟到子类去完成
2.更容易扩展对象的新版本
工厂方法给子类提供了一个挂钩,使得扩展新的对象版本变得非常容易.比如上面示例的参数化工厂方法实现中,扩展一个新的导出xml文件格式的实现,已有的代码都不会改变,只要新加入一个子类来提供新的工厂方法实现,然后在客户端使用这个新的子类即可.
3.连接平行的类层次
缺点:
1.具体产品对象和工厂方法的耦合性
在工厂方法模式中,工厂方法是需要创建产品对象的,也就是需要选择具体的产品对象,并创建它们的实例,因此具体产品对象和工厂方法是耦合的.
6.3.6 思考工厂方法模式
Q:工厂方法模式的本质
A:延迟到子类来选择实现
Q:对设计原则的体现
A:工厂方法模式很好地体现了"依赖倒置原则"
Q:何时选用工厂方法模式
A: 1.如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类中去实现
2.如果一个类本身就希望由它的子类来创建所需的对象的时候,应该使用工厂方法模式
6.3.7 相关模式
1.工厂方法模式和抽象工厂模式
这两个模式可以组合使用,具体的放到抽象工厂模式中去讲
2.工厂方法模式和模板方法模式
这两个模式外观类似,都有一个抽象类,然后由子类来提供一些实现,但是工厂方法模式的子类专注的是创建产品对象,而模板方法模式的子类专注的是为固定的算法骨架提供某些步骤的实现
这两个模式可以组合使用,通常在模板方法模式里面,使用工厂方法来创建模板方法需要的对象
第7章 抽象工厂模式(Abstract Factory)
7.1 场景问题
7.1.1 选择组装电脑的配件
对于装机工程师而言,他只知道组装一台电脑,需要相应的配件,但是具体使用什么样的配件,还得由客户说了算.也就是说装机工程师只是负责组装,而客户负责选择装配所需要的具体的配件.因此,当装机工程师为不同的客户组装电脑时,只需要按照客户的装机方案,去获取相应的配件,然后组装即可
7.1.2 不用模式的解决方案
using System; namespace Test2 { class Program { static void Main(string[] args) { ComputerEngineer engineer = new ComputerEngineer(); engineer.makeComputer(1, 1); } } public interface CPUApi { void calculate(); } public class InteCPU : CPUApi { private int pins = 0; public InteCPU(int pins) { this.pins = pins; } public void calculate() { Console.WriteLine("now in Intel CPU,pins = " + pins); } } public class AMDCPU : CPUApi { private int pins = 0; public AMDCPU(int pins) { this.pins = pins; } public void calculate() { Console.WriteLine("now in AMD CPU,pins = " + pins); } } public interface MainboardApi { void installCPU(); } public class GAMainboard : MainboardApi { private int cpuHoles = 0; public GAMainboard(int cpuHoles) { this.cpuHoles = cpuHoles; } public void installCPU() { Console.WriteLine("now in GAMainboard,cpuHoles = " + cpuHoles); } } public class MSIMainboard : MainboardApi { private int cpuHoles = 0; public MSIMainboard(int cpuHoles) { this.cpuHoles = cpuHoles; } public void installCPU() { Console.WriteLine("now in MSIMainboard,cpuHoles = " + cpuHoles); } } public class CPUFactory { public static CPUApi createCPUApi(int type) { CPUApi cpu = null; if (type == 1) { cpu = new InteCPU(1156); } else if (type == 2) { cpu = new AMDCPU(939); } return cpu; } } public class MainboardFactory { public static MainboardApi createMainboardApi(int type) { MainboardApi mainboard = null; if (type == 1) { mainboard = new GAMainboard(1156); } else if (type == 2) { mainboard = new MSIMainboard(939); } return mainboard; } } public class ComputerEngineer { private CPUApi cpu = null; private MainboardApi mainboard = null; public void makeComputer(int cpuType, int mainboardType) { // 1:首先准备好装机所需要的配件 prepareHardwares(cpuType,mainboardType); // 2:组装机器 // 3:测试机器 // 4:交付客户 } private void prepareHardwares(int cpuType, int mainboardType) { this.cpu = CPUFactory.createCPUApi(cpuType); this.mainboard = MainboardFactory.createMainboardApi(mainboardType); this.cpu.calculate(); this.mainboard.installCPU(); } } }
7.1.3 有何问题
Q:有何问题
A:这些CPU对象和主板对象其实是有关系的,是需要相互匹配的.而在上面的实现中,并没有维护这种关联关系,CPU和主板是由客户端随意选择的
7.2 解决方案
7.2.1 使用抽象工厂模式来解决问题
Q:抽象工厂模式的定义
A:提供一个创建一些列相关或相互依赖对象的接口,而无需指定它们具体的类
7.2.2 抽象工厂模式的结构和说明
[AbstractFactory] 抽象工厂,定义创建一系列产品对象的操作接口
[ConcreteFactory] 具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建
[AbstractProduct] 定义一类产品对象的接口
[ConcreteProduct] 具体的产品实现对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型的对象
[Client] 客户端,主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程,以实现需要的功能
7.2.3 抽象工厂模式示例代码
using System; namespace Test2 { class Program { static void Main(string[] args) { AbstractFactory af = new ConcreateFactory1(); af.createProductA(); af.createProductB(); } } public interface AbstractProductA { } public class ProductA1 : AbstractProductA { } public class ProductA2 : AbstractProductA { } public interface AbstractProductB { } public class ProductB1 : AbstractProductB { } public class ProductB2 : AbstractProductB { } public interface AbstractFactory { AbstractProductA createProductA(); AbstractProductB createProductB(); } public class ConcreateFactory1 : AbstractFactory { public AbstractProductA createProductA() { return new ProductA1(); } public AbstractProductB createProductB() { return new ProductB1(); } } public class ConcreateFactory2 : AbstractFactory { public AbstractProductA createProductA() { return new ProductA2(); } public AbstractProductB createProductB() { return new ProductB2(); } } }
7.2.4 使用抽象工厂模式重写示例
using System; namespace Test2 { class Program { static void Main(string[] args) { ComputerEngineer engineer = new ComputerEngineer(); AbstractFactory schema = new Schema1(); engineer.makeComputer(schema); } } public interface CPUApi { void calculate(); } public class InteCPU : CPUApi { private int pins = 0; public InteCPU(int pins) { this.pins = pins; } public void calculate() { Console.WriteLine("now in Intel CPU,pins = " + pins); } } public class AMDCPU : CPUApi { private int pins = 0; public AMDCPU(int pins) { this.pins = pins; } public void calculate() { Console.WriteLine("now in AMD CPU,pins = " + pins); } } public interface MainboardApi { void installCPU(); } public class GAMainboard : MainboardApi { private int cpuHoles = 0; public GAMainboard(int cpuHoles) { this.cpuHoles = cpuHoles; } public void installCPU() { Console.WriteLine("now in GAMainboard,cpuHoles = " + cpuHoles); } } public class MSIMainboard : MainboardApi { private int cpuHoles = 0; public MSIMainboard(int cpuHoles) { this.cpuHoles = cpuHoles; } public void installCPU() { Console.WriteLine("now in MSIMainboard,cpuHoles = " + cpuHoles); } } public interface AbstractFactory { CPUApi createCPUApi(); MainboardApi createMainboardApi(); } public class Schema1 : AbstractFactory { public CPUApi createCPUApi() { return new InteCPU(1156); } public MainboardApi createMainboardApi() { return new GAMainboard(1156); } } public class Schema2 : AbstractFactory { public CPUApi createCPUApi() { return new AMDCPU(939); } public MainboardApi createMainboardApi() { return new MSIMainboard(939); } } public class ComputerEngineer { private CPUApi cpu = null; private MainboardApi mainboard = null; public void makeComputer(AbstractFactory schema) { // 1:首先准备好装机所需要的配件 prepareHardwares(schema); // 2:组装机器 // 3:测试机器 // 4:交付客户 } private void prepareHardwares(AbstractFactory schema) { this.cpu = schema.createCPUApi(); this.mainboard = schema.createMainboardApi(); this.cpu.calculate(); this.mainboard.installCPU(); } } }
7.3 模式讲解
7.3.1 认识抽象工厂模式
Q:抽象工厂模式的功能
A:抽象工厂的功能是为一系列相关对象或相互依赖对象创建一个接口.一定要注意,这个接口内的方法不是任意堆砌的,而是一系列相关或相互依赖的方法.从某种意义上看,抽象工厂其实是一个产品系列,或者是产品簇
7.3.2 定义可扩展的工厂
7.3.3 抽象工厂模式和DAO
7.3.4 抽象工厂模式的优缺点
优点:
1.分类接口和实现
客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已.也就是说,客户端从具体的产品实现中解耦
2.使得切换产品簇变得容易
一个具体的工厂实现代表的是一个产品簇.客户端选用不同的工厂实现,就相当于是在切换不同的产品簇
缺点:
1.不太容易扩展新的产品
如果需要给整个产品簇添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类.
2.容易造成类层次复杂
7.3.5 思考抽象工厂模式
Q:抽象工厂模式的本质
A:选择产品簇的实现
7.3.6 相关模式
1:抽象工厂模式和工厂方法模式
工厂方法模式一般是针对单独的产品对象的创建,而抽象工厂模式注重产品簇对象的创建,这是它们的区别
如果把抽象工厂创建的产品簇简化,整个产品簇就只有一个产品,那么这个时候的抽象工厂跟工厂方法是差不多的,也就是抽象工厂可以退化成工厂方法,而工厂方法又可以退化成简单工厂,这是它们的联系
在抽象工厂的实现中,还可以使用工厂方法来提供抽象工厂的具体实现,也就是说它们可以组合使用
2.抽象工厂模式和单例模式
在抽象工厂模式里面,具体的工厂实现,在整个应用中,通常一个产品系列只需要一个实例就可以了,因此可以把具体的工厂实现成为单例.
第8章 生成器模式(Builder)
8.1 场景问题
8.1.1 继续导出数据的应用框架
8.1.2 不用模式的解决方案
using System; using System.Collections.Generic; using System.Text; namespace Test2 { class Program { static void Main(string[] args) { ExportHeaderModel ehm = new ExportHeaderModel(); ehm.setDepId("一分公司"); ehm.setExportDate("2010-05-18"); Dictionary<string,List<ExportDataModel>> mapData = new Dictionary<string, List<ExportDataModel>>(); List<ExportDataModel> col = new List<ExportDataModel>(); ExportDataModel edm1 = new ExportDataModel(); edm1.setProductId("产品001号"); edm1.setPrice(100); edm1.setAmount(80); ExportDataModel edm2 = new ExportDataModel(); edm2.setProductId("产品002号"); edm2.setPrice(99); edm2.setAmount(55); col.Add(edm1); col.Add(edm2); mapData["销售记录表"] = col; ExportFooterModel efm = new ExportFooterModel(); efm.setExportUser("张三"); ExportToTxt toTxt = new ExportToTxt(); toTxt.export(ehm,mapData,efm); ExportToXml toXml = new ExportToXml(); toXml.export(ehm,mapData,efm); } } public class ExportHeaderModel { private string depId; private string exportDate; public string getDepId() { return depId; } public void setDepId(string depId) { this.depId = depId; } public string getExportDate() { return exportDate; } public void setExportDate(string exporteDate) { this.exportDate = exporteDate; } } public class ExportDataModel { private string productId; private double price; private double amount; public string getProductId() { return productId; } public void setProductId(string productId) { this.productId = productId; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } } public class ExportFooterModel { private string exportUser; public string getExportUser() { return exportUser; } public void setExportUser(string exportUser) { this.exportUser = exportUser; } } public class ExportToTxt { public void export(ExportHeaderModel ehm, Dictionary<string, List<ExportDataModel>> mapData, ExportFooterModel efm) { StringBuilder sb = new StringBuilder(); sb.Append(ehm.getDepId() + "," + ehm.getExportDate() + "\n"); foreach (string mapDataKey in mapData.Keys) { sb.Append(mapDataKey + "\n"); foreach (ExportDataModel exportDataModel in mapData.GetValueOrDefault(mapDataKey)) { sb.Append(exportDataModel.getProductId() + "," + exportDataModel.getPrice() + "," + exportDataModel.getAmount() + "\n"); } } sb.Append(efm.getExportUser()); Console.WriteLine("输出到文本文件的内容: \n" + sb); } } public class ExportToXml { public void export(ExportHeaderModel ehm, Dictionary<string, List<ExportDataModel>> mapData, ExportFooterModel efm) { StringBuilder buffer = new StringBuilder(); buffer.Append("<?xml version='1.0' encoding='gb2312'?>\n"); buffer.Append("<Report>\n"); buffer.Append(" <Header>\n"); buffer.Append(" <DepId>" + ehm.getDepId() + "</DepId>\n"); buffer.Append(" <ExportDate>" + ehm.getExportDate() + "</ExportDate>\n"); buffer.Append(" </Header>\n"); buffer.Append(" <Body>\n"); foreach (string mapDataKey in mapData.Keys) { buffer.Append(" <Datas TableName=\"" + mapDataKey + "\">\n"); foreach (ExportDataModel exportDataModel in mapData.GetValueOrDefault(mapDataKey)) { buffer.Append(" <Data>\n"); buffer.Append(" <ProductId>" + exportDataModel.getProductId() + "</ProductId>\n"); buffer.Append(" <Price>" + exportDataModel.getPrice() + "</Price>\n"); buffer.Append(" <Amount>" + exportDataModel.getAmount() + "</Amount>\n"); buffer.Append(" </Data>\n"); } buffer.Append(" </Datas>\n"); } buffer.Append(" </Body>\n"); buffer.Append(" <Footer>\n"); buffer.Append(" <ExportUser>" + efm.getExportUser() + "</ExportUser>\n"); buffer.Append(" </Footer>\n"); buffer.Append("</Report>\n"); Console.WriteLine("输出到XML文件的内容: \n" + buffer); } } }
8.1.3 有何问题
Q:有何问题
A:对于不同的输出格式,处理步骤是一样的,但是每步的具体实现是不一样的
1.先拼接文件头的内容
2.然后拼接文件体的内容
3.再拼接文件尾的内容
4.最后把拼接好的内容输出去成为文件
构建每种输出格式的文件内容的时候,都会重复这几个处理步骤,应该提炼出来,形成公共的处理过程
今后可能会有很多不同输出格式的要求,这就需要在处理过程不变的情况下,能方便地切换不同的输出格式的处理
换句化来说,也就是构建每种格式的数据文件处理过程,应该和具体的步骤实现分开,这样就能够复用处理过程,而且能很容易地切换不同地输出格式.
8.2 解决方案
8.2.1 使用生成器模式来解决问题
Q:生成器模式的定义
A:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.
8.2.2 生成器模式的结构和说明
[Builder] 生成器接口,定义创建一个Product对象所需要的各个部件的操作
[ConcreteBuilder] 具体的生成器实现,实现各个部件的创建,并负责组装Product对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法
[Director] 指导者,也被称为导向者,主要用来使用Builder接口,以一个统一的过程来构建所需要的Product对象
[Product] 产品,表示被生成器构建的复杂对象,包含多个部件
8.2.3 生成器模式示例代码
using System; namespace Test2 { class Program { static void Main(string[] args) { } } public interface Builder { void buildPart(); } public class ConcreteBuilder : Builder { private Product resultProduct; public Product getResult() { return resultProduct; } public void buildPart() { } } public interface Product { } public class Director { private Builder builder; public Director(Builder builder) { this.builder = builder; } public void construct() { builder.buildPart(); } } }
8.2.4 使用生成器模式重写示例
using System; using System.Collections.Generic; using System.Text; namespace Test2 { class Program { static void Main(string[] args) { ExportHeaderModel ehm = new ExportHeaderModel(); ehm.setDepId("一分公司"); ehm.setExportDate("2010-05-18"); Dictionary<string,List<ExportDataModel>> mapData = new Dictionary<string, List<ExportDataModel>>(); List<ExportDataModel> col = new List<ExportDataModel>(); ExportDataModel edm1 = new ExportDataModel(); edm1.setProductId("产品001号"); edm1.setPrice(100); edm1.setAmount(80); ExportDataModel edm2 = new ExportDataModel(); edm2.setProductId("产品002号"); edm2.setPrice(99); edm2.setAmount(55); col.Add(edm1); col.Add(edm2); mapData["销售记录表"] = col; ExportFooterModel efm = new ExportFooterModel(); efm.setExportUser("张三"); TxtBuilder txtBuilder = new TxtBuilder(); Director director = new Director(txtBuilder); director.constructor(ehm,mapData,efm); Console.WriteLine("输出到文本文件的内容: \n" + txtBuilder.getResult()); XmlBuilder xmlBuilder = new XmlBuilder(); Director director2 = new Director(xmlBuilder); director2.constructor(ehm,mapData,efm); Console.WriteLine("输出到XMl文件的内容: \n" + xmlBuilder.getResult()); } } public class ExportHeaderModel { private string depId; private string exportDate; public string getDepId() { return depId; } public void setDepId(string depId) { this.depId = depId; } public string getExportDate() { return exportDate; } public void setExportDate(string exporteDate) { this.exportDate = exporteDate; } } public class ExportDataModel { private string productId; private double price; private double amount; public string getProductId() { return productId; } public void setProductId(string productId) { this.productId = productId; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } } public class ExportFooterModel { private string exportUser; public string getExportUser() { return exportUser; } public void setExportUser(string exportUser) { this.exportUser = exportUser; } } public interface Builder { void buildHeader(ExportHeaderModel ehm); void buildBody(Dictionary<string, List<ExportDataModel>> mapData); void buildFooter(ExportFooterModel efm); } public class TxtBuilder : Builder { private StringBuilder buffer = new StringBuilder(); public void buildHeader(ExportHeaderModel ehm) { buffer.Append(ehm.getDepId() + "," + ehm.getExportDate() + "\n"); } public void buildBody(Dictionary<string, List<ExportDataModel>> mapData) { foreach (string mapDataKey in mapData.Keys) { buffer.Append(mapDataKey + "\n"); foreach (ExportDataModel exportDataModel in mapData.GetValueOrDefault(mapDataKey)) { buffer.Append(exportDataModel.getProductId() + "," + exportDataModel.getPrice() + "," + exportDataModel.getAmount() + "\n"); } } } public void buildFooter(ExportFooterModel efm) { buffer.Append(efm.getExportUser()); } public StringBuilder getResult() { return buffer; } } public class XmlBuilder : Builder { private StringBuilder buffer = new StringBuilder(); public void buildHeader(ExportHeaderModel ehm) { buffer.Append("<?xml version='1.0' encoding='gb2312'?>\n"); buffer.Append("<Report>\n"); buffer.Append(" <Header>\n"); buffer.Append(" <DepId>" + ehm.getDepId() + "</DepId>\n"); buffer.Append(" <ExportDate>" + ehm.getExportDate() + "</ExportDate>\n"); buffer.Append(" </Header>\n"); } public void buildBody(Dictionary<string, List<ExportDataModel>> mapData) { buffer.Append(" <Body>\n"); foreach (string mapDataKey in mapData.Keys) { buffer.Append(" <Datas TableName=\"" + mapDataKey + "\">\n"); foreach (ExportDataModel exportDataModel in mapData.GetValueOrDefault(mapDataKey)) { buffer.Append(" <Data>\n"); buffer.Append(" <ProductId>" + exportDataModel.getProductId() + "</ProductId>\n"); buffer.Append(" <Price>" + exportDataModel.getPrice() + "</Price>\n"); buffer.Append(" <Amount>" + exportDataModel.getAmount() + "</Amount>\n"); buffer.Append(" </Data>\n"); } buffer.Append(" </Datas>\n"); } buffer.Append(" </Body>\n"); } public void buildFooter(ExportFooterModel efm) { buffer.Append(" <Footer>\n"); buffer.Append(" <ExportUser>" + efm.getExportUser() + "</ExportUser>\n"); buffer.Append(" </Footer>\n"); buffer.Append("</Report>\n"); } public StringBuilder getResult() { return buffer; } } public class Director { private Builder builder; public Director(Builder builder) { this.builder = builder; } public void constructor(ExportHeaderModel ehm, Dictionary<string, List<ExportDataModel>> mapData, ExportFooterModel efm) { builder.buildHeader(ehm); builder.buildBody(mapData); builder.buildFooter(efm); } } }
8.3 模式讲解
8.3.1 认识生成器模式
Q:生成器模式的功能
A:生成器模式的主要功能是构建复杂的产品,而且是细化的,分步骤的构建产品,也就是生成器模式重在一步一步解决构造复杂对象的问题.再直白点说,生成器模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用
Q:生成器模式的构造
A:Builder模式存在两个部分,一个部分是部件构造和产品装配,另一个部分是整体构建的算法
8.3.2 生成器模式的实现
8.3.3 使用生成器模式构建复杂对象
8.3.4 生成器模式的优点
优点
1. 松散耦合
2.可以很容易地改变产品的内部表示
3.更好的复用性
8.3.5 思考生成器模式
Q:生成器模式的本质
A:分离整体构建算法和部件构造
构建一个复杂的对象,本来就有构建的过程,以及构建过程中具体的实现.生成器模式就是用来分离这两个部分,从而使得程序结构更松散,扩展更容易,复用性更好,同时也会使得代码更清晰,意图更明确
虽然在生成器模式的整体构建算法中,会一步一步引导Builder来构建对象,但这并不是说生成器主要就是用来实现分步骤构建对象的,生成器模式的重心还是在于分离整体构建算法和部件构造,而分步
构建对象不过是整体构建算法的一个简单表现,或者说是一个附带产物
Q:何时选用生成器模式
A: 1.如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时
2.如果同一个构建过程有着不同的表示时
8.3.6 相关模式
1.生成器模式和工厂方法模式
这两个模式可以组合使用
生成器模式的Builder实现中,通常需要选择具体的部件实现.一个可行的方案就是实现成为工厂方法,通过工厂方法来获取具体的部件对象,然后再进行部件的装配
2.生成器模式和抽象工厂模式
抽象工厂模式的主要目的是创建产品簇,这个产品簇里面的单个产品就相当于是构成一个复杂对象的部件对象,抽象工厂对象创建完成后就立即返回整个产品簇;
而生成器模式的主要目的是按照构造算法,一步一步来构建一个复杂的产品对象,通常要等到整个构建过程结束以后,才会得到最终的产品对象.
事实上,这两个模式是可以组合使用的.在生成器模式的Builder实现中,需要创建各个部件对象,而这些部件对象是有关联的,通常是构成一个复杂对象的部件对象
也就是说,Builder实现中,需要获取构成一个复杂对象的产品簇,那自然就可以使用抽象工厂模式来实现,这样一来,由抽象工厂模式负责了部件对象的创建,
Builder实现里面则主要负责产品对象整体的构建了
3.生成器模式和模板方法模式
这是两个非常类似的模式,初看之下,不会觉得这两个模式有什么关联.但是仔细一思考,却发现两个模式在功能上很类似.模板方法模式主要是用来定义算法的骨架,把算法中某些步骤延迟到子类中实现.
再想想生成器模式,Director用来定义整体的构建算法,把算法中某些涉及到具体部件对象的创建和装配的功能,委托给具体的Builder实现
虽然生成器不是延迟到子类,是委托给Builder,但那只是具体实现方式上的差别,从实质上看两个模式很类似,都是定义一个固定算法骨架,然后把算法中的某些具体步骤交给其他类来完成,都能实现整体算法步骤
和某些具体步骤实现的分离
当然这两个模式也有很大的区别,首先是模式的目的,生成器模式是用来构建复杂对象的,而模板方法是用来定义算法骨架的,尤其是一些复杂的业务功能的处理算法的骨架;其次是模式的实现,生成器模式是采用委托的方法,
而模板方法采用的是继承的方式;另外从使用的复杂度上,生成器模式需要组合Director和Builder对象,然后才能开始构建,要等构建完后才能获得最终的对象,而模板方法就没有这么麻烦,直接使用子类对象即可.
4.生成器模式和组合模式
这两个模式可以组合使用
对于复杂的组合结构,可以使用生成器模式来一步一步构建.
第9章 原型模式(Prototype)
9.1 场景问题
9.1.1 订单处理系统
9.1.2 不用模式的解决方案
using System; namespace Test2 { class Program { static void Main(string[] args) { PersonOrder op = new PersonOrder(); op.setOrderProductNum(2925); op.setCustomerName("张三"); op.setProductId("P0001"); OrderBusiness ob = new OrderBusiness(); ob.saveOrder(op); } } public interface OrderApi { int getOrderProductNum(); void setOrderProductNum(int num); } public class PersonOrder : OrderApi { private string customerName; private string productId; private int orderProductNum = 0; public string getCustomerName() { return customerName; } public void setCustomerName(string customerName) { this.customerName = customerName; } public string getProductId() { return productId; } public void setProductId(string productId) { this.productId = productId; } public int getOrderProductNum() { return orderProductNum; } public void setOrderProductNum(int num) { this.orderProductNum = num; } public override string ToString() { return "本个人订单的订购人是 = " + this.customerName + ", 订购产品是 = " + this.productId + ", 订购数量为 = " + this.orderProductNum; } } public class EnterpriseOrder : OrderApi { private string enterpriseName; private string productId; private int orderProductNum = 0; public string getEnterpriseName() { return enterpriseName; } public void setEnterpriseName(string enterpriseName) { this.enterpriseName = enterpriseName; } public string getProductId() { return productId; } public void setProductId(string productId) { this.productId = productId; } public int getOrderProductNum() { return orderProductNum; } public void setOrderProductNum(int num) { this.orderProductNum = num; } public override string ToString() { return "本企业订单的订购企业是 = " + this.enterpriseName + ", 订购产品是 = " + this.productId + ", 订购数量为 = " + this.orderProductNum; } } public class OrderBusiness { public void saveOrder(OrderApi order) { while (order.getOrderProductNum() > 1000) { OrderApi newOrder = null; if (order is PersonOrder) { PersonOrder p2 = new PersonOrder(); PersonOrder p1 = (PersonOrder) order; p2.setCustomerName(p1.getCustomerName()); p2.setProductId(p1.getProductId()); p2.setOrderProductNum(1000); newOrder = p2; } else if (order is EnterpriseOrder) { EnterpriseOrder e2 = new EnterpriseOrder(); EnterpriseOrder e1 = (EnterpriseOrder) order; e2.setEnterpriseName(e1.getEnterpriseName()); e2.setProductId(e1.getProductId()); e2.setOrderProductNum(1000); newOrder = e2; } order.setOrderProductNum(order.getOrderProductNum() - 1000); Console.WriteLine("拆分生成订单 == " + newOrder); } Console.WriteLine("订单 == " + order); } } }
9.1.3 有何问题
有何问题
已经有了某个对象实例后,如何能够快速简单地创建出更多的这种对象?
9.2 解决方案
9.2.1 使用原型模式来解决问题
Q:原型模式的定义
A:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
9.2.2 原型模式的结构和说明
[Prototype] 声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法
[ConcretePrototype] 实现Prototype接口的类,这些类真正实现了克隆自身的功能.
[Client] 使用原型的客户端,首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的对象实例
9.2.3 原型模式示例代码
using System; namespace Test2 { class Program { static void Main(string[] args) { } } public interface Prototype { Prototype clone(); } public class ConcretePrototype1 : Prototype { public Prototype clone() { Prototype prototype = new ConcretePrototype1(); return prototype; } } public class ConcretePrototype2 : Prototype { public Prototype clone() { Prototype prototype = new ConcretePrototype2(); return prototype; } } public class Client { private Prototype prototype; public Client(Prototype prototype) { this.prototype = prototype; } public void operation() { Prototype newPrototype = prototype.clone(); } } }
9.2.4 使用原型模式重写示例
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { OrderApi oa1 = new PersonalOrder(); oa1.setOrderProductNum(100); Console.WriteLine("这是第一次获取的对象实例 === " + oa1.getOrderProductNum()); OrderApi oa2 = (OrderApi) oa1.cloneOrder(); oa2.setOrderProductNum(80); Console.WriteLine("输出克隆出来的实例 === " + oa2.getOrderProductNum()); Console.WriteLine("再次输出原型实例 === " + oa1.getOrderProductNum()); } public interface OrderApi { int getOrderProductNum(); void setOrderProductNum(int num); OrderApi cloneOrder(); } public class PersonalOrder : OrderApi { private string customerName; private string productId; private int orderProductNum = 0; public string getCustomerName() { return customerName; } public void setCustomerName(string customerName) { this.customerName = customerName; } public string getProductId() { return productId; } public void setProductId(string productId) { this.productId = productId; } public int getOrderProductNum() { return this.orderProductNum; } public void setOrderProductNum(int num) { this.orderProductNum = num; } public override string ToString() { return "本个人订单的订购人是 = " + this.customerName + ", 订购产品是 = " + this.productId + ", 订购数量 = " + this.orderProductNum; } public OrderApi cloneOrder() { PersonalOrder order = new PersonalOrder(); order.setCustomerName(this.customerName); order.setProductId(this.productId); order.setOrderProductNum(this.orderProductNum); return order; } } public class EnterpriseOrder : OrderApi { private string enterpriseName; private string productId; private int orderProductNum = 0; public string getEnterpriseName() { return enterpriseName; } public void setEnterpriseName(string enterpriseName) { this.enterpriseName = enterpriseName; } public string getProductId() { return productId; } public void setProductId(string productId) { this.productId = productId; } public int getOrderProductNum() { return this.orderProductNum; } public void setOrderProductNum(int num) { this.orderProductNum = num; } public override string ToString() { return "本企业订单的订购企业是 = " + this.enterpriseName + ", 订购产品 = " + this.productId + ", 订购数量为 = " + this.orderProductNum; } public OrderApi cloneOrder() { EnterpriseOrder order = new EnterpriseOrder(); order.setEnterpriseName(this.enterpriseName); order.setProductId(this.productId); order.setOrderProductNum(this.orderProductNum); return order; } } public class OrderBusiness { public void saveOrder(OrderApi order) { while (order.getOrderProductNum() > 1000) { OrderApi newOrder = order.cloneOrder(); newOrder.setOrderProductNum(1000); order.setOrderProductNum(order.getOrderProductNum() - 1000); Console.WriteLine("拆分生成订单 == " + newOrder); } Console.WriteLine("订单 === " + order); } } } }
9.3 模式讲解
9.3.1 认识原型模式
Q:原型模式的功能
A: 1.一个是通过克隆来创建新的对象实例
2.另一个是为克隆出来的新的对象实例复制原型实例属性的值.
原型模式要实现的主要功能就是:通过克隆来创建新的对象实例.一般来讲,新创建出来的实例的数据是和原型实例一样的.但是具体如何实现克隆,需要由程序自行实现,原型模式并没有统一的要求和实现算法
Q:原型与new
A: 原型模式从某种意义上说,就像是new操作,在前面的例子实现中,克隆方法就是使用new来实现的.但请注意,只是"类似于new"而不是"就是new".
克隆方法和new操作最明显的不同在于:new一个对象实例,一般属性是没有值的,或者是只有默认值;如果是克隆得到的一个实例,通常属性是有值的,属性的值就是原型对象实例在克隆的时候,原型对象实例的属性的值
Q:原型实例和克隆的实例
A: 原型实例和克隆出来的实例,本质上是不同的实例,克隆完成后,它们之间是没有关联的,如果克隆完成后,克隆出来的实例的属性值发生了改变,是不会影响到原型实例的.
9.3.2 Java中的克隆方法
9.3.3 浅度克隆和深度克隆
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { Product product = new Product(); product.setName("产品1"); PersonalOrder oa1 = new PersonalOrder(); oa1.setProduct(product); oa1.setOrderProductNum(100); Console.WriteLine("这是第一次获取的对象实例 = " + oa1); PersonalOrder oa2 = (PersonalOrder)oa1.cloneOrder(); oa2.getProduct().setName("产品2"); oa2.setOrderProductNum(80); Console.WriteLine("输出克隆出来的实例 = " + oa2); Console.WriteLine("再次输出原型实例 = " + oa1); } public interface ProductPrototype { ProductPrototype cloneProduct(); } public class Product : ProductPrototype { private string productId; private string name; public string getProductId() { return productId; } public void setProductId(string productId) { this.productId = productId; } public string getName() { return name; } public void setName(string name) { this.name = name; } public override string ToString() { return "产品编号 = " + this.productId + ", 产品名称 = " + this.name; } public ProductPrototype cloneProduct() { Product product = new Product(); product.setProductId(this.productId); product.setName(this.name); return product; } } public interface OrderApi { void setOrderProductNum(int num); } public class PersonalOrder : OrderApi { private string custormerName; private int orderProductNum = 0; private Product product = null; public int getOrderProductNum() { return this.orderProductNum; } public void setOrderProductNum(int num) { this.orderProductNum = num; } public string getCustomerName() { return custormerName; } public void setCustomerName(string customerName) { this.custormerName = customerName; } public Product getProduct() { return product; } public void setProduct(Product product) { this.product = product; } public override string ToString() { return "订购产品是 = " + this.product.getName() + ", 订购数量为 = " + this.orderProductNum; } public OrderApi cloneOrder() { PersonalOrder order = new PersonalOrder(); order.setCustomerName(this.custormerName); order.setOrderProductNum(this.orderProductNum); order.setProduct((Product)this.product.cloneProduct()); return order; } } } }
9.3.4 原型管理器
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { Prototype p1 = new ConcretePrototype1(); PrototypeManager.setPrototype("Prototype1", p1); Prototype p3 = PrototypeManager.getPrototype("Prototype1").clone(); p3.setName("张三"); Console.WriteLine("第一个实例: " + p3); Prototype p2 = new ConcretePrototype2(); PrototypeManager.setPrototype("Prototype1",p2); Prototype p4 = PrototypeManager.getPrototype("Prototype1").clone(); p4.setName("李四"); Console.WriteLine("第二个实例: " + p4); PrototypeManager.removePrototype("Prototype1"); Prototype p5 = PrototypeManager.getPrototype("Prototyp1").clone(); p5.setName("王五"); Console.WriteLine("第三个实例: " + p5); } } public interface Prototype { Prototype clone(); string getName(); void setName(string name); } public class ConcretePrototype1 : Prototype { private string name; public string getName() { return name; } public void setName(string name) { this.name = name; } public Prototype clone() { ConcretePrototype1 prototype = new ConcretePrototype1(); prototype.setName(this.name); return prototype; } public override string ToString() { return "Now in Prototype1,name = " + name; } } public class ConcretePrototype2 : Prototype { private string name; public string getName() { return name; } public void setName(string name) { this.name = name; } public Prototype clone() { ConcretePrototype2 prototype = new ConcretePrototype2(); prototype.setName(this.name); return prototype; } public override string ToString() { return "Now in Prototype2, name = " + name; } } public class PrototypeManager { private static Dictionary<string,Prototype> map = new Dictionary<string, Prototype>(); private PrototypeManager() { } public static void setPrototype(string prototypeId, Prototype prototype) { map[prototypeId] = prototype; } public static void removePrototype(string prototypeId) { map.Remove(prototypeId); } public static Prototype getPrototype(string prototypeId) { Prototype prototype = map[prototypeId]; if (prototype == null) { throw new Exception("您希望获取的原型还没有注册或已被销毁"); } return prototype; } } }
9.3.5 原型模式的优缺点
优点
1.对客户端隐藏具体的实现类型
原型模式的客户端只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类型的依赖
2.在运行时动态改变具体的实现类型
原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了.因为克隆一个原型就类似于实例化一个类
缺点
1.原型模式最大的缺点就在于每个原型的子类都必须实现clone的操作,尤其在包含引用类型的对象时,clone方法会比较麻烦,必须要能够递归地让所有的相关对象都要正确地实现克隆
9.3.6 思考原型模式
Q:原型模式的本质
A:克隆生成对象
克隆是手段,目的是生成新的对象实例.正是因为原型的目的是为了生成新的对象实例,原型模式通常是被归类为创建型的模式
原型模式也可以用来解决"只知接口而不知实现的问题",使用原型模式,可以出现一种独特的"接口造接口"的景象,这在面向接口编程中很有用.同样的功能也可以考虑使用工厂来实现
另外,原型模式的中心还是在创建新的对象实例,至于创建出来的对象,其属性的值是否一定要和原型对象属性的值完全一样,这个并没有强制规定,只不过在目前大多数实现中,克隆出来的对象和原型对象的属性值是一样的
也就是说,可以通过克隆来创造值不一样的实例,但是对象类型必须一样.可以有部分甚至是全部的属性的值不一样,可以有选择性地克隆,就当是标准原型模式的一个变形使用吧
Q:何时使用原型模式
A: 1.如果一个系统想要独立于它想要使用的对象时,可以使用原型模式,让系统只面向接口编程,在系统需要新的对象的时候,可以通过克隆原型来得到.
2.如果需要实例化的类是在运行时刻动态指定时,可以使用原型模式,通过克隆原型来得到需要的实例
9.3.7 相关模式
1.原型模式和抽象工厂模式
功能上相似,都是用来获取一个新的对象实例的
不同之处在于,原型模式的着眼点是在如何创造出实例对象来,最后选择的方案是通过克隆;而抽象工厂模式的着眼点则在于如何创造产品簇,至于具体如何创建出产品簇中的每个对象实例,抽象工厂模式则不是很关注
正式因为它们的关注点不一样,所以它们也可以配合使用,比如在抽象工厂模式里面,具体创建每一种产品的时候就可以使用该产品的原型,也就是抽象工厂管产品簇,具体的每种产品怎么创建则可以选择原型模式
2.原型模式和生成器模式
这两种模式可以配合使用
生成器模式关注的是构建的过程,而在构建的过程中,很可能需要某个部件的实例,那么很自然地就可以应用上原型模式,通过原型模式来得到部件的实例
第10章 中介者模式(Mediator)
10.1 场景问题
10.1.1 如果没有主板
10.1.2 有何问题
Q:有何问题
A:如果上面的情况发生在软件开发上呢?
若把每个电脑配件都抽象成为一个类或者是子系统,那就相当于出现了多个类之间相互交互,而且交互很繁琐,导致每个类都必须知道所有需要交互的类,也就是我们常说的类和类耦合了.
在软件开发中出现这种情况可就不秒了,不但开发的时候每个类会复杂,因为要兼顾其他的类,更要命的是每个类在发生改动的时候,需要通知所有相关的类一起修改,因为接口或者是功能发生了变动,使用它的地方都得变
那该如何来简化这种多个对象之间的交互呢?
10.1.3 使用电脑来看电影
10.2 解决方案
10.2.1 使用中介者模式来解决问题
Q:中介者模式的定义
A:用一个中介对象来封装一系列的对象交互.中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
Q:应用中介者模式来解决问题的思路
A:中介者模式的解决思路很简单,跟电脑的例子一样,中介者模式通过引入一个中介对象,让其他的对象都只和中介对象交互,而中介对象知道如何和其他所有的对象交互,这样对象之间的交互关系就没有了,从而实现了对象之间的解耦
对于中介对象而言,所有相互交互的对象,被视为同事类,中介对象就是来维护各个同事之间的关系,而所有的同事类都只是和中介对象交互.
每个同事对象,当自己发生变化的时候,不需要知道这会引起其他对象有什么变化,它只需要通知中介者就可以了,然后由中介者去与其他对象交互.这样松散耦合带来的好处是,除了让同事对象之间相互没有关联外,还有利于功能的修改和扩展.
有了个中介者之后,所有的交互都封装到中介者对象里面,各个对象就不再需要维护这些关系了.扩展关系的时候也只需要扩展或修改中介者对象就可以了.
10.2.2 中介者模式的结构和说明
[Mediator] 中介者接口.在里面定义各个同事之间交互需要的方法,可以是公共的通信方法,比如changed方法,大家都用,也可以是小范围的交互方法
[ConcreteMediator] 具体中介者实现对象,它需要了解并维护各个同事对象,并负责具体的协调各同事对象的交互关系
[Colleague] 同事类的定义,通常实现成为抽象类,主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能.比如,每个具体同事类都应该知道中介者对象,也就是具体同事类都会持有中介者对象,都可以定义到这个类里面
[ConcreteColleague] 具体的同事类,实现自己的业务,在需要与其他同事通信的时候,就与持有的中介者通信,中介者会负责与其他的同事交互
10.2.3 中介者模式示例代码
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { } } public abstract class Colleague { private Mediator mediator; public Colleague(Mediator mediator) { this.mediator = mediator; } public Mediator getMediator() { return mediator; } } public class ConcreteColleagueA : Colleague { public ConcreteColleagueA(Mediator mediator) : base(mediator) { } public void someOperation() { getMediator().changed(this); } } public class ConcreteColleagueB : Colleague { public ConcreteColleagueB(Mediator mediator) : base(mediator) { } public void someOperation() { getMediator().changed(this); } } public interface Mediator { void changed(Colleague colleague); } public class ConcreteMediator : Mediator { private ConcreteColleagueA colleagueA; private ConcreteColleagueB colleagueB; public void setConcreteColleagueA(ConcreteColleagueA colleagueA) { this.colleagueA = colleagueA; } public void setConcreteColleagueB(ConcreteColleagueB colleagueB) { this.colleagueB = colleagueB; } public void changed(Colleague colleague) { } } }
10.2.4 使用中介者模式来实现示例
using System; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { MotherBoard mediator = new MotherBoard(); CDDriver cd = new CDDriver(mediator); CPU cpu = new CPU(mediator); VideoCard vc = new VideoCard(mediator); SoundCard sc = new SoundCard(mediator); mediator.setCdDriver(cd); mediator.setCpu(cpu); mediator.setVideoCard(vc); mediator.setSoundCard(sc); cd.readCD(); } } public abstract class Colleague { private Mediator mediator; public Colleague(Mediator mediator) { this.mediator = mediator; } public Mediator getMediator() { return mediator; } } public class CDDriver : Colleague { private string data = ""; public CDDriver(Mediator mediator) : base(mediator) { } public string getData() { return this.data; } public void readCD() { this.data = "设计模式,值得好好研究"; this.getMediator().changed(this); } } public class CPU : Colleague { private string videoData = ""; private string soundData = ""; public CPU(Mediator mediator) : base(mediator) { } public string getVideoData() { return videoData; } public string getSoundData() { return soundData; } public void executeData(string data) { string[] ss = data.Split(','); this.videoData = ss[0]; this.soundData = ss[1]; this.getMediator().changed(this); } } public class VideoCard : Colleague { public VideoCard(Mediator mediator) : base(mediator) { } public void showData(string data) { Console.WriteLine("您正观看的是: " + data); } } public class SoundCard : Colleague { public SoundCard(Mediator mediator) : base(mediator) { } public void soundData(string data) { Console.WriteLine("画外音: " + data); } } public interface Mediator { void changed(Colleague colleague); } public class MotherBoard : Mediator { private CDDriver cdDriver = null; private CPU cpu = null; private VideoCard videoCard = null; private SoundCard soundCard = null; public void setCdDriver(CDDriver cdDriver) { this.cdDriver = cdDriver; } public void setCpu(CPU cpu) { this.cpu = cpu; } public void setVideoCard(VideoCard videoCard) { this.videoCard = videoCard; } public void setSoundCard(SoundCard soundCard) { this.soundCard = soundCard; } public void changed(Colleague colleague) { if (colleague == cdDriver) { this.openCDDriverReadData((CDDriver)colleague); } else if (colleague == cpu) { this.openCPU((CPU)colleague); } } private void openCDDriverReadData(CDDriver cd) { string data = cd.getData(); this.cpu.executeData(data); } private void openCPU(CPU cpu) { string videoData = cpu.getVideoData(); string soundData = cpu.getSoundData(); this.videoCard.showData(videoData); this.soundCard.soundData(soundData); } } }
10.3 模式讲解
10.3.1 认识中介者模式
Q:中介者模式的功能
A:中介者的功能非常简单,就是封装对象之间的交互.如果一个对象的操作会引起其他相关对象的变化,或者是某个操作需要引起其他对象的后续或连带操作,而这个对象又不希望自己来处理这些关系,那么就可以找中介者,把所有的麻烦扔给它,只在需要的时候通知中介者,其他的就让中介者去处理就可以了
反过来,其他的对象在操作的时候,可能会引起这个对象的变化,也可以这么做.最后对象之间就完全分离了,谁都不直接跟其他对象交互,那么相互的关系全部被集中到中介者对象里面了,所有的对象就只是跟中介者对象进行通信,相互之间不再有联系.
把所有对象之间的交互都封装在中介者当中,无形中还可以得到另外一个好处,就是能够集中地控制这些对象的交互关系,这样当有变化的时候,修改起来就很方便.
Q:需要Mediator接口吗
A:取决于是否会提供多个不同的中介者实现.
Q:同事关系
A:在标准的中介者模式中,将使用中介者对象来交互的那些对象称为同事类.
Q:同事和中介者的关系
A:在中介者模式中,当一个同事对象发生了改变,需要主动通知中介者,让中介者去处理与其他同事对象相关的交互
这就导致了同事对象和中介者对象之间必须有关系,首先是同事对象需要知道中介者对象是谁;反过来,中介者对象也需要知道相关的同事对象,这样它才能与同事对象进行交互.也就是说中介者对象和同事对象之间是互相依赖的.
Q:如何实现同事和中介者的通信
A:一个同事对象产生了改变,会通知中介者对象,中介者对象会处理与其他同事的交互,这就产生了同事对象和中介者对象的相互通信.怎么实现这种通信关系呢?
一种实现方式是在Mediator接口中定义一个特殊的通知接口,作为一个通用的方法,让各个同事类来调用这个方法,在中介者模式结构图里画的就是这种方式.在前面实例的也是这种方式,定义了一个通用的changed方法,并且把同事对象当作参数传入,这样在中介者对象里面,就可以去获取这个同事对象的实例的数据了.
另外一种实现方式是可以采用观察者模式,把Mediator实现称为观察者,而各个同事类实现成为Subject,这样同事类发生了改变,会通知Mediator.Mediator在接到通知以后,会与相应的同事对象进行交互
10.3.2 广义中介者
仔细查看中介者的结构,定义和示例,会发现几个问题,使得中介者模式在实际使用的时候,变得繁琐或困难
1.是否有必要为同事对象定义一个公共的父类?
大家都知道,JAVA/C#是单继承的,为了使用中介者模式,就让这些同事对象继承一个父类,这是很不好的;再说了,这个父类目前也没有什么特别的功能,也就是说继承它也得不到多少好处
在实际开发中,很多相互交互的对象本身是没有公共父类的,强行加上一个父类,会让这些对象实现起来特别别扭
2.同事类有必要持有中介者对象吗?
同事类需要知道中介者对象,以便当它们发生改变的时候能够通知中介者对象.但是是否需要作为属性并通过构造方法传入这么强的依赖关系呢?
也可以用简单的方式去通知中介对象,比如把中介对象做成单例,直接在同事类的方法里面去调用中介者对象.
3.是否需要中介者接口?
在实际开发中,很常见的情况是不需要中介者接口的,而且中介者对象也不需要创建很多个实例.因为中介者是用来封装和处理同事对象的关系的,它一般是没有状态需要维护的,因此中介者通常可以实现成单例
4.中介者对象是否需要持有所有的同事?
虽说中介者对象需要知道所有的同事类,这样中介者才能与它们交互.但是是否需要作为属性这么强烈的依赖关系,而且中介者对象在不同的关系维护上,可能会需要不同的同事对象的实例,因此可以在中介者处理的方法里面去创建,或者获取,或者从参数传入需要的同事对象
5.中介者对象只是提供一个公共的方法来接受同事对象的通知吗?
从示例中可以看出来,在公共方法里,还是要去区分到底是谁调过来,这还是简单的,还没有去区分到底是什么样的业务触发调用过来的,因为不同的业务,引起的与其他对象的交互是不一样的.
因此在实际开发中,通常会提供具体的业务通知方法,这样就不用再去判断到底是什么对象,具体是什么业务了
基于上面的考虑,在实际应用开发中,经常会简化中介者模式,来使得开发变得简单,比如有如下的简化
1.通常会去掉同事对象的父类,这样可以让任意的对象,只要需要相互交互,就可以成为同事
2.通常不定义Mediator接口,把具体的中介者对象实现成为单例
3.同事对象不再持有中介者,而是在需要的时候直接获取中介者对象并调用;中介者也不再持有同事对象,而是在具体处理方法里面去创建,或者获取,或者从参数传入需要的同事对象
把这样经过简化,变形使用的情况称为广义中介者
例子:
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { DepUserMediatorImpl mediator = DepUserMediatorImpl.getInstance(); Dep dep = new Dep(); dep.setDepId("d1"); Dep dep2 = new Dep(); dep2.setDepId("d2"); User user = new User(); user.setUserId("u1"); Console.WriteLine("撤销部门前------"); mediator.showUserDeps(user); dep.deleteDep(); Console.WriteLine("撤销部门后------"); mediator.showUserDeps(user); Console.WriteLine("人员离职前------"); mediator.showDepUsers(dep2); user.dismission(); Console.WriteLine("人员离职后------"); mediator.showDepUsers(dep2); } } public class Dep { private string depId; private string depName; public string getDepId() { return depId; } public void setDepId(string depId) { this.depId = depId; } public string getDepName() { return depName; } public void setDepName(string depName) { this.depName = depName; } public bool deleteDep() { DepUserMediatorImpl mediator = DepUserMediatorImpl.getInstance(); mediator.deleteDep(depId); return true; } } public class User { private string userId; private string userName; public string getUserId() { return userId; } public void setUserId(string userId) { this.userId = userId; } public string getUserName() { return userName; } public void setUserName(string userName) { this.userName = userName; } public bool dismission() { DepUserMediatorImpl mediator = DepUserMediatorImpl.getInstance(); mediator.deleteUser(userId); return true; } } public class DepUserModel { private string depUserId; private string depId; private string userId; public string getDepUserId() { return depUserId; } public void setDepUserId(string depUserId) { this.depUserId = depUserId; } public string getDepId() { return depId; } public void setDepId(string depId) { this.depId = depId; } public string getUserId() { return userId; } public void setUserId(string userId) { this.userId = userId; } } public class DepUserMediatorImpl { private static DepUserMediatorImpl mediator = new DepUserMediatorImpl(); private List<DepUserModel> depUserCol = new List<DepUserModel>(); private DepUserMediatorImpl() { initTestData(); } public static DepUserMediatorImpl getInstance() { return mediator; } private void initTestData() { DepUserModel du1 = new DepUserModel(); du1.setDepUserId("du1"); du1.setDepId("d1"); du1.setUserId("u1"); depUserCol.Add(du1); DepUserModel du2 = new DepUserModel(); du2.setDepUserId("du2"); du2.setDepId("d1"); du2.setUserId("u2"); depUserCol.Add(du2); DepUserModel du3 = new DepUserModel(); du3.setDepUserId("du3"); du3.setDepId("d2"); du3.setUserId("u3"); depUserCol.Add(du3); DepUserModel du4 = new DepUserModel(); du4.setDepUserId("du4"); du4.setDepId("d2"); du4.setUserId("u4"); depUserCol.Add(du4); DepUserModel du5 = new DepUserModel(); du5.setDepUserId("du5"); du5.setDepId("d2"); du5.setUserId("u1"); depUserCol.Add(du5); } public bool deleteDep(string depId) { depUserCol.RemoveAll((depUserModel)=> { if (depUserModel.getDepId().Equals(depId)) { return true; } else { return false; } }); return true; } public bool deleteUser(string userId) { depUserCol.RemoveAll((depUserModel)=> { if (depUserModel.getDepId().Equals(userId)) { return true; } else { return false; } }); return true; } public void showDepUsers(Dep dep) { foreach (DepUserModel depUserModel in depUserCol) { if (depUserModel.getDepId().Equals(dep.getDepId())) { Console.WriteLine("部门编号 = " + dep.getDepId() + "下面拥有人员,其编号是: " + depUserModel.getUserId()); } } } public void showUserDeps(User user) { foreach (DepUserModel depUserModel in depUserCol) { if (depUserModel.getUserId().Equals(user.getUserId())) { Console.WriteLine("人员编号 = " + user.getUserId() + "属于部门编号是: " + depUserModel.getDepId()); } } } public bool changeDep(string userId, string oldDepId, string newDepId) { return false; } public bool joinDep(List<string> colDepIds, Dep newDep) { return false; } } }
10.3.3 中介者模式的优缺点
优点
1.松散耦合
中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互不依赖,这样一来,同事对象就可以独立地变化和复用,而不再像以前那样"牵一发而动全身"了
2.集中控制交互
多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那就扩展中介者对象,而各个同事类不需要做修改
3.多对多变成一对多
没有使用中介者模式的时候,同事对象之间的关系通常是多对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成了双向的一对多,这会让对象的关系更容易理解和实现
缺点
1.中介者模式的一个潜在缺点是,过渡集中化.如果同事对象的交互非常多,而且比较复杂,当这些复杂性全部集中到中介者的时候,会导致中介者对象变得十分复杂,而且难于管理和维护
10.3.4 思考中介者模式
Q:中介者模式的阿本质
A:封装交互
中介者模式的目的,就是用来封装多个对象的交互,这些交互的处理多在中介者对象里面实现.因此中介对象的复杂程度,就取决于它封装的交互的复杂程度
只要是实现封装对象之间的交互功能,就可以应用中介者模式,而不必过于拘泥于中介者模式本身的结构.标准的中介者模式限制很多,导致能完全按照标准使用中介者模式的地方并不是很多,而且多集中在界面实现上.只要本质不变,稍稍变形一下,简化一下,或许能更好地使用中介者模式
Q:何时选用中介者模式
A: 1.如果一组对象之间的通信方式比较复杂,导致相互依赖,结构混乱,可以采用中介者模式,把这些对象相互的交互管理起来,各个对象都只需要和中介者交互,从而使得各个对象松散耦合,结构也更清晰易懂.
2.如果一个对象引用很多的对象,并直接跟这些对象交互,导致难以复用该对象,可以采用中介者模式,把这个对象跟其他对象的交互封装到中介者对象里面,这个对象只需要和中介者对象交互就可以了.
10.3.5 相关模式
1.中介者模式和外观模式
这两个模式有相似的地方,也存在很大的不同
外观模式多用来封装一个子系统内部的多个模块,目的是向子系统外部提供简单易用的接口.也就是说外观模式封装的是子系统外部和子系统内部模块间的交互;而中介者模式是提供多个平等的同事对象之间的交互关系的封装,一般是用在内部实现上
另外,外观模式是实现单向的交互,是从子系统外部来调用子系统内部,不会反着来,而中介者模式实现的是内部多个模块间的多向的交互
2.中介者模式和观察者模式
这两个模式可以组合使用
中介者模式可以组合使用观察者模式,来实现当同事对象发生改变的时候,通知中介对象,让中介对象去进行与其他相关对象的交互
第11章 代理模式(Proxy)
11.1 场景问题
11.1.1 访问多条数据
11.1.2 不用模式的解决方案
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { UserManager userManager = new UserManager(); List<UserModel> col = userManager.getUserByDepId("0101"); Console.WriteLine(col); } } public class UserModel { private string userId; private string name; private string depId; private string sex; public string getUserId() { return userId; } public void setUserId(string userId) { this.userId = userId; } public string getName() { return name; } public void setName(string name) { this.name = name; } public string getDepId() { return depId; } public void setDepId(string depId) { this.depId = depId; } public string getSex() { return sex; } public void setSex(string sex) { this.sex = sex; } public override string ToString() { return "userId = " + userId + ", name = " + name + ", depId = " + depId + ", sex = " + sex + "\n"; } } public class UserManager { public List<UserModel> getUserByDepId(string depId) { List<UserModel> col = new List<UserModel>(); Connection conn = null; conn = this.getConnection(); string sql = "select * from tbl_user u,tbl_dep d where u.depId = d.depId and d.depId like ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, depId + "%"); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { UserModel um = new UserModel(); um.setUserId(rs.getString("userId")); um.setName(rs.getString("name")); um.setDepId(rs.getString("depId")); um.setSex(rs.getString("sex")); col.Add(um); } rs.close(); pstmt.close(); } private Connection getConnection() { return DriverManager.getConnection("连接数据库的URL", "用户名", "密码"); } } }
11.1.3 有何问题
Q:有何问题
A:上面的实现看起来很简单,功能也正确,但是蕴含一个较大的问题.那就是,当一次性访问的数据条数过多,而且每条描述的数据量又很大的话,将会消耗较多的内存.
前面也说i了,对于用户表,实际上是很多字段的,不仅仅是示例的几个,再加上不使用翻页,一次性访问的数据就可能会有很多条.如果一次性需要访问的数据较多,内存开销将会比较大
但是从客户使用的角度来说i,有很大的随机性.客户有可能访问每一条数据,也有可能一条都不访问.也就是说,一次性访问很多条数据,消耗了大量内存,但是很可能是浪费掉了,客户根本就不会去访问那么多数据,对于每条数据,客户只需要看看姓名而已.
那么该怎么实现,才能既能把多条用户数据的姓名显示出来,而又能节省内存空间?当然还要实现在客户想要看到更多数据的时候,能正确访问到数据呢
11.2 解决方案
11.2.1 使用代理模式来解决问题
Q:代理模式的定义
A:为其他对象提供一种代理以控制对这个对象的访问.
11.2.2 代理模式的结构和说明
[Proxy] 代理对象,通常具有如下功能.实现与具体的对象一样的接口,这样就可以使用代理来代替具体的目标对象.保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象.可以控制对具体目标对象的访问,并可以负责创建和删除它
[Subject] 目标接口,定义代理和具体目标对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象
[RealSubject] 具体的目标对象,真正实现目标接口要求的功能
11.2.3 代理模式示例代码
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { } } public interface Subject { void request(); } public class RealSubject : Subject { public void request() { } } public class Proxy : Subject { private RealSubject realSubject = null; public Proxy(RealSubject realSubject) { this.realSubject = realSubject; } public void request() { realSubject.request(); } } }
11.2.4 使用代理模式重写示例
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { UserManager userManager = new UserManager(); List<UserModelApi> col = userManager.getUserByDepId("0101"); foreach (UserModelApi userModelApi in col) { Console.WriteLine("用户编号: = " + userModelApi.getUserId() + ",用户姓名: = " + userModelApi.getName()); } foreach (UserModelApi userModelApi in col) { Console.WriteLine("用户编号: = " + userModelApi.getUserId() + ",用户姓名: = " + userModelApi.getName() + ",所属部门: = " + userModelApi.getDepId()); } } } public interface UserModelApi { string getUserId(); void setUserId(string userId); string getName(); void setName(string name); string getDepId(); void setDepId(string depId); string getSex(); void setSex(string sex); } public class UserModel : UserModelApi { private string userId; private string name; private string depId; private string sex; public string getUserId() { return userId; } public void setUserId(string userId) { this.userId = userId; } public string getName() { return name; } public void setName(string name) { this.name = name; } public string getDepId() { return depId; } public void setDepId(string depId) { this.depId = depId; } public string getSex() { return sex; } public void setSex(string sex) { this.sex = sex; } public override string ToString() { return "userId = " + userId + ", name = " + name + ", depId = " + depId + ", sex = " + sex + "\n"; } } public class Proxy : UserModelApi { private bool loaded = false; private UserModel realSubject = null; public Proxy(UserModel realSubject) { this.realSubject = realSubject; } public string getUserId() { return realSubject.getUserId(); } public void setUserId(string userId) { realSubject.setUserId(userId); } public string getName() { return realSubject.getName(); } public void setName(string userName) { realSubject.setName(userName); } public string getDepId() { if (!this.loaded) { reload(); this.loaded = true; } return realSubject.getDepId(); } public void setDepId(string depId) { realSubject.setDepId(depId); } public string getSex() { if (!this.loaded) { reload(); this.loaded = true; } return realSubject.getSex(); } private void reload() { Console.WriteLine("重新查询数据库获取完整的用户数据,userId = " + realSubject.getUserId()); Connection conn = null; conn = this.getConnection(); string sql = "select * from tbl_user where userId = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, realSubject.getUserId()); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { realSubject.setDepId(rs.getString("depId")); realSubject.setSex(rs.getString("sex")); } rs.close(); pstmt.close(); } private Connection getConnection() { return DriverManager.getConnection("连接数据库的ULR", "用户名", "密码"); } public override string ToString() { return "userId = " + getUserId() + ", name = " + getName() + ", depId = " + getDepId() + ", sex = " + getSex() + "\n"; } } public class UserManager { public List<UserModelApi> getUserByDepId(string depId) { List<UserModelApi> col = new List<UserModelApi>(); Connection conn = null; conn = this.getConnection(); string sql = "select u.userId,u.name from tbl_user u,tbl_dep d where u.depId = d.depId and d.depId like ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, depId + "%"); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { Proxy proxy = new Proxy(new UserModel()); proxy.setUserId(rs.getString("userId")); proxy.setName(rs.getString("name")); col.Add(proxy); } rs.close(); pstmt.close(); } private Connection getConnection() { return DriverManager.getConnection("连接数据库的URL", "用户名", "密码"); } } }
11.3 模式讲解
11.3.1 认识代理模式
Q:代理模式的功能
A:代理模式是通过创建一个代理对象,用这个代理对象去代表真实的对象,客户端得到这个代理对象后,对客户端并没有什么影响,就跟得到了真实对象一样来使用
当客户端操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成.只不过是通过代理操作的,也就是客户端操作代理,代理操作真正的对象.
正是因为由代理对象夹在客户端和被代理的真实对象中间,相当于一个中转,那么在中转的时候就有很多花招可以玩,比如,判断一下权限,如果没有足够的权限那就不给你中转,等等
Q:代理的分类
A: 1:虚代理:根据需要来创建开销很大的对象,该对象只有在需要的时候才会被真正创建.
2:用来在不同的地址空间上代表同一个对象,这个不同的地址空间可以是在本机,也可以在其他机器上.
3:copy-on-write代理:在客户端操作的时候,只有对象确实改变了,才会真的拷贝(或克隆)一个目标对象,算是虚代理的一个分支
4:保护代理:控制对原始对象的访问,如果有需要,可以给不同的用户提供不同的访问权限,以控制他们对原始对象的访问.
5:Cache代理:为那些昂贵操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
6:防火墙代理:保护对象不被恶意用户访问和操作
7:同步代理:使多个用户能够同时访问目标对象而没有冲突
8:智能指引:在访问对象时执行一些复加操作,比如,对指向实际对象的引用计数,第一次引用一个持久对象时,将它装入内存等.
11.3.2 保护代理
using System; namespace Test2 { class Program { static void Main(string[] args) { OrderApi order = new OrderProxy(new Order("设计模式",100,"张三")); order.setOrderNum(123,"李四"); Console.WriteLine("李四修改后订单记录没有变化:" + order); order.setOrderNum(123,"张三"); Console.WriteLine("张三修改后,订单记录: " + order); } } public interface OrderApi { string getProductName(); void setProductName(string productName, string user); int getOrderNum(); void setOrderNum(int orderNum, string user); string getOrderUser(); void setOrderUser(string orderUser, string user); } public class Order : OrderApi { private string productName; private int orderNum; private string orderUser; public Order(string productName, int orderNum, string orderUser) { this.productName = productName; this.orderNum = orderNum; this.orderUser = orderUser; } public string getProductName() { return productName; } public void setProductName(string productName, string user) { this.productName = productName; } public int getOrderNum() { return orderNum; } public void setOrderNum(int orderNum, string user) { this.orderNum = orderNum; } public string getOrderUser() { return orderUser; } public void setOrderUser(string orderUser, string user) { this.orderUser = orderUser; } } public class OrderProxy : OrderApi { private Order order = null; public OrderProxy(Order realSubject) { this.order = realSubject; } public void setProductName(string productName, string user) { if (user != null && user.Equals(this.getOrderUser())) { order.setProductName(productName, user); } else { Console.WriteLine("对不起" + user + ",您无权修改订单中的产品名称."); } } public void setOrderNum(int orderNum, string user) { if (user != null && user.Equals(this.getOrderUser())) { order.setOrderNum(orderNum, user); } else { Console.WriteLine("对不起" + user + ",您无权修改订单中的产品名称."); } } public void setOrderUser(string orderUser, string user) { if (user != null && user.Equals(this.getOrderUser())) { order.setOrderUser(orderUser, user); } else { Console.WriteLine("对不起" + user + ",您无权修改订单中的产品名称."); } } public int getOrderNum() { return this.order.getOrderNum(); } public string getOrderUser() { return this.order.getOrderUser(); } public string getProductName() { return this.order.getProductName(); } public override string ToString() { return "productName = " + this.getProductName() + ", orderNum = " + this.getOrderNum() + ", orderUser = " + this.getOrderUser(); } } }
11.3.3 Java中的代理
11.3.4 代理模式的特点
11.3.5 思考代理模式
Q:代理模式的本质
A:控制对象访问
代理模式通过代理目标对象,把代理对象插入到客户和目标对象之间,从而为客户和目标对象引入一定的间接性.正是这个间接性,给了代理对象很多的活动空间.代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或是扩展目标对象的功能.更狠的是,代理对象还可以不去创建和调用目标对象,也就是说,目标对象被完全代理掉了,或是被替换掉了.
Q:何时选用代理模式
A: 1.需要为一个对象在不同的地址空间提供局部代表的时候,就可以使用远程代理\
2.需要按照需要创建开销很大的对象的时候,可以使用虚代理
3.需要控制对原始对象的访问的时候,可以使用保护代理
4.需要在访问对象执行一些附加操作的时候,可以使用智能指引代理.
11.3.6 相关模式
1.代理模式和适配器模式
这两个模式有一定的相似性,但也有差异
这两个模式有相似性,它们都为另一个对象提供间接性的访问,而且都是从自身以外的一个接口向这个对象转发请求
但是从功能上,两个模式是不一样的.适配器模式主要用来解决接口之间不匹配的问题,它通常是为所适配的对象提供一个不同的接口;而代理模式会实现和目标对象相同的接口
2.代理模式和装饰模式
这两个模式从实现上相似,但是功能上是不同的.
装饰模式的实现和保护代理的实现上是类似的,都是在转调其他对象的前后执行一定的功能.但是它们的目的和功能都是不同的
装饰模式的目的是为了让你不生成子类就可以给对象添加职责,也就是为了动态地增加功能;而代理模式的主要目的是控制对对象的访问
第12章 观察者模式(Observer)
12.1 场景问题
12.1.1 订阅报纸的过程
12.1.2 订阅报纸的问题
12.2 解决方案
12.2.1 使用观察者模式来解决问题
Q:观察者者模式的定义
A:定义对象间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
Q:应用观察者模式来解决的思路
A:在前面描述的订阅报纸的例子里面,对于报社来硕,在一开始,它并不清楚究竟有多少个订阅者会来订阅报纸,因此,报社需要维护一个订阅者的列表,这样,当报社出版报纸的时候,才能够把报纸发送到所有的订阅者手里.对于订阅者来说,订阅者也就是看报的读者,多个订阅者会订阅同一份报纸
这就出现了一个典型的一对多的对象关系,一个报纸对象,会有多个订阅者对象来订阅,当报纸出版的时候,也就是报纸对象改变的时候,需要通知所有的订阅者对象.那么怎么来建立并维护这样的关系呢?
观察者模式可以处理这种问题,观察者模式把这多个订阅者称为观察者:Observer,多个观察者观察的对象被称为目标:Subject
一个目标可以有任意多个观察者对象,一旦目标的状态发生了改变,所有注册的观察者都会得到通知,然后各个观察者会对通知做出相应的响应,执行相应的业务功能处理,并使自己的状态和目标对象的状态保持一致.
12.2.2 观察者模式的结构和说明
[Subject] 目标对象,通常具有如下功能
1.一个目标可以被多个观察者观察
2.目标提供对观察者注册和退订的维护
3.当目标的状态发生变化时,目标负责通知所有注册的,有效的观察者
[Observer] 定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理.可以在这个方法里面回调目标对象,以获取目标对象的数据
[ConcreteSubject] 具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知所有注册的,有效的观察者,让观察者执行相应的处理
[ConcreteObserver] 观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,比如更新自身的状态以保持和目标的相应状态一致
12.2.3 观察者模式示例代码
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { } } public class Subject { private List<Observer> observers = new List<Observer>(); public void attach(Observer observer) { observers.Add(observer); } public void detach(Observer observer) { observers.Remove(observer); } protected void notifyObservers() { foreach (Observer observer in observers) { } } } public class ConcreteSubject : Subject { private string subjectState; public string getSubjectState() { return subjectState; } public void setSubjectState(string subjectState) { this.subjectState = subjectState; this.notifyObservers(); } } public interface Observer { void update(Subject subject); } public class ConcreteObserver : Observer { private string observerState; public void update(Subject subject) { observerState = ((ConcreteSubject) subject).getSubjectState(); } } }
12.2.4 使用观察者模式实现示例
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main(string[] args) { NewsPaper subject = new NewsPaper(); Reader reader1 = new Reader(); reader1.setName("张三"); Reader reader2 = new Reader(); reader2.setName("李四"); Reader reader3 = new Reader(); reader3.setName("王五"); subject.attach(reader1); subject.attach(reader2); subject.attach(reader3); subject.setContent("本期内容是观察者模式"); } } public class Subject { private List<Observer> readers = new List<Observer>(); public void attach(Observer reader) { readers.Add(reader); } public void detach(Observer reader) { readers.Remove(reader); } protected void notifyObservers() { foreach (Observer observer in readers) { observer.update(this); } } } public interface Observer { void update(Subject subject); } public class NewsPaper : Subject { private string content; public string getContent() { return content; } public void setContent(string content) { this.content = content; notifyObservers(); } } public class Reader : Observer { private string name; public void update(Subject subject) { Console.WriteLine(name + "收到报纸了,阅读它.内容是 === " + ((NewsPaper)subject).getContent()); } public string getName() { return name; } public void setName(string name) { this.name = name; } } }
12.3 模式讲解
12.3.1 认识观察者模式
Q:目标和观察者之间的关系
A:按照模式的定义,目标和观察者之间是典型的一对多的关系
但是要注意,如果观察者只有一个,也是可以的,这样就变相实现了目标和观察者之间一对一的关系,这也使得在处理一个对象的状态变化会影响到另一个对象的时候,也可以考虑使用观察者模式
同样地,一个观察者也可以观察多个目标,如果观察者为多个目标定义的通知更新方法都是update方法的话,这会带来麻烦,因为需要接收多个目标的通知,如果是一个update的方法,那就需要在方法内部区分,到底这个更新的通知来自于哪一个目标,不同的目标有不同的后续操作
一般情况下,观察者应该为不同的观察者定义不同的回调方法,这样实现最简单,不需要在update方法内部进行区分
Q:单向依赖
A:在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而目标是不会依赖于观察者的
它们之间联系的主动权掌握在目标手中,只有目标知道什么时候需要通知观察者.在整个过程中,观察者始终是被动的,被动地等待目标的通知,等待目标传值给它
对目标而言,所有的观察者都是一样的,目标会一视同仁地对待,当然也可以通过在目标中进行控制,实现有区别地对待观察者,比如某些状态变化,只需要通知部分观察者,但那是属于稍微变形的用法了,不属于标准的,原始的观察者模式了.
12.3.2 推模式和拉模型
1.推模型
目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据,相当于是在广播通信
2.拉模型
目标对象在通知观察者的时候,只传递少量信息.如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据.一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了(前面的例子就是典型的拉模型)
推模型例子
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { } } public interface Observer { void update(string content); } public class Reader : Observer { private string name; public string getName() { return name; } public void setName(string name) { this.name = name; } public void update(string content) { Console.WriteLine(name + "收到报纸了,阅读它.内容是 === " + content); } } public class Subject { private List<Observer> readers = new List<Observer>(); public void attach(Observer reader) { readers.Add(reader); } public void detach(Observer reader) { readers.Remove(reader); } protected void notifyObservers(string content) { foreach (Observer observer in readers) { observer.update(content); } } } public class NewsPaper : Subject { private string content; public string getContent() { return content; } public void setContent(string content) { this.content = content; notifyObservers(content); } } }
两种模型的比较
1.推模型是假定目标对象知道观察者需要的数据,而拉模型是目标对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传给观察者,让观察者自己去按需取值
2.推模型可能会使得观察者对象难以复用,因为观察者定义的update方法是按需而定义的,可能无法兼顾没有考虑到的使用情况.这就意味着出现新的情况的时候,就可能需要提供新的update方法,或者是干脆重新实现观察者
而拉模型就不会造成这样的情况,因为拉模型下,update方法的参数是目标对象本身,这基本上是目标对象能传递的最大数据集合了,基本上可以适应各种情况的需要
12.3.3 Java中的观察者模式
12.3.4 观察者模式的优缺点
优点:
1.观察者模式实现了观察者和目标之间的抽象耦合
原本目标对象在状态发生改变的时候,需要直接调用所有的观察者对象,但是抽象出观察者接口以后,目标和观察者就只是在抽象层面上耦合了,也就是说目标只是知道观察者接口,并不知道具体的观察者的类,从而实现目标类和具体的观察者类之间解耦
2.观察者模式实现了动态联动
所谓联动,就是做一个操作会引起其他相关的操作.由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态地控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动
3.观察者模式支持广播通信
缺点:
1.可能会引起无谓的操作
由于观察者模式每次都是广播通信,不管观察者需不需要,每个观察者都会调用update方法,如果观察者不需要执行相应处理,那么这次操纵就浪费了.其实浪费了还好,最怕引起误更新,那就麻烦了,比如,本应该在执行这次状态更新前把某个观察者删除掉,这样通知的时候就没有这个观察者了,但是现在忘掉了,那么就会引起误操作
12.3.5 思考观察者模式
Q:观察者模式的本质
A:触发联动
当修改目标对象的状态时,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法,其实就相当于联动调用这些观察者的方法
而且这个联动还是动态的,可以通过注册和取消注册来控制观察者,因而可以在程序运行期间,通过动态地控制观察者,来变相地实现添加和删除某些功能处理,这些功能j就是观察者在update的时候执行的功能
同时目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确地联动起来
理解这个本质对我们非常有用,对于我们识别和使用观察者模式有非常重要的意义,尤其是在变形使用的时候.万变不离其宗
Q:何时选用观察者模式
1.当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式,将这两者封装成观察者和目标对象,当目标对象变化的时候,依赖于它的观察者对象也会发生相应的变化.这样就把抽象模型的这两个方面分离开了,使得它们可以独立地改变和复用。
2.如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变,这种情况可以选用观察者模式,被更改的那一个对象很明显就相当于是目标对象,而需要连带修改的多个其他对象,就作为多个观察者对象了.
3.当一个对象必须通知其他的对象,但是你又希望这个对象和其他被通知的对象是松散耦合的.也就是说这个对象其实不想知道具体被通知的对象.这种情况可以选用观察者模式,这个对象就相当于是目标对象,而被它通知的对象就是观察者对象了.
12.3.6 Swing中的观察者
12.3.7 简单变形示例-区别对待观察者
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { WaterQulity subject = new WaterQulity(); WatcherObserver watcher1 = new Watcher(); watcher1.setJob("监测人员"); WatcherObserver watcher2 = new Watcher(); watcher2.setJob("预警人员"); WatcherObserver watcher3 = new Watcher(); watcher3.setJob("监测部门领导"); subject.attach(watcher1); subject.attach(watcher2); subject.attach(watcher3); Console.WriteLine("当水质为正常的时候------"); subject.setPolluteLevel(0); Console.WriteLine("当水质为轻度污染的时候------"); subject.setPolluteLevel(1); Console.WriteLine("当水质为中度污染的时候------"); subject.setPolluteLevel(2); } } public abstract class WaterQualitySubject { protected List<WatcherObserver> observers = new List<WatcherObserver>(); public void attach(WatcherObserver observer) { observers.Add(observer); } public void detach(WatcherObserver observer) { observers.Remove(observer); } public abstract void notifyWatchers(); public abstract int getPolluteLevel(); } public interface WatcherObserver { void update(WaterQualitySubject subject); void setJob(string job); string getJob(); } public class WaterQulity : WaterQualitySubject { private int polluteLevel = 0; public override int getPolluteLevel() { return polluteLevel; } public void setPolluteLevel(int polluteLevel) { this.polluteLevel = polluteLevel; this.notifyWatchers(); } public override void notifyWatchers() { foreach (WatcherObserver watcherObserver in observers) { if (this.polluteLevel >= 0) { if ("监测人员".Equals(watcherObserver.getJob())) { watcherObserver.update(this); } } if (this.polluteLevel >= 1) { if ("预警人员".Equals(watcherObserver.getJob())) { watcherObserver.update(this); } } if (this.polluteLevel >= 2) { if ("监测部门领导".Equals(watcherObserver.getJob())) { watcherObserver.update(this); } } } } } public class Watcher : WatcherObserver { private string job; public string getJob() { return job; } public void setJob(string job) { this.job = job; } public void update(WaterQualitySubject subject) { Console.WriteLine(job + "获取到通知,当前污染级别为: " + subject.getPolluteLevel()); } } }
12.3.8 相关模式
1.观察者模式和状态模式
观察者模式和状态模式是有相似之处的.
观察者模式是当目标状态发生改变时,触发并通知观察者,让观察者去执行相应的操作.而状态模式是根据不同的状态,选择不同的实现,这个实现类的主要功能就是针对状态相应地操作,它不像观察者,观察者本身还有很多其他的功能,接收通知并执行相应处理只是观察者的部分功能.
当然观察者模式和状态模式是可以结合使用的.观察者模式的重心在触发联动,但是到底决定哪些观察者会被联动,这时就可以采用状态模式来实现了,也可以采用策略模式来进行选择需要联动的观察者
2.观察者模式和中介者模式
观察者模式和中介者模式是可以结合使用的
前面的例子中目标都只是简单地通知一下,然后让各个观察者自己去完成更新就结束了.如果观察者和被观察的目标之间的交互很复杂,比如,有一个界面,里面有三个下拉列表组件,分别是选择国家,省份/州,具体的城市,很明显这是一个三级联动,当你选择一个国家的时候,省份/州应该相应改变数据,省份/州一改变,具体的城市也需要改变.
这种情况下,很明显需要相关的状态都联动准备好了,然后再一次性地通知观察者.也就是界面做更新处理,不会仅国家改变一下,省份和城市还没有改,就通知界面更新.这种情况就可以使用中介者模式来封装观察者和目标的关系.
在使用Swing的小型应用里面,也可以使用中介者模式,比如,把一个界面所有的事件用一个对象来处理,把一个组件触发事件以后,需要操作其他组件的动作都封装在一起,这个对象就是典型的中介者
第13章 命令模式(Command)
13.1 场景问题
13.1.1 如何开机
13.1.2 与我何干
13.1.3 有何问题
13.2 解决方案
13.2.1 使用命令模式来解决问题
Q:命令模式的定义
A:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作
13.2.2 命令模式的结构和说明
[Command] 定义命令的接口,声明执行的方法
[ConcreteCommand] 命令接口实现对象,是"虚"的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作
[Receiver] 接收者,真正执行命令的对象,任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能
[Invoker] 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象.这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口
[Client] 创建具体的命令对象,并且设置命令对象的接收者.注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client成为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行
13.2.3 命令模式示例代码
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Receiver receiver = new Receiver(); Command command = new ConcreteCommand(receiver); Invoker invoker = new Invoker(); invoker.setCommand(command); invoker.runCommand(); } } public interface Command { void execute(); } public class ConcreteCommand : Command { private Receiver receiver = null; private string state; public ConcreteCommand(Receiver receiver) { this.receiver = receiver; } public void execute() { receiver.action(); } } public class Receiver { public void action() { Console.WriteLine("action"); } } public class Invoker { private Command command = null; public void setCommand(Command command) { this.command = command; } public void runCommand() { command.execute(); } } }
13.2.4 使用命令模式来实现示例
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { MainBoardApi mainBoard = new GigaMainBoard(); OpenCommand openCommand = new OpenCommand(mainBoard); Box box = new Box(); box.setCommand(openCommand); box.openButtonPressed(); } } // Receiver public interface MainBoardApi { void open(); } public class GigaMainBoard : MainBoardApi { public void open() { Console.WriteLine("技嘉主板现在正在开机,请等候"); Console.WriteLine("接通电源......"); Console.WriteLine("设备检查......"); Console.WriteLine("装载系统......"); Console.WriteLine("机器正常运转起来......"); Console.WriteLine("机器已经正常打开,请操作"); } } // Command public interface Command { void execute(); } public class OpenCommand : Command { private MainBoardApi mainBoard = null; public OpenCommand(MainBoardApi mainBoard) { this.mainBoard = mainBoard; } public void execute() { mainBoard.open(); } } // Invoker public class Box { private Command openCommand; public void setCommand(Command command) { this.openCommand = command; } public void openButtonPressed() { openCommand.execute(); } } }
13.3 模式讲解
13.3.1 认识命令模式
Q:命令模式的关键
A:命令模式的关键之处就是把请求封装成对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储,转发,记录,处理,撤销等,整个命令模式都是围绕整个对象在进行
Q:命令模式的组装和调用
A:在命令模式中经常会有一个命令的组装者,用它来维护命令的"虚"实现和真实实现之间的关系.如果是超级智能的命令,也就是说命令对象自己完全实现好了,不需要接收者,那就是命令模式的退化,不需要接收者,自然也不需要组装者了.
而真正的用户就是具体化请求的内容,然后提交请求进行触发就可以了.真正的用户会通过Invoker来触发命令
在实际开发过程中,Client和Invoker可以融合在一起,由客户在使用命令模式的时候,先进行命令对象和接收者的组装,组装完成后,就可以调用命令执行请求
Q:命令模式的接收者
A:接收者可以是任意的类,对它没有什么特殊要求,这个对象知道如何真正执行命令的操作,执行时是从Command的实现l类里面转调过来
一个接收者对象可以处理多个命令,接收者和命令之间没有约定的对应关系.接收者提供的方法个数,名称,功能和命令中的可以不一样,只要能够通过调用接收者的方法来实现命令对应的功能就可以了.
Q:智能命令
A:在标准的命令模式里面,命令的实现类是没有真正实现命令要求的功能的,真正执行命令的功能的是接收者
如果命令的实现对象比较智能,它自己就能真实地实现命令要求的功能,而不再需要调用接收者,那么这种情况就称为智能命令
也可以有半智能的命令,命令对象知道部分实现,其他的还是需要调用接收者来完成,也就是说命令的功能由命令对象和接收者功能来完成
Q:发起请求的对象和真正实现的对象是解耦的
A:请求究竟由谁处理?如何处理?发起请求的对象是不知道的,也就是发起请求的对象和真正实现的对象是解耦的.发起请求的对象只管发出命令,其他的就不管了
13.3.2 参数化配置
Q:命令模式的参数化配置
A:可以用不同的命令对象,去参数化配置客户的请求
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { MainBoardApi mainBoard = new GigaMainBoard(); OpenCommand openCommand = new OpenCommand(mainBoard); ResetCommand resetCommand = new ResetCommand(mainBoard); Box box = new Box(); box.setOpenCommand(openCommand); box.setResetCommand(resetCommand); box.openButtonPressed(); box.resetButtonPressed(); } } // Receiver public interface MainBoardApi { void open(); void reset(); } public class GigaMainBoard : MainBoardApi { public void open() { Console.WriteLine("技嘉主板现在正在开机,请等候"); Console.WriteLine("接通电源......"); Console.WriteLine("设备检查......"); Console.WriteLine("装载系统......"); Console.WriteLine("机器正常运转起来......"); Console.WriteLine("机器已经正常打开,请操作"); } public void reset() { Console.WriteLine("技嘉主板现在正在重新启动机器,请等候"); Console.WriteLine("机器已经正常打开,请操作"); } } // Command public interface Command { void execute(); } public class OpenCommand : Command { private MainBoardApi mainBoard = null; public OpenCommand(MainBoardApi mainBoard) { this.mainBoard = mainBoard; } public void execute() { mainBoard.open(); } } public class ResetCommand : Command { private MainBoardApi mainBoard = null; public ResetCommand(MainBoardApi mainBoard) { this.mainBoard = mainBoard; } public void execute() { this.mainBoard.reset(); } } // Invoker public class Box { private Command openCommand; private Command resetCommand; public void setOpenCommand(Command command) { this.openCommand = command; } public void setResetCommand(Command command) { this.resetCommand = command; } public void openButtonPressed() { openCommand.execute(); } public void resetButtonPressed() { resetCommand.execute(); } } }
13.3.3 可撤销的操作
Q:可撤销的操作
A:可撤销操作的意思是:放弃该操作,回到未执行该操作前的状态
有两种基本的思路来实现可撤销的操作:
1.补偿式,又称反操作式,比如被撤销的操作是加的功能,那撤销的实现就变成减的功能;同理被撤销的操作是打开的功能,那么撤销的实现就变成关闭的功能
2.存储恢复式,意思就是把操作前的状态记录下来,然后要撤销操作的时候就直接恢复回去就可以了(放在备忘录模式中讲解)
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { OperationApi operation = new Operation(); AddCommand addCmd = new AddCommand(operation,5); SubstractCommand substractCommand = new SubstractCommand(operation,3); Calculator calculator = new Calculator(); calculator.setAddCmd(addCmd); calculator.setSubstractCmd(substractCommand); Console.WriteLine("补偿式(反操作式)"); calculator.addPressed(); Console.WriteLine("一次加法运算后的结果: " + operation.getResult()); calculator.substractPressed(); Console.WriteLine("一次减法运算后的结果: " + operation.getResult()); calculator.undoPressed(); Console.WriteLine("撤销一次后的结果为: " + operation.getResult()); calculator.undoPressed(); Console.WriteLine("再撤销一次后的结果为: " + operation.getResult()); calculator.redoPressed(); Console.WriteLine("恢复操作一次后的结果为: " + operation.getResult()); calculator.redoPressed(); Console.WriteLine("再恢复操作一次后的结果为: " + operation.getResult()); } } public interface OperationApi { int getResult(); void setResult(int result); void add(int num); void substract(int num); } public class Operation : OperationApi { private int result; public int getResult() { return result; } public void setResult(int result) { this.result = result; } public void add(int num) { result += num; } public void substract(int num) { result -= num; } } public interface Command { void execute(); void undo(); } public class AddCommand : Command { private OperationApi operation = null; private int operNum; public AddCommand(OperationApi operation, int operNum) { this.operation = operation; this.operNum = operNum; } public void execute() { this.operation.add(operNum); } public void undo() { this.operation.substract(operNum); } } public class SubstractCommand : Command { private OperationApi operation = null; private int operNum; public SubstractCommand(OperationApi operation, int operNum) { this.operation = operation; this.operNum = operNum; } public void execute() { this.operation.substract(operNum); } public void undo() { this.operation.add(operNum); } } public class Calculator { private Command addCmd = null; private Command substractCmd = null; private List<Command> undoCmds = new List<Command>(); private List<Command> redoCmds = new List<Command>(); public void setAddCmd(Command command) { this.addCmd = command; } public void setSubstractCmd(Command command) { this.substractCmd = command; } public void addPressed() { this.addCmd.execute(); undoCmds.Add(addCmd); } public void substractPressed() { this.substractCmd.execute(); undoCmds.Add(substractCmd); } public void undoPressed() { if (undoCmds.Count > 0) { Command cmd = undoCmds[undoCmds.Count - 1]; cmd.undo(); this.redoCmds.Add(cmd); undoCmds.Remove(cmd); } else { Console.WriteLine("很抱歉,没有可撤销的命令"); } } public void redoPressed() { if (redoCmds.Count > 0) { Command cmd = redoCmds[redoCmds.Count - 1]; cmd.execute(); undoCmds.Add(cmd); redoCmds.Remove(cmd); } else { Console.WriteLine("很抱歉,没有可恢复的命令"); } } } }
13.3.4 宏命令
Q:什么是宏命令
A:简单点说就是包含多个命令的命令,是一个命令的组合
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Waiter waiter = new Waiter(); Command chop = new ChopCommand(); Command duck = new DuckCommand(); Command meat = new CoolMeatCommand(); waiter.orderDish(chop); waiter.orderDish(duck); waiter.orderDish(meat); waiter.orderOver(); } } public interface CookApi { void cook(string name); } public class HotCook : CookApi { public void cook(string name) { Console.WriteLine("本厨师正在做: " + name); } } public class CoolCook : CookApi { public void cook(string name) { Console.WriteLine("凉菜" + name + "已经做好,本厨师正在装盘"); } } public interface Command { void execute(); } public class ChopCommand : Command { private CookApi cookApi = null; public void setCookApi(CookApi cookApi) { this.cookApi = cookApi; } public void execute() { this.cookApi.cook("绿豆排骨煲"); } } public class DuckCommand : Command { private CookApi cookApi = null; public void setCookApi(CookApi cookApi) { this.cookApi = cookApi; } public void execute() { this.cookApi.cook("北京烤鸭"); } } public class CoolMeatCommand : Command { private CookApi cookAPi = null; public void setCookAPi(CookApi cookApi) { this.cookAPi = cookApi; } public void execute() { this.cookAPi.cook("蒜泥白肉"); } } // 宏命令对象 public class MenuCommand : Command { private List<Command> col = new List<Command>(); public void addCommand(Command command) { col.Add(command); } public void execute() { foreach (Command command in col) { command.execute(); } } } // 负责组合菜单,负责组装每个菜单和具体的实现者,还负责执行调用,相当于标准Command模式的Client+Invoker public class Waiter { private MenuCommand menuCommand = new MenuCommand(); public void orderDish(Command cmd) { CookApi hotCook = new HotCook(); CookApi coolCook = new CoolCook(); if (cmd is DuckCommand) { ((DuckCommand) cmd).setCookApi(hotCook); } else if (cmd is ChopCommand) { ((ChopCommand) cmd).setCookApi(hotCook); } else if (cmd is CoolMeatCommand) { ((CoolMeatCommand) cmd).setCookAPi(coolCook); } menuCommand.addCommand(cmd); } public void orderOver() { menuCommand.execute(); } } }
13.3.5 队列请求
13.3.6 日志请求
13.3.7 命令模式的优点
13.3.8 思考命令模式
Q:命令模式的本质
A:封装请求
前面讲了,命令模式的关键就是把请求封装成为命令对象,然后就可以对这个对象进行一系列的处理了,不如上面讲到的参数化配置,可撤销操作,宏命令,队列请求,日志请求等功能处理.
Q:何时选用命令模式
A: 1.如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式,将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置
2.如果需要在不同的时刻指定,排列和执行请求,可以选用命令模式.将这些请求封装成为命令对象,然后实现将请求队列化
3.如果需要支持取消操作,可以选用命令模式,通过管理命令对象,能很容易地实现命令的恢复和重做功能
4.如果需要支持当系统崩溃时,能将系统的操作功能重新执行一遍,可以选用命令模式,将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能.
5.在需要事务的系统中,可以选用命令模式,命令模式提供了对事务进行建模的方法,命令模式有一个别名就是Transaction
13.3.9 退化的命令模式
using System; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Command cmd = new PrintService("退化的命令模式示例"); Invoker invoker = new Invoker(); invoker.setCmd(cmd); invoker.startPrint(cmd); } } public interface Command { void execute(); } public class PrintService : Command { private string str = ""; public PrintService(string s) { str = s; } public void execute() { // 智能的体现,自己知道怎么实现命令所要求的功能,并真正地实现了相应的功能,不再转调接收者了 Console.WriteLine("打印的内容为 = " + str); } } public class Invoker { private Command cmd = null; public void setCmd(Command cmd) { this.cmd = cmd; } public void startPrint() { this.cmd.execute(); } public void startPrint(Command cmd) { Console.WriteLine("在Invoker中,输出服务前"); cmd.execute(); Console.WriteLine("输出服务结束"); } } }
13.3.10 相关模式
1.命令模式和组合模式
这两个模式可以组合使用
在命令模式中,实现宏命令的功能就可以使用组合模式来实现.前面的示例并没有按照组合模式来做,那是为了保持示例的简单,还有突出命令模式的实现,这点请注意
2.命令模式和备忘录模式
这两个模式可以组合使用
在命令模式中,实现可撤销操作功能时,前面讲了有两种实现方式,其中有一种就是保存命令执行前的状态,撤销的时候就把状态恢复.如果采用这种方式实现,就可以考虑使用备忘录模式
如果状态存储在命令对象中,那么还可以使用原型模式,把命令对象当作原型来克隆一个新的对象,然后将克隆出来的对象通过备忘录模式存放
3.命令模式和模板方法
这两个模式从某种意义上有相似的功能,命令模式可以作为模板方法的一种替代模式,也就是说命令模式可以模仿实现模板方法模式的功能
如同前面讲述的退化的命令模式可以实现Java的回调,而Invoker智能化后向服务进化,如果Invoker的方法就是一个算法骨架,其中有两步在这个骨架里面没有具体实现,需要外部来实现,这个时候就可以通过回调命令接口来实现.
而类似的功能在模板方法中,是先调用抽象方法,然后等待子类来实现.可以看出虽然实现方式不一样,但是可以实现相同的功能.
第14章 迭代器模式(Iterator)
14.1 场景问题
14.1.1 工资表数据的集合
14.1.2 有何问题.
Q:有何问题
A:如何能够以一个统一的方式来访问内部实现不同的聚合对象
14.2 解决方案
14.2.1 使用迭代器模式解决问题
Q:迭代器模式的定义
A:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示
所谓聚合是指一组对象的组合结构
Q:应用迭代器模式来解决问题的思路
A:仔细分析上面的问题,要以一个统一的方式来访问内部实现不同的聚合对象,那么首先需要把这个统一的访问方式定义出来,按照这个统一的访问方式定义出来的接口,在迭代器模式中对应的就是Iterator接口
迭代器迭代的是具体的聚合对象,那么不同的聚合对象就应该有不同的迭代器,为了让迭代器以一个统一的方式来操作聚合对象,因此给所有的聚合对象抽象出一个公共的父类,让它提供操作聚合对象的公共接口,这个抽象的公共父类在迭代器模式中对应的就是Aggregate对象
14.2.2 迭代器模式的结构和说明
[Iterator] 迭代器接口,定义访问和遍历元素的接口
[ConcreteIterator] 具体的迭代器实现对象,实现对聚合对象的遍历,并跟踪遍历时的当前位置
[Aggregate] 聚合对象.定义创建相应迭代器对象的接口
[ConcreteAggregate] 具体聚合对象.实现创建相应的迭代器对象
14.2.3 迭代器模式示例代码
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { string[] names = {"1", "2", "3"}; Aggregate aggregate = new ConcreteAggregate(names); Iterator it = aggregate.createIterator(); it.first(); while (!it.isDone()) { Object obj = it.currentItem(); Console.WriteLine("the object = " + obj); it.next(); } } } public interface Iterator { void first(); void next(); bool isDone(); object currentItem(); } public abstract class Aggregate { public abstract Iterator createIterator(); } public class ConcreteIterator : Iterator { private ConcreteAggregate aggregate; private int index = -1; public ConcreteIterator(ConcreteAggregate aggregate) { this.aggregate = aggregate; } public void first() { index = 0; } public void next() { if (index < aggregate.size()) { index = index + 1; } } public bool isDone() { if (index == aggregate.size()) { return true; } else { return false; } } public object currentItem() { return aggregate.get(index); } } public class ConcreteAggregate : Aggregate { private string[] ss = null; public ConcreteAggregate(string[] ss) { this.ss = ss; } public override Iterator createIterator() { return new ConcreteIterator(this); } public Object get(int index) { Object obj = null; if (index < ss.Length) { obj = ss[index]; } return obj; } public int size() { return this.ss.Length; } } }
14.2.4 使用迭代器模式来实现示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { PayManager payManager = new PayManager(); payManager.calcPay(); Console.WriteLine("集团工资列表: "); test(payManager.createIterator()); SalaryManager salaryManager = new SalaryManager(); salaryManager.calcSalary(); Console.WriteLine("新收购的公司工资列表: "); test(salaryManager.createIterator()); } private static void test(Iterator it) { it.first(); while (!it.isDone()) { Object obj = it.currentItem(); Console.WriteLine("the obj == " + obj); it.next(); } } } public interface Iterator { void first(); void next(); bool isDone(); object currentItem(); } public abstract class Aggregate { public abstract Iterator createIterator(); } public class ArrayIteratorImpl : Iterator { private SalaryManager aggregate = null; private int index = -1; public ArrayIteratorImpl(SalaryManager aggregate) { this.aggregate = aggregate; } public void first() { index = 0; } public void next() { if (index < aggregate.size()) { index = index + 1; } } public bool isDone() { if (index == aggregate.size()) { return true; } return false; } public object currentItem() { return aggregate.get(index); } } public class CollectionIteratorImpl : Iterator { private PayManager aggregate = null; private int index = -1; public CollectionIteratorImpl(PayManager aggregate) { this.aggregate = aggregate; } public void first() { index = 0; } public void next() { if (index < aggregate.size()) { index = index + 1; } } public bool isDone() { if (index == aggregate.size()) { return true; } return false; } public object currentItem() { return aggregate.get(index); } } public class PayModel { private string userName; private double pay; public string getUserName() { return userName; } public void setUserName(string userName) { this.userName = userName; } public double getPay() { return pay; } public void setPay(double pay) { this.pay = pay; } public override string ToString() { return "userName = " + userName + ",pay = " + pay; } } public class PayManager : Aggregate { private ArrayList list = new ArrayList(); public override Iterator createIterator() { return new CollectionIteratorImpl(this); } public Object get(int index) { Object obj = null; if (index < this.list.Count) { obj = list[index]; } return obj; } public int size() { return list.Count; } public ArrayList getPayList() { return list; } public void calcPay() { PayModel pm1 = new PayModel(); pm1.setPay(3800); pm1.setUserName("张三"); PayModel pm2 = new PayModel(); pm2.setPay(5800); pm2.setUserName("李四"); list.Add(pm1); list.Add(pm2); } } public class SalaryManager : Aggregate { private PayModel[] pms = null; public override Iterator createIterator() { return new ArrayIteratorImpl(this); } public object get(int index) { Object obj = null; if (index < pms.Length) { obj = pms[index]; } return obj; } public int size() { return pms.Length; } public PayModel[] getPays() { return pms; } public void calcSalary() { PayModel pm1 = new PayModel(); pm1.setPay(2200); pm1.setUserName("王五"); PayModel pm2 = new PayModel(); pm2.setPay(3600); pm2.setUserName("赵六"); pms = new PayModel[2]; pms[0] = pm1; pms[1] = pm2; } } }
14.3 模式讲解
14.3.1 认识迭代器模式
Q:迭代器模式的功能
A:迭代器模式的功能主要在于提供对聚合对象的迭代访问.迭代器就围绕着这个"访问"做文章,延伸出很多的功能来,比如:
1.以不同的方式遍历聚合对象,比如向前,向后等.
2.对同一个聚合同时进行多个遍历
3.以不同的遍历策略来遍历聚合,比如是否需要过滤等
4.多态迭代,含义是:为不同的聚合结构提供统一的迭代接口,也就是说通过一个迭代接口可以访问不同的聚合结构,这就叫做多态迭代.上面的示例就已经实现了多态迭代.事实上,标准的迭代模式实现基本上都是支持多态迭代的.
Q:迭代器模式的关键思想
A:迭代器模式的关键思想就是把对聚合对象的遍历和访问从聚合对象中分离出来,放入单独的迭代器中,这样聚合对象会变得简单一些;而且迭代器和聚合对象可以独立地变化和发展,会大大加强系统的灵活性.
Q:内部迭代器和外部迭代器
A: 所谓内部迭代器,指的是由迭代器自己来控制迭代下一个元素的步骤,客户端无法干预
所谓外部迭代器,指的是由客户端来控制迭代下一个元素的步骤
14.3.2 使用Java的迭代器
14.3.3 带迭代策略的迭代器
14.3.4 双向迭代器
14.3.5 迭代器模式的优点
优点:
1.更好的封装性
2.迭代器模式可以让你访问一个聚合对象的内容,而无须暴露该聚合对象的内部表示,从而提高聚合对象的封装性
3.可以以不同的遍历方式来遍历一个聚合
4.使用迭代器模式,使得聚合对象的内容和具体的迭代算法分离开.这样就可以通过使用不同的迭代器的实例,不同的遍历方式来遍历一个聚合对象了.
5.迭代器简化了聚合的接口
6.有了迭代器的接口,则聚合本身就不需要再定义这些接口了,从而简化了聚合的接口定义
7.简化客户端调用
8.迭代器为遍历不同的聚合对象提供了一个统一的接口,使得客户端遍历聚合对象的内容变得更简单
9.同一个聚合上可以有多个遍历
10.每个迭代器保持它自己的遍历状态,比如前面实现中的迭代索引位置,因此可以对同一个聚合对象同时进行多个遍历
14.3.6 思考迭代器模式
Q:迭代器模式的本质
A:控制访问聚合对象中的元素
Q:何时选用迭代器模式
A: 1.如果你希望提供访问一个聚合对象的内容,但是又不想暴露它的内部表示的时候,可以使用迭代器模式来提供迭代器接口,从而让客户端只是通过迭代器的接口来访问聚合对象,而无须关系聚合对象的内部实现
2.如果你希望有多种遍历方式可以访问聚合对象,可以使用迭代器模式
3.如果你希望为遍历不同的聚合对象提供一个统一的接口,可以使用迭代器模式
14.3.7 翻页迭代
14.3.8 相关模式
1.迭代器模式和组合模式
这两个模式可以组合使用
组合模式是一种递归的对象结构,在枚举某个组合对象的子对象的时候,通常会使用迭代器模式
2.迭代器模式和工厂方法模式
这两个模式可以组合使用
在聚合对象创建迭代器的时候,通常会采用工厂方法模式来实例化相应的迭代器对象
第15章 组合模式(Composite)
15.1 场景问题
15.1.1 商品类别树
商品类别树:
1.有一个根节点,比如服装,它没有父节点,它可以包含其他的节点
2.树枝节点,有一类节点可以包含其他的节点,称之为树枝节点,比如男装,女装
3.叶子节点,有一类节点没有子节点,称之为叶子节点,比如衬衣,夹克,裙子,套装
15.1.2 不用模式的解决方案
要管理商品类别树,就是要管理树的各个节点,现在树上的节点有三类,根节点,树枝节点和叶子节点,再进一步分析发现,根节点和树枝节点是类似的,都是可以包含其他节点的节点,把它们称为容器节点
这样一来,商品类别树的节点就被分成了两种,一种是容器节点,另一种是叶子节点.容器节点可以包含其他的容器节点或者叶子节点.把它们分别实现成为对象,也就是容器对象和叶子对象,容易对象可以包含其他的容器对象或者叶子对象,换句话说,容器对象是一种组合对象
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Composite root = new Composite("服装"); Composite c1 = new Composite("男装"); Composite c2 = new Composite("女装"); Leaf leaf1 = new Leaf("衬衣"); Leaf leaf2 = new Leaf("夹克"); Leaf leaf3 = new Leaf("裙子"); Leaf leaf4 = new Leaf("套装"); root.addComposite(c1); root.addComposite(c2); c1.addLeaf(leaf1); c1.addLeaf(leaf2); c2.addLeaf(leaf3); c2.addLeaf(leaf4); root.printStruct(""); } } public class Leaf { private string name = ""; public Leaf(string name) { this.name = name; } public void printStruct(string preStr) { Console.WriteLine(preStr + "-" + name); } } public class Composite { private List<Composite> childComposite = new List<Composite>(); private List<Leaf> childLeaf = new List<Leaf>(); private string name = ""; public Composite(string name) { this.name = name; } public void addComposite(Composite c) { childComposite.Add(c); } public void addLeaf(Leaf l) { childLeaf.Add(l); } public void printStruct(string preStr) { Console.WriteLine(preStr + "+" + name); preStr += " "; foreach (Leaf leaf in childLeaf) { leaf.printStruct(preStr); } foreach (Composite composite in childComposite) { composite.printStruct(preStr); } } } }
15.1.3 有何问题
Q:有何问题
A:上面的实现,虽然能实现要求的功能,但是有一个很明显的问题:那就是必须区分组合对象和叶子对象,并进行有区别的对待,比如在Composite和Client里面,都需要去区别对待这两种对象
区别对待组合对象和叶子对象,不仅让程序变得复杂,还对功能的扩展也带来不便.实际上,大多数情况下用户并不想要去区别它们,而是认为它们是一样的,这样他们操作起来最简单
15.2 解决方案
15.2.1 使用组合模式来解决问题
Q:组合模式的定义
A:将对象组合成树型结构以表示"部分-整体"的层次结构.组合模式使得用户对单个对象和组合对象的使用具有一致性
仔细分析上面不用模式的例子,要区分组合对象和叶子对象的根本原因,就在于没有把组合对象和叶子对象统一起来,也就是说,组合对象类型h和叶子对象类型是完全不同的类型,这导致了操作的时候必须区分它们
组合模式通过引入一个抽象的组件对象,作为组合对象和叶子对象的父对象,这样就把组合对象和叶子对象统一起来了,用户使用的时候,始终是在操作组件对象,而不再去区分是在操作组合对象还是叶子对象
15.2.2 组合模式的结构和说明
[Component] 抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现
[Leaf] 叶子节点对象,定义和实现叶子对象的行为,不再包含其他的子节点对象
[Composite] 组合对象,通常会存储子组件,定义包含子组件的那些组件的行为,并实现在组件接口中定义的与子组件有关的操作
[Client] 客户端,通过组件接口来操作组合结构里面的组件对象
15.2.3 组合模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Composite root = new Composite(); Composite c1 = new Composite(); Composite c2 = new Composite(); Leaf leaf1 = new Leaf(); Leaf leaf2 = new Leaf(); Leaf leaf3 = new Leaf(); root.addChild(c1); root.addChild(c2); root.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3); Component o = root.getChildren(1); Console.WriteLine(o); } } public abstract class Component { public virtual void addChild(Component child) { throw new NotImplementedException("对象不支持这个功能"); } public virtual void removeChild(Component child) { throw new NotImplementedException("对象不支持这个功能"); } public virtual Component getChildren(int index) { throw new NotImplementedException("对象不支持这个功能"); } public abstract void someOperation(); } public class Composite : Component { private List<Component> childComponents = null; public override void addChild(Component child) { if (childComponents == null) { childComponents = new List<Component>(); } childComponents.Add(child); } public override void removeChild(Component child) { if (childComponents != null) { childComponents.Remove(child); } } public override Component getChildren(int index) { if (childComponents != null) { if (index >= 0 && index < childComponents.Count) { return childComponents[index]; } } return null; } public override void someOperation() { if (childComponents != null) { foreach (Component component in childComponents) { component.someOperation(); } } } } public class Leaf : Component { public override void someOperation() { } } }
15.2.4 使用组合模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Component root = new Composite("服装"); Component c1 = new Composite("男装"); Component c2 = new Composite("女装"); Component leaf1 = new Leaf("衬衣"); Component leaf2 = new Leaf("夹克"); Component leaf3 = new Leaf("裙子"); Component leaf4 = new Leaf("套装"); root.addChild(c1); root.addChild(c2); c1.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3); c2.addChild(leaf4); root.printStruct(""); } } public abstract class Component { public virtual void addChild(Component child) { throw new NotImplementedException("对象不支持这个功能"); } public virtual void removeChild(Component child) { throw new NotImplementedException("对象不支持这个功能"); } public virtual Component getChildren(int index) { throw new NotImplementedException("对象不支持这个功能"); } public abstract void printStruct(string preStr); } public class Composite : Component { private string name = ""; private List<Component> childComponents = null; public Composite(string name) { this.name = name; } public override void addChild(Component child) { if (childComponents == null) { childComponents = new List<Component>(); } childComponents.Add(child); } public override void printStruct(string preStr) { Console.WriteLine(preStr + "+" + name); if (childComponents != null) { preStr += " "; foreach (Component component in childComponents) { component.printStruct(preStr); } } } } public class Leaf : Component { private string name = ""; public Leaf(string name) { this.name = name; } public override void printStruct(string preStr) { Console.WriteLine(preStr + "-" + name); } } }
15.3 模式讲解
15.3.1 认识组合模式
Q:组合模式的目的
A:让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操作
实现这个目标的关键之处,是设计一个抽象的组件类,让它可以代表组合对象和叶子对象.这样一来,客户端就不用区分到底操作的是组合对象还是叶子对象了,只需要把它们全部当作组件对象进行统一的操作就可以了
Q:对象树
A:通常,组合模式会组合出树型结构来,组成这个树型结构所使用的多个组件对象,就自然地形成了对象树
这也意味着,所有可以使用对象树来描述或操作的功能,都可以考虑使用组合模式,比如读取XML文件,或是对语句进行语法解析等.
Q:最大化Component定义
A:前面讲到了组合模式的目的,让客户端不再区分操作的是组合对象还是叶子对象,而是以一种统一的方式来操作
由于要统一两种对象的操作,所以Component中的方法也主要是两种对象对外方法的和.换句话说,有点大杂烩的意思,组件里面既有叶子对象需要的方法,也有组合对象需要的方法
常见的做法是在Component中为对某些子对象没有意义的方法提供默认的实现,或是默认抛出不支持该功能的例外,这样一来,如果子对象需要这个功能,那就覆盖实现它,如果不需要,那就不用管了,使用父类的默认实现就可以了
从另一个层面来说,如果把叶子对象看成是一个特殊的Composite对象,也就是没有子节点的组合对象,对于Component而言,子对象就被全部看做是组合对象,因此定义的所有方法都是有意义的了.
15.3.2 安全性和透明性
Q:安全性和透明性
A: 安全性:从客户使用组合模式上看是否更安全,如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的功能
透明性:从客户使用组合模式上看,是否需要区分到底是组合对象还是叶子对象.如果是透明的,那就不用再区分,对于客户而言,都是组件对象,具体的类型对于客户而言是透明的,是客户无须关心的.
Q:透明性的实现
A: 如果把管理子组件的操作定义在Component中,那么客户端只需要面对Component,而无须关心具体的组件类型,这种实现方式就是透明性的实现.事实上,前面示例的实现方式都是这种实现方式
但是透明性的实现是以安全性为代价的,因为在Component中定义的一些方法,对于叶子对象来说是没有意义的,比如增加,删除子组件对象.而客户不知道这些区别,对客户是透明的,因此客户可能会对叶子对象调用这种增加或删除子组件的方法,这样的操作是不安全的
组合模式的透明性实现,通常的方式是:在Component中声明管理子组件的操作,并在Component中为这些方法提供默认的实现,如果子对象不支持的功能,默认的实现可以是抛出一个例外,来表示不支持这个功能
Q:安全性的实现
A:如果把管理子组件的操作定义在Composite中,那么客户在使用叶子对象的时候,就不会发生使用添加子组件或是删除子组件的操作了,因为压根就没有这样的功能,这种实现方式是安全的
但是这样一来,客户端在使用的时候,就必须区分到底使用的是Composite对象,还是叶子对象,不同对象的功能是不一样的.也就是说,这种实现方式,对客户而言就不是透明的了
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Composite root = new Composite("服装"); Composite c1 = new Composite("男装"); Composite c2 = new Composite("女装"); Leaf leaf1 = new Leaf("衬衣"); Leaf leaf2 = new Leaf("夹克"); Leaf leaf3 = new Leaf("裙子"); Leaf leaf4 = new Leaf("套装"); root.addChild(c1); root.addChild(c2); c1.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3); c2.addChild(leaf4); root.printStruct(""); } } public abstract class Component { public abstract void printStruct(string preStr); } public class Composite : Component { private string name = ""; private List<Component> childComponents = null; public Composite(string name) { this.name = name; } public void addChild(Component child) { if (childComponents == null) { childComponents = new List<Component>(); } childComponents.Add(child); } public override void printStruct(string preStr) { Console.WriteLine(preStr + "+" + name); if (childComponents != null) { preStr += " "; foreach (Component component in childComponents) { component.printStruct(preStr); } } } } public class Leaf : Component { private string name = ""; public Leaf(string name) { this.name = name; } public override void printStruct(string preStr) { Console.WriteLine(preStr + "-" + name); } } }
Q:两种实现方式的选择
A:对于组合模式而言,在安全性和透明性上,会更看重透明性,毕竟组合模式的功能就是要让用户对叶子对象和组合对象的使用具有一致性
而且对于安全性的实现,需要区分是组合对象还是叶子对象.有的时候,需要将对象进行类型转换,却发现类型信心丢失了,只好强行转换,这种类型转换必然是不够安全的.
对于这种情况的处理方法是在Component中定义一个getComposite方法,用来判断是组合对象还是叶子对象,如果是组合对象,就返回组合对象,如果是叶子对象,就返回null,这样就实现了先判断,然后再强制转换
因此在使用组合模式的时候,建议多采用透明性的实现方式,而少用安全性的实现方式
15.3.3 父组件引用
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Composite root = new Composite("服装"); Composite c1 = new Composite("男装"); Composite c2 = new Composite("女装"); Leaf leaf1 = new Leaf("衬衣"); Leaf leaf2 = new Leaf("夹克"); Leaf leaf3 = new Leaf("裙子"); Leaf leaf4 = new Leaf("套装"); root.addChild(c1); root.addChild(c2); c1.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3); c2.addChild(leaf4); root.printStruct(""); Console.WriteLine("------"); root.removeChild(c1); root.printStruct(""); } } public abstract class Component { private Component parent = null; public Component getParent() { return parent; } public void setParent(Component parent) { this.parent = parent; } public virtual List<Component> getChildren() { throw new NotImplementedException("对象不支持这个功能"); } public virtual void addChild(Component child) { throw new NotImplementedException("对象不支持这个功能"); } public virtual void removeChild(Component child) { throw new NotImplementedException("对象不支持这个功能"); } public virtual void getChildren(int index) { throw new NotImplementedException("对象不支持这个功能"); } public abstract void printStruct(string preStr); } public class Composite : Component { private string name = ""; private List<Component> childComponents = null; public Composite(string name) { this.name = name; } public override void addChild(Component child) { if (childComponents == null) { childComponents = new List<Component>(); } childComponents.Add(child); child.setParent(this); } public override void removeChild(Component child) { if (childComponents != null) { int idx = childComponents.IndexOf(child); if (idx != -1) { foreach (Component component in child.getChildren()) { component.setParent(this); childComponents.Add(component); } childComponents.RemoveAt(idx); } } } public override List<Component> getChildren() { return childComponents; } public override void printStruct(string preStr) { Console.WriteLine(preStr + "+" + name); if (childComponents != null) { preStr += " "; foreach (Component component in childComponents) { component.printStruct(preStr); } } } } public class Leaf : Component { private string name = ""; public Leaf(string name) { this.name = name; } public override void printStruct(string preStr) { Console.WriteLine(preStr + "-" + name); } } }
15.3.4 环状引用
15.3.5 组合模式的优缺点
优点
1.定义了包含基本对象和组合对象的类层次结构
在组合模式中,基本对象可以被组合成复杂的组合对象,而组合对象又可以组合成更复杂的组合对象,可以不断地递归组合下去,从而构成一个统一的组合对象的类层次结构
2.统一了组合对象和叶子对象
在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来.
3.简化了客户端调用
组合模式通过统一组合对象和叶子对象,使得客户端在使用它们的时候,不需要再去区分它们,客户不关心使用的到底是什么类型的对象,这就大大简化了客户端的使用
4.更容易扩展
由于客户端是统一地面对Component来操作,因此,新定义的Composite或Leaf子类能够很容易地与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变
缺点
1.组合模式的缺点是很难限制组合中的组件类型
容易增加新的组件也会带来一些问题,比如很难限制组合中的组件类型。
这在需要检测组件类型的时候,使得我们不能依靠编译期的类型约束来完成,必须在运行期间动态检测.
15.3.6 思考组合模式
Q:组合模式的本质
A:统一叶子对象和组合对象
组合模式通过把叶子对象当成特殊的组合对象看待,从而对叶子对象和组合对象一视同仁,全部当成了Component对象,有机地统一了叶子对象和组合对象
正是因为统一了叶子对象和组合对象,在将对象构建成树型结构的时候,才不需要做区分,反正是组件对象里面包含其他的组件对象,如此递归下去;也才使得对于树形结构的操作变得简单,不管对象类型,统一操作
Q:何时选用组合模式
A: 1.如果你想表示对象的部分---整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也容易
2. 如果你希望统一地使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能.
15.3.7 相关模式
1.组合模式和装饰模式
这两个模式可以组合使用
装饰模式在组装多个装饰器对象的时候,是一个装饰器找下一个装饰器,下一个装饰器再找下一个,如此递归下去.其实这种结构也可以使用组合模式来帮助构建,这样一来,装饰器对象就相当于组合模式的Composite对象了.
要让两个模式能很好地组合使用,通常会让它们有一个公共的父类,因此装饰器必须支持组合模式需要的一些功能,比如,增加,删除子组件等
2.组合模式和享元模式
这两个模式可以组合使用
如果组合模式中出现大量相似的组件对象的话,可以考虑使用享元模式来帮助缓存组件对象,这样可以减少对内存的需要
使用享元模式也是条件的,如果组件对象的可变化部分的状态能够从组件对象中分离出去,并且组件对象本身不需要向父组件发送请求的话,就可以采用享元模式
3.组合模式和迭代器模式
这两个模式可以组合使用
在组合模式中,通常可以使用迭代器模式来遍历组合对象的子对象集合,而无需关心具体存放子对象的聚合结构
4.组合模式和访问者模式
这两个模式可以组合使用
访问者模式能够在不修改原有对象结构的情况下,为对象结构中的对象增添新的功能.访问者模式h和组合模式合用,可以把原本分散在Composite和Leaf类中的操作和行为都局部化
如果在使用组合模式的时候,预计到今后可能会有增添其他功能的可能,那么可以采用访问者模式,来预留好添加新功能的方式和通道,这样以后在添加新功能的时候,就不需要再修改已有的对象结构和已经实现的功能了
5.组合模式和职责链模式
这两个模式可以组合使用
职责链模式要解决的问题是:实现请求的发送者和接收者之间解耦,职责链模式的实现方式是把多个接收者组合起来,构成职责链,然后让请求在这条链上传递,直到有接收者处理这个请求为止
可以应用组合模式来构建这条链,相当于是子组件找父组件,父组件又找父组件,如此递归下去,构成一条处理请求的组件对象链
6:组合模式和命令模式
这两个模式可以组合使用
命令模式中有一个宏命令的功能,通常这个宏命令就是使用组合模式来组装出来的.
第16章 模板方法模式(Template Method)
16.1 场景问题
16.1.1 登录控制
16.1.2 不用模式的解决方案
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Test2 { class Program { static void Main(string[] args) { } } public class UserModel { private string uuid, userId, pwd, name; public string getUuid() { return uuid; } public void setUuid(string uuid) { this.uuid = uuid; } public string getUserId() { return userId; } public void setUserId(string userId) { this.userId = userId; } public string getPwd() { return pwd; } public void setPwd(string pwd) { this.pwd = pwd; } public string getName() { return name; } public void setName(string name) { this.name = name; } } public class LoginModel { private string userId, pwd; public string getUserId() { return userId; } public void setUserId(string userId) { this.userId = userId; } public string getPwd() { return pwd; } public void setPwd(string pwd) { this.pwd = pwd; } } public class NormalLogin { public bool login(LoginModel lm) { UserModel um = this.findUserByUserId(lm.getUserId()); if (um != null) { if (um.getUserId().Equals(lm.getPwd()) && um.getPwd().Equals(lm.getPwd())) { return true; } } return false; } private UserModel findUserByUserId(string userId) { UserModel um = new UserModel(); um.setUserId(userId); um.setName("test"); um.setPwd("test"); um.setUserId("User0001"); return um; } } public class WorkerModel { private string uuid, workerId, pwd, name; public string getUuid() { return uuid; } public void setUuid(string uuid) { this.uuid = uuid; } public string getWorkerId() { return workerId; } public void setWorkerId(string workerId) { this.workerId = workerId; } public string getPwd() { return pwd; } public void setPwd(string pwd) { this.pwd = pwd; } public string getName() { return name; } public void setName(string name) { this.name = name; } } public class LoginModel { private string workerId, pwd; public string getWorkerId() { return workerId; } public void setWorkerId(string workerId) { this.workerId = workerId; } public string getPwd() { return pwd; } public void setPwd(string pwd) { this.pwd = pwd; } } public class WorkerLogin { public bool login(LoginModel lm) { WorkerModel wm = findWorkerByWorkerId(lm.getWorkerId()); if (wm != null) { string encryptPwd = this.encryptPwd(lm.getPwd()); if (wm.getWorkerId().Equals(lm.getWorkerId()) && wm.getPwd().Equals(encryptPwd)) { return true; } } return false; } private string encryptPwd(string pwd) { return pwd; } private WorkerModel findWorkerByWorkerId(string workerId) { WorkerModel wm = new WorkerModel(); wm.setWorkerId(workerId); wm.setName("Worker1"); wm.setPwd("worker1"); wm.setUuid("Worker0001"); return wm; } } }
16.1.3 有何问题
Q:有何问题
A:看了上面的实现示例,是不是很简单,但是,仔细看看,总会觉得有点问题,两种登录的实现太相似了,现在是完全分开,当作两个独立的模块来实现的,如果今后要扩展功能,比如要添加"控制同一个编号同时只能登录一次"的功能,那么两个模块都需要修改,是很麻烦的,而且,现在的 实现中,也有很多相似的地方,显得很重复;另外,具体的实现h和判断的步骤混合在一起,不利于今后变换功能,比如要变换加密算法等.
总之,上面的实现,有两个很明显的问题:一是重复或相似代码太多;二是扩展起来很不方便
16.2. 解决方案
16.2.1 使用模板方法模式来解决问题
Q:模板方法模式的定义
A:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
16.2.2 模板方法模式的结构和说明
[AbstractClass] 抽象类.用来定义算法骨架和原语操作,具体的子类通过重定义这些原语操作来实现一个算法的各个步骤.在这个类里面,还可以提供算法中通用的实现
[ConcreteClass] 具体实现类,用来实现算法骨架中的某些步骤,完成与特定子类相关的功能
16.2.3 模板方法模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { } } public abstract class AbstractClass { public abstract void doPrimitiveOperation1(); public abstract void doPrimitiveOperation2(); public void templateMethod() { doPrimitiveOperation1(); doPrimitiveOperation2(); } } public class ConcreteClass : AbstractClass { public override void doPrimitiveOperation1() { } public override void doPrimitiveOperation2() { } } }
16.2.4 使用模板方法模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { LoginModel lm = new LoginModel(); lm.setLoginId("admin"); lm.setPwd("workerpwd"); LoginTemplate lt = new WorkerLogin(); LoginTemplate lt2 = new NormalLogin(); bool flag = lt.login(lm); Console.WriteLine("可以登录工作平台 = " + flag); bool flag2 = lt2.login(lm); Console.WriteLine("可以进行普通人员登录 = " + flag2); } } public class LoginModel { private string loginId; private string pwd; public string getLoginId() { return loginId; } public void setLoginId(string loginId) { this.loginId = loginId; } public string getPwd() { return pwd; } public void setPwd(string pwd) { this.pwd = pwd; } } public abstract class LoginTemplate { public bool login(LoginModel lm) { LoginModel dbLm = this.findLoginUser(lm.getLoginId()); if (dbLm != null) { string encryptPwd = this.encryptPwd(lm.getPwd()); lm.setPwd(encryptPwd); return this.match(lm, dbLm); } return false; } public abstract LoginModel findLoginUser(string loginId); public virtual string encryptPwd(string pwd) { return pwd; } public bool match(LoginModel lm, LoginModel dbLm) { if (lm.getLoginId().Equals(dbLm.getLoginId()) && lm.getPwd().Equals(dbLm.getPwd())) { return true; } return false; } } public class NormalLogin : LoginTemplate { public override LoginModel findLoginUser(string loginId) { LoginModel lm = new LoginModel(); lm.setLoginId(loginId); lm.setPwd("testpwd"); return lm; } } public class WorkerLogin : LoginTemplate { public override LoginModel findLoginUser(string loginId) { LoginModel lm = new LoginModel(); lm.setLoginId(loginId); lm.setPwd("workerpwd"); return lm; } public override string encryptPwd(string pwd) { Console.WriteLine("使用MD5进行密码加密"); return pwd; } } }
16.3 模式讲解
16.3.1 认识模板方法模式
Q:模板方法模式的功能
A:模板方法模式的功能在于固定算法骨架,而让具体算法实现可扩展
这在实际应用中非常广泛,尤其是在设计框架级功能的时候非常有用.框架定义好了算法的步骤,在合适的点让开发人员进行扩展,实现具体的算法
模板方法模式还额外提供了一个好处,就是可以控制子类的扩展.因为在父类中定义好了算法的步骤,只是在某几个固定的点才会调用到被子类实现的方法,因此也就只允许在这几个点来扩展功能.
这些可以被子类覆盖以扩展功能的方法通常被称为"钩子"方法
Q:为何不是接口
A:什么时候使用抽象类
"既要约束子类的行为,又要为子类提供公共功能"的时候使用抽象类
按照这个原则来思考模板方法模式的实现,模板方法模式需要固定定义算法的骨架,这个骨架应该只有一份,算是一个公共的行为,但其中具体的步骤的实现又可能是各不相同的,恰好符合选择抽象类的原则
把模板实现称为抽象类,为所有的子类提供了公共的功能,就是定义了具体的算法骨架;同时在模板中把需要由子类扩展的具体步骤的算法定义成为抽象方法,要求子类去实现这些方法,这就约束了子类的行为
因此综合考虑,用抽象类来实现模板是一个很好的选择
Q:变与不变
A:程序设计的一个很重要的思考点就是"变与不变",也就是分析程序中那些功能是可变的,那些功能是不变的,然后把不变的部分抽象出来,进行公共的实现,把变化的部分分离出去,用接口来封装隔离,或者是用抽象类来约束子类行为.
模板方法模式很好地体现了这一点.模板类实现的就是不变的方法和算法的骨架,而需要变化的地方,都通过抽象方法,把具体实现延迟到子类中了.而且还通过父类的定义来约束了子类的行为,从而使系统能有更好的复用性和扩展性
Q:好莱坞法则
A:什么是好莱坞法则呢?简单点说,就是"不要找我们,我们会联系你"
模板方法模式很好地体现了这一点,作为父类的模板会在需要的时候,调用子类相应的方法,也就是由父类来找子类,而不是让子类来找父类
这其实也是一种反向的控制结构.按照通常的思路,是子类找父类才对,也就是应该是子类来调用父类的方法,因为父类根本就不知道子类,而子类是知道父类的,但是在模板方法模式里面,是父类来找子类,所以是一种反向的控制结构
16.3.2 模板的写法
在实现模板的时候,到底哪些方法实现在模板上呢?模板能不能全部实现了,也就是模板不提供抽象方法呢?当然,就算没有抽象方法,模板一样可以定义成为抽象类
通常在模板里面包含以下操作类型
模板方法:就是定义算法骨架的方法
具体的操作:在模板中直接实现某些步骤的方法.通常这些步骤的实现算法是固定的,而且是不怎么变化的,因此可以将其当作公共功能实现在模板中.如果不需为子类提供访问这些方法的话,还可以是private的.这样一来,子类的实现就相对简单些
具体的AbstractClass操作:在模板中实现某些公共功能,可以提供给子类使用,一般不是具体的算法步骤的实现,而是一些辅助的公共功能.
原语操作:就是在模板中定义的抽象操作,通常是模板方法需要调用的操作,是必须的操作,而且在父类中还没有办法确定下来如何实现,需要子类来真正实现的方法
钩子操作:在模板中定义,并提供默认实现的操作.这些方法通常被视为可扩展的点,但不是必须的,子类可以有选择地覆盖这些方法,以提供新的实现来扩展功能.比如,模板方法中定义了5步操作,但是根据需要,某种具体的实现只需要其中的1,2,3,4几个步骤,因此它就只需要覆盖实现1,2,3这几个步骤对应的方法.那么4和5步骤对应的方法该怎么办呢,由于有默认实现,那就不用管了.也就是说钩子操作是可以被扩展的点,但不是必须的
FactoryMethod:在模板方法中,如果得到某些对象实例的话,可以考虑通过工厂方法模式来获取,把具体的构建对象的实现延迟到子类中去
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { } } public abstract class AbstractTemplate { public void templateMethod() { operation1(); operation2(); doPrimitiveOperation1(); doPrimitiveOperation2(); hookOperation1(); } private void operation1() { } private void operation2() { } protected void commonOperation() { } protected abstract void doPrimitiveOperation1(); protected abstract void doPrimitiveOperation2(); protected void hookOperation1() { } protected abstract object createOneObject(); } }
16.3.3 Java回调与模板方法模式
16.3.4 典型应用:排序
16.3.5 实现通用的增删改查
16.3.6 模板方法模式的优缺点
优点
1.模板方法模式的优点是实现代码复用
模板方法模式是一种实现代码复用的很好的手段.通过把子类的公共功能提炼和抽取,把公共部分放到模板中去实现
缺点
1.模板方法模式的缺点是算法骨架不容易升级
模板方法模式最基本的功能就是通过模板的制定,把算法骨架完全固定下来.事实上模板和子类是非常耦合的,如果要对模板中的算法骨架进行变更,可能就会要求所有相关的子类进行相应的变化.所以抽取算法骨架的时候要特别小心,尽量确保是不会变化的部分才放到模板中
16.3.7 思考模板方法模式
Q:模板方法模式的本质
A:固定算法骨架
模板方法模式主要是通过指定模板,把算法步骤固定下来,至于谁来实现,模板可以自己提供实现,也可以由子类去实现,还可以通过回调机制让其他类来实现
通过固定算法骨架来约束子类的行为,并在特定的扩展点来让子类进行功能扩展,从而让程序既有很好的复用性,又有较好的扩展性
Q:对设计原则的体现
A:模板方法很好地体现了开闭原则和里氏替换原则
首先从设计上分离变与不变,然后把不变的的部分抽取出来,定义到父类中,比如算法骨架,一些公共的,固定的实现等,这些不变的部分被封闭起来,尽量不去修改它们.想要扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放的
其次,能够实现统一的算法骨架,通过切换不同的具体实现来切换不同的功能,一个根本原因就是里氏替换原则,遵循这个原则,保证所有的子类实现的是同一个算法模板,并能在使用模板的地方,根据需要切换不同的具体实现
Q:何时选用模板方法模式
1.需要固定定义算法骨架,实现一个算法的不变的部分,并把可变的行为留给子类来实现的情况
2.各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复
3.需要控制子类扩展的情况.模板方法模式会在特定的点来调用子类的方法,这样只允许在这些点进行扩展
16.3.8 相关模式
1.模板方法模式和工厂方法模式
这两个模式可以配合使用
模板方法模式可以通过工厂方法来获取需要调用的对象
2.模板方法模式和策略模式
这个模式的功能有些相似,但是是有区别的
从表面上看,两个模式都能实现算法的封装,但是模板方法封装的是算法的骨架,这个算法骨架是不变的,变化的是算法中某些步骤的具体实现;而策略模式是把某个步骤的具体实现算法封装起来,所有封装的算法对象是等价的,可以相互替换
因此,可以在模板方法中使用策略模式,就是把那些变化的算法步骤通过使用策略模式来实现,但是具体选取哪些策略还是要由外部来确定,而整体的算法步骤,也就是算法骨架则由模板方法来定义了.
第17章 策略模式(Strategy)
17.1 场景问题
17.1.1 报价管理
17.1.2 不用模式的解决方案
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { } } public class Price { public double quote(double goodsPrice, string customerType) { if ("普通客户".Equals(customerType)) { Console.WriteLine("对于新客户或者是普通客户,没有折扣"); return goodsPrice; } else if ("老客户".Equals(customerType)) { Console.WriteLine("对于老客户,统一折扣5%"); return goodsPrice * (1 - 0.05); } else if ("大客户".Equals(customerType)) { Console.WriteLine("对于大客户,统一折扣10%"); return goodsPrice * (1 - 0.1); } return goodsPrice; } } }
17.1.3 有何问题
Q:有何问题
A: 1.价格类包含了所有计算报价的算法,使得价格类,尤其是报价这个方法比较庞杂,难以维护
2.经常会有这样的需要,在不同的时候,要使用不同的计算方式
17.2 解决方案
17.2.1 使用策略模式来解决问题
Q:策略模式的定义
A:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换.本模式使得算法可独立于使用它的客户而变化
Q:应用策略模式来解决问题的思路
A:仔细分析上面的问题,先来把它抽象一下,各种计算报价的计算方式就好比是具体的算法,而使用这些计算方式来计算报价的程序,就相当于是使用算法的客户
再分析上面的实现方式,为什么会造成那些问题?根本原因就在于算法和使用算法的客户是耦合的,甚至是密不可分的.在上面的实现中,具体的算法和使用算法的客户是同一个类中的不同方法.
现在来解决那些问题.按照策略模式的方式,应该先把所有的计算方式独立出来,每个计算方式做成一个单独的算法类,从而形成一系列的算法,并且为这一系列算法定义一个公共的接口,这些算法实现是同一接口的不同实现,地位是平等的,可以相互替换.这样一来,要扩展新的算法就变成了增加一个新的算法实现类,要维护某个算法,也只是修改某个具体的算法实现即可,不会对其他代码造成影响.也就是说这样就解决了可维护,可扩展的问题
为了实现让算法能独立于使用他的客户,策略模式引入了一个上下文的对象,这个对象负责持有算法,但是不负责决定具体选用哪个算法,把选择算法的功能交给了客户,由客户选择好具体的算法后,设置到上下文对象中,让上下文对象持有客户选择的算法,当客户通知上下文对象执行功能的时候,上下文对象则转调具体的算法.这样一来,具体的算法和直接使用算法的客户是分离的
具体的算法和使用他的客户分离以后,使得算法可以独立于使用它的客户而变化,并且能够动态地切换需要使用的算法,只要客户端动态地选择使用不同的算法,然后设置到上下文对象中去,在实际调用的时候,就可以调用到不同的算法
17.2.2 策略模式的结构和说明
[Strategy] 策略接口,用来约束一系列具体的策略算法.Context使用这个接口来调用具体的策略实现定义的算法
[ConcreteStrategy] 具体的策略实现,也就是具体的算法实现
[Context] 上下文,负责和具体的策略类交互.通常上下文会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法
17.2.3 策略模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { } } public interface Strategy { void algorithmInterface(); } public class ConcreteStrategyA : Strategy { public void algorithmInterface() { } } public class ConcreteStrategyB : Strategy { public void algorithmInterface() { } } public class ConcreteStrategyC : Strategy { public void algorithmInterface() { } } public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void contextInterface() { strategy.algorithmInterface(); } } }
17.2.4 使用策略模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { Strategy strategy = new LargeCustomerStrategy(); Price ctx = new Price(strategy); double quote = ctx.quote(1000); Console.WriteLine("向客户报价: " + quote); } } public interface Strategy { double calcPrice(double goodsPrice); } public class NormalCustomerStrategy : Strategy { public double calcPrice(double goodsPrice) { Console.WriteLine("对于新客户或者是普通客户,没有折扣"); return goodsPrice; } } public class OldCustomerStrategy : Strategy { public double calcPrice(double goodsPrice) { Console.WriteLine("对于老客户,统一折扣5%"); return goodsPrice * (1 - 0.05); } } public class LargeCustomerStrategy : Strategy { public double calcPrice(double goodsPrice) { Console.WriteLine("对于大客户,统一折扣10%"); return goodsPrice * (1 - 0.1); } } public class Price { private Strategy strategy; public Price(Strategy strategy) { this.strategy = strategy; } public double quote(double goodsPrice) { return strategy.calcPrice(goodsPrice); } } }
17.3 模式讲解
17.3.1 认识策略模式
Q:策略模式的功能
A:策略模式的功能是把具体的算法实现从具体的业务处理中独立出来,把它们实现成为单独的算法类,从而形成一系列的算法,并让这些算法可以相互替换
策略模式的重心不是如何来实现算法,而是如何组织,调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性
Q:策略模式和if-else语句
A:看了前面的示例,很多朋友会发现,每个策略算法具体实现的功能,就是原来在if-else结构中的具体实现
没错,其实多个if-elseif语句表达的就是一个平等的功能结构,你要么执行if,要么执行else,或者是elseif,这个时候,if块中的实现和else块中的实现从运行地位上来讲是平等的
而策略模式就是把各个平等的具体实现封装到单独的策略实现类了,然后通过上下文来与具体的策略类进行交互
因此多个if-else语句可以考虑使用策略模式
Q:算法的平等性
A:策略模式一个很大的特点j就是各个策略算法的平等性.对于一系列具体的策略算法,大家的地位是完全一样的,正是因为这个平等性,才能实现算法之间可以相互依赖
所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的
所以可以这样描述这一系列策略算法;策略算法是相同行为的不同实现
Q:谁来选择具体的策略算法
A:在策略模式中,可以在两个地方来进行具体策略的选择
一个是在客户端,当使用上下文的时候,由客户端来选择具体的策略算法,然后把这个策略算法设置给上下文
还有一个是客户端不管,由上下文来选择具体的策略算法
Q:Strategy的实现方式
A:在前面的示例中,Strategy都是使用接口来定义的,这也是常见的实现方式.但是如果多个算法具有公共功能的话,可以把Strategy实现成抽象类,然后把多个算法的公共功能实现到Strategy中
Q:运行时策略的唯一性
A:运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同策略实现中切换,但是同时只能使用一个
Q:增加新的策略
A:策略模式可以让你很灵活地扩展新的算法.具体的做法是,先写一个策略算法类来实现新的要求,然后在客户端使用的时候指定使用新的策略算法类就可以了
17.3.2 Context和Strategy的关系
17.3.3 容错恢复机制
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { LogContext log = new LogContext(); log.log("记录日志"); log.log("再次记录日志"); } } public interface LogStrategy { void log(string msg); } public class DbLog : LogStrategy { public void log(string msg) { if (msg != null && msg.Trim().Length > 5) { int a = 5 / 0; } Console.WriteLine("现在把 '" + msg + "' 记录到数据库中"); } } public class FileLog : LogStrategy { public void log(string msg) { Console.WriteLine("现在把 '" + msg + "' 记录到文件中"); } } public class LogContext { public void log(string msg) { LogStrategy strategy = new DbLog(); try { strategy.log(msg); } catch (Exception err) { strategy = new FileLog(); strategy.log(msg); } } } }
17.3.4 策略模式结合模板方法模式
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { class Program { static void Main(string[] args) { LogContext log = new LogContext(); log.log("记录日志"); log.log("再次记录日志"); } } public interface LogStrategy { void log(string msg); } public abstract class LogStrategyTemplate : LogStrategy { public void log(string msg) { msg = DateTime.Now.ToString() + " 内容是: " + msg; doLog(msg); } public abstract void doLog(string msg); } public class DbLog : LogStrategyTemplate { public override void doLog(string msg) { if (msg != null && msg.Trim().Length > 5) { int a = 5 / 0; } Console.WriteLine("现在把 '" + msg + "' 记录到数据库中"); } } public class FileLog : LogStrategyTemplate { public override void doLog(string msg) { Console.WriteLine("现在把 '" + msg + "' 记录到文件中"); } } public class LogContext { public void log(string msg) { LogStrategy strategy = new DbLog(); try { strategy.log(msg); } catch (Exception err) { strategy = new FileLog(); strategy.log(msg); } } } }
17.3.5 策略模式的优缺点
优点
1.定义一系列算法
策略模式的功能j就是定义一系列算法,实现让这些算法可以相互替换.所以会为这一系列算法定义公共接口,以约束一系列算法要实现的功能.如果这一系列算法具有公共功能,可以把策略接口实现成为抽象类,把这些公共功能实现到父类中
2.避免多重条件语句
3.更好的扩展性
缺点
1.客户必须了解每种策略的不同
2.增加了对象数目
3.策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法,就相当于兄弟算法.而且在运行时刻只有一个算法被使用,这就限制了算法使用的层级,使用的时候不能嵌套使用
对于出现需要嵌套使用多个算法的情况,比如折上折,折后返卷等业务的实现,需要组合或者是嵌套使用多个算法的情况,可以考虑使用装饰模式,或是变形的职责链,或是AOP等方式来实现
17.3.6 思考策略模式
Q:策略模式的本质
A:分离算法,选择实现
仔细思考策略模式的结构和实现的功能,会发现,如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程的,那么就能够享受到接口的封装隔离带来的好处.也就是通过一个统一的策略接口来封装和隔离具体的策略算法,面向接口编程的话,自然不需要关系具体的策略实现,也可以通过使用不同的实现类来实例化接口,从而实现切换具体的策略
看起来好像没有上下文什么事情,但是如果没有上下文,那么就需要客户端来直接与具体的策略交互,尤其是当需要提供一些公共功能,或者是相关状态存储的时候,会大大增加客户端使用的难度.因此,引入上下文还是很有必要的,有了上下文,这些工作就由上下文来完成了,客户端只需要与上下文交互就可以了,这样会让整个设计模式更独立,更有整体性,也让客户端更简单
但纵观整个策略模式实现的功能和设计,它的本质还是"分离算法,选择实现",因为分离并封装了算法,才能够很容易地修改和添加算法;也能很容易地动态切换使用不同的算法,也就是动态选择一个算法来实现需要的功能
Q:对设计原则的体现
从设计原则上来看,策略模式很好地体现了开-闭原则.策略模式通过把一系列可变的算法进行封装,并定义出合理的使用结构,使得在系统出现新算法的时候,能很容易地把新的算法加入到已有的系统中,而已有的实现不需要做任何修改.
从设计原则上来看,策略模式还很好地体现了里氏替换原则.策略模式是一个扁平结构,一系列的实现算法其实是兄弟关系,都是实现同一个接口或者继承的同一个父类.这样只要使用策略的客户保持面向对象抽象类型编程,就能够使用不同策略的具体实现对象来配置它,从而实现一系列算法可以相互代替
Q:何时选用策略模式
A: 1.出现有许多相关的类,仅仅是行为有差别的前提下,可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法动态切换
2.出现同一个算法,有很多不同实现的情况下,可以使用策略模式来把这些"不同的实现"实现成为一个算法的类层次
3.需要封装算法中,有与算法相关数据的情况下,可以使用策略模式来避免暴露这些跟算法相关的数据结构
4.出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来选择这些行为的情况下,可以使用策略模式来代替这些条件语句
17.3.7 相关模式
1.策略模式和状态模式
这两个模式从模式结构上看是一样的,但是实现的功能却是不一样的
状态模式是根据状态的变化来选择相应的行为,不同的状态对应不同的类,每个状态对应的类实现了该状态对应的功能,在实现功能的同时,还会维护状态数据的变化.这些实现状态对应的功能的类之间是不能相互替换的.策略模式是根据需要或者是客户端的要求来选择相应的实现类,各个实现类是平等的,是可以相互替换的.另外策略模式可以让客户端来选择需要使用的策略算法;而状态模式一般是由上下文,或者是在状态实现类里面来维护具体的状态数据,通常不由客户端来指定状态
2.策略模式和模板方法模式
这两个模式可组合使用
模板方法重在封装算法骨架;而策略模式重在分离并封装算法实现
3.策略模式和享元模式
这两个模式可组合使用
策略模式分离并封装出一系列的策略算法对象,这些对象的功能通常都比较单一,很多时候就是为了实现某个算法的功能而存在.因此,针对这一系列的,多个细粒度的对象,可以应用享元模式来节省资源,但前提是这些算法对象要被频繁地使用,如果偶尔用一次,就没有必要做成享元了
第18章 状态模式(State)
18.1 场景问题
18.1.1 实现在线投票
18.1.2 不用模式的解决方案
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { VoteManager vm = new VoteManager(); for (int i = 0; i < 8; i++) { vm.vote("u1", "A"); } } } public class VoteManager { private Dictionary<string,string> mapVote = new Dictionary<string, string>(); private Dictionary<string,int> mapVoteCount = new Dictionary<string, int>(); public void vote(string user, string voteItem) { int oldVoteCount = 0; if (!mapVoteCount.TryGetValue(user, out oldVoteCount)) { oldVoteCount = 0; } oldVoteCount = oldVoteCount + 1; mapVoteCount[user] = oldVoteCount; if (oldVoteCount == 1) { mapVote[user] = voteItem; Console.WriteLine("恭喜你投票成功"); } else if (oldVoteCount > 1 && oldVoteCount < 5) { Console.WriteLine("请不要重复投票"); } else if (oldVoteCount >= 5 && oldVoteCount < 8) { string s = ""; if (!mapVote.TryGetValue(user,out s)) { mapVote.Remove(user); } Console.WriteLine("你有恶意刷票行为,取消投票资格"); } else if (oldVoteCount >= 8) { Console.WriteLine("进入黑名单,将禁止登录和使用本系统"); } } } }
18.1.3 有何问题
Q:有何问题
A:看起来很简单,是不是?辛亏这里只是示意,否则,你想想,在vote()方法中那么多判断,还有每个判断对应的功能处理都放在一起,是不是有点太杂乱了,那简直就是个大杂烩,如果把每个功能都完整地实现出来,那vote()方法会很长的
一个问题是,如果现在要修改某种投票情况所对应的具体功能处理,那就需要在那个大杂烩中,找到相应的代码块,然后进行改动
另外一个问题是,如果要添加新的功能,比如投票超过8次但不足10次的,给个机会,只是禁止登录和使用系统3天,如果再犯,才永久封掉账号,该怎么办呢?那就需要改动投票管理的源代码,在上面的if-else结构中再添加一个else if块进行处理
不管哪一种情况,都是在一大堆的控制代码中找出需要的部分,然后进行修改,这不是个好方法.那么该如何实现才能做到:既能够很容易地给vote()方法添加新的功能,又能够很方便地修改已有的功能处理呢?
18.2 解决方案
18.2.1 使用状态模式来解决问题
Q:状态模式的定义
A:允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类
18.2.2 状态模式的结构和说明
[Context] 环境,也称上下文,通常用来定义客户感兴趣的接口,同时维护一个来具体处理当前状态的实例对象
[State] 状态接口,用来封装与上下文的一个特定状态所对应的行为
[ConcreteState] 具体实现状态处理的类,每个类实现一个跟上下文相关的状态的具体处理
18.2.3 状态模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { } } public interface State { void handle(string sampleParameter); } public class ConcreteStateA : State { public void handle(string sampleParameter) { } } public class ConcreteStateB : State { public void handle(string sampleParameter) { } } public class Context { private State state; public void setState(State state) { this.state = state; } public void request(string sampleParameter) { state.handle(sampleParameter); } } }
18.2.4 使用状态模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { VoteManager vm = new VoteManager(); for (int i = 0; i < 8; i++) { vm.vote("u1","A"); } } } public interface VoteState { void vote(string user, string voteItem, VoteManager voteManager); } public class VoteManager { private VoteState state = null; private Dictionary<string,string> mapVote = new Dictionary<string, string>(); private Dictionary<string,int> mapVoteCount = new Dictionary<string, int>(); public Dictionary<string, string> getMapVote() { return mapVote; } public void vote(string user, string voteItem) { int oldVoteCount = 0; if (!mapVoteCount.TryGetValue(user,out oldVoteCount)) { oldVoteCount = 0; } oldVoteCount = oldVoteCount + 1; mapVoteCount[user] = oldVoteCount; if (oldVoteCount == 1) { state = new NormalVoteState(); } else if (oldVoteCount > 1 && oldVoteCount < 5) { state = new RepeatVoteState(); } else if (oldVoteCount >= 5 && oldVoteCount < 8) { state = new SpiteVoteState(); } else if (oldVoteCount >= 8) { state = new BlackVoteState(); } state.vote(user, voteItem, this); } } public class NormalVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { voteManager.getMapVote()[user] = voteItem; Console.WriteLine("恭喜你投票成功"); } } public class RepeatVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { Console.WriteLine("请不要重复投票"); } } public class SpiteVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { string s = null; if (voteManager.getMapVote().TryGetValue(user,out s)) { voteManager.getMapVote().Remove(user); } Console.WriteLine("你有恶意刷票行为,取消投票资格"); } } public class BlackVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { Console.WriteLine("进入黑名单,将禁止登录和使用本系统"); } } }
18.3 模式讲解
18.3.1 认识状态模式
Q:状态和行为
A:所谓对象的状态,通常指的就是对象实例的属性的值;而行为大多可以对应到方法上.
状态模式的功能就是分离状态的行为,通过维护状态的变化,来调用不同状态对应的不同功能
也就是说,状态和行为是相关联的,它们的关系可以描述为:状态决定行为
由于状态是在运行期被改变的,因此行为也会在运行期根据状态的改变而改变,看起来,同一个对象,在不同的运行时刻,行为是不一样的,就像是类被修改了一样
Q:行为的平行性
A:注意是平行性而不是平等性,所谓平行性指的是各个状态的行为为所处的层次是一样的,相互是独立的,没有关联的,是根据不同的状态来决定到底走平行线的哪一条.行为是不同的,当然对应的实现也是不同的,相互之间是不可替换的
而平等性强调的是可替换性,大家是同一行为的不同描述或实现,因此在同一个行为发生的时候,可以根据条件挑选任意一个实现来进行相应的处理
大家可能会发现状态模式的结构和策略模式的结构完全一样,但是它们的目的,实现,本质却是完全不一样的.还有行为之间的特性也是状态模式和策略模式一个很重要的区别,状态模式的行为是平行性的,不可相互替换的;而策略模式的行为是平等性的,是可以相互替换的
Q:上下文和状态处理对象
A:在状态模式中,上下文是持有状态的对象,但是上下文自身并不处理跟状态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理
在具体的状态处理类中经常需要获取上下文自身的数据,甚至在必要的时候会回调上下文的方法,因此,通常将上下文自身当作一个参数传递给具体的状态处理类
客户端一般只和上下文交互.客户端可以用状态对象来配置一个上下文,一旦配置完毕,就不再需要和状态对象打交道了.客户端通常不负责运行期间状态的维护,也不负责决定后续到底使用哪一个具体的状态处理对象
18.3.2 状态的维护和转换控制
所谓状态的维护,指的是维护状态的数据,给状态设置不同的状态值;而状态的转换,指的是根据状态的变化来选择不同的状态处理对象.在状态模式下,通常有两个地方可以进行状态的维护和转换控制
一个就是在上下文中.因为状态本身通常被实现为上下文对象的状态,因此可以在上下文中进行状态维护,当然也就可以控制状态的转换了.前面投票的示例就是采用这种方式
另外一个地方就是在状态的处理类中,当每个状态处理对象处理完自身状态所对应的功能后,可以根据需要指定后继状态,以便让应用能正确处理后续的请求
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { VoteManager vm = new VoteManager(); for (int i = 0; i < 8; i++) { vm.vote("u1","A"); } } } public interface VoteState { void vote(string user, string voteItem, VoteManager voteManager); } public class VoteManager { private VoteState state = null; private Dictionary<string,VoteState> mapState = new Dictionary<string, VoteState>(); private Dictionary<string,string> mapVote = new Dictionary<string, string>(); private Dictionary<string,int> mapVoteCount = new Dictionary<string, int>(); public Dictionary<string, VoteState> getMapState() { return mapState; } public Dictionary<string, string> getMapVote() { return mapVote; } public Dictionary<string, int> getMapVoteCount() { return mapVoteCount; } public void vote(string user, string voteItem) { int oldVoteCount = 0; if (!mapVoteCount.TryGetValue(user,out oldVoteCount)) { oldVoteCount = 0; } oldVoteCount = oldVoteCount + 1; mapVoteCount[user] = oldVoteCount; VoteState state; if (!mapState.TryGetValue(user, out state)) { state= new NormalVoteState(); } state.vote(user, voteItem, this); } } public class NormalVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { voteManager.getMapVote()[user] = voteItem; Console.WriteLine("恭喜你投票成功"); voteManager.getMapState()[user] = new RepeatVoteState(); } } public class RepeatVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { Console.WriteLine("请不要重复投票"); if (voteManager.getMapVoteCount()[user] >= 4) { voteManager.getMapState()[user] = new SpiteVoteState(); } } } public class SpiteVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { string s = null; if (voteManager.getMapVote().TryGetValue(user,out s)) { voteManager.getMapVote().Remove(user); } Console.WriteLine("你有恶意刷票行为,取消投票资格"); if (voteManager.getMapVoteCount()[user] >= 7) { voteManager.getMapState()[user] = new BlackVoteState(); } } } public class BlackVoteState : VoteState { public void vote(string user, string voteItem, VoteManager voteManager) { Console.WriteLine("进入黑名单,将禁止登录和使用本系统"); } } }
那么到底如何选择这两种方式呢?
1.如果状态转换的规则是一定的,一般不需要进行什么扩展规则,那么就适合在上下文中统一进行状态的维护
2.如果状态的转换取决于前一个状态动态处理的结果,或者是依赖于外部数据,为了增强灵活性,这种情况下,一般是在状态处理类中进行状态的维护
18.3.3 使用数据库来维护状态
18.3.4 模拟工作流
18.3.5 状态模式的优缺点
优点
1.简化应用逻辑控制
2.更好地分离状态和行为
3.更好的扩展性
4.显式化进行状态转换
缺点
1.状态模式也有一个很明显的缺点,一个状态对应一个状态处理类,会使得程序引入太多的状态类,这样程序变得杂乱
18.3.6 思考状态模式
Q:状态模式的本质
A:根据状态来分离和选择行为
仔细分析状态模式的结构,如果没有上下文,那么就退化回到只有接口和实现了,正式通过接口,把状态和状态对应的行为分开,才使得通过状态模式设计的程序易于扩展和维护
而上下文主要负责的是公共的状态驱动,每当状态发生改变的时候,通常都是回调上下文来执行状态对应的功能.当然,上下文自身也可以维护状态的变化,另外,上下文通常还会作为多个状态处理类之间的数据载体,在多个状态处理类之间传递数据
Q:何时选用状态模式
A: 1.如果一个对象的行为取决于它的状态,而且它必须在运行时刻根据状态来改变它的行为,可以使用状态模式,来把状态和行为分离开,虽然分离开了,但状态和行为是有对应关系的,可以在运行期间,通过改变状态,就能够调用到该状态对应的状态处理对象上去,从而改变对象的行为
2.如果一个操作中含有庞大的多分支语句,而且这些分支依赖于该对象的状态,可以使用状态模式,把各个分支的处理分散包装到单独的对象处理类中,这样,这些分支对应的对象就可以不依赖于其他对象而独立变化了
18.3.7 相关模式
1.状态模式和策略模式
2.状态模式和观察者模式
这两个模式乍一看,功能是很相似的,但是又有区别,可以组合使用
这两个模式都是在状态发生改变的时候触发行为,只不过观察者模式的行为是固定的,那就是通知所有的观察者,而状态模式是根据状态来选择不同的处理.从表面来看,两个模式功能相似,观察者模式中的被观察对象就好比状态模式中的上下文,观察者模式中当被观察对象的状态发生改变的时候,触发的通知所有观察者的方法就好比是状态模式中,根据状态的变化选择对应的状态处理
但实际这两个模式是不同的,观察则者模式的目的是在被观察者的状态发生改变的时候,触发观察者联动,具体如何处理观察者模式不管;而状态模式的主要目的在于根据状态来分离和选择行为,当状态发生改变的时候,动态地改变行为.
这两个模式是可以组合使用的,比如在观察者模式的观察部分,当被观察对象的状态发生了改变,触发通知了所有的观察者以后,观察者该怎么处理呢?这个时候就可以使用状态模式,根据通知过来的状态选择相应的处理
3.状态模式和单例模式
这两个模式可以组合使用,可以把状态模式中的状态处理类实现成单例
4.状态模式和享元模式
由于状态模式把状态对应的行为分散到多个状态对象中,会造成很多细粒度的状态对象,可以把这些状态处理对象通过享元模式来共享,从而节省资源
第19章 备忘录模式(Memento)
19.1 场景问题
19.1.1 开发仿真系统
19.1.2 不用模式的解决方案
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { FlowAMock mock = new FlowAMock("TestFlow"); mock.runPhaseOne(); int tempResult = mock.getTempResult(); string tempState = mock.getTempState(); mock.schema1(); mock.setTempResult(tempResult); mock.setTempState(tempState); mock.schema2(); } } public class FlowAMock { private string flowName; private int tempResult; private string tempState; public FlowAMock(string flowName) { this.flowName = flowName; } public string getTempState() { return tempState; } public void setTempState(string tempState) { this.tempState = tempState; } public int getTempResult() { return tempResult; } public void setTempResult(int tempResult) { this.tempResult = tempResult; } public void runPhaseOne() { tempResult = 3; tempState = "PhaseOne"; } public void schema1() { tempState += ",Schema1"; Console.WriteLine(tempState + " : now run " + tempResult); tempResult += 11; } public void schema2() { tempState += ",Schema2"; Console.WriteLine(tempState + " : now run " + tempResult); tempResult += 22; } } }
19.1.3 有何问题
Q:有何问题
A:上面的实现有一个不太理想的地方,那就是数据是一个一个零散着在外部存放的,如果需要外部存放的数据多了,会显得很杂乱.这个容易解决,只需要定义一个数据对象来封装这些需要外部存放的数据就可以了
还有一个严重的问题,那就是,为了把运行期间的数据放到外部存储起来,模拟流程的对象被迫把内部数据结构开放出来,这暴露了对象的实现细节,而且也破坏了对象的封装性.本来这些数据只是模拟流程的对象内部数据,应该是不对外的
19.2 解决方案
19.2.1 使用备忘录模式来解决问题
Q:备忘录模式的定义
A:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态
一个备忘录是一个对象,它存储另一个对象在某个瞬间的内部状态,后者被称为备忘录的原发器
备忘录模式引入一个存储状态的备忘录对象,为了让外部无法访问这个对象的值,一般把这个对象实现成为需要保存数据的对象的内部类,通常还是私有的,这样一来,除了这个需要保存数据的对象,外部无法访问到这个备忘录对象的数据,这就保证了对象的封装性不被破坏
但是这个备忘录对象需要存储在外部.为了避免让外部访问到这个对象内部的数据,备忘录模式引入了一个备忘录对象的窄接口,这个接口一般是空的,什么方法都没有,这样外部存储的地方,只是知道存储了一些备忘录接口的对象,但是由于接口是空的,它们无法通过接口去访问备忘录对象内部的数据
19.2.2 备忘录模式的结构和说明
[Memento] 备忘录.主要用来存储原发器对象的内部状态,但是具体需要存储哪些数据是由原发器对象来决定的.另外备忘录应该只能由原发器对象来访问它内部的数据,原发器外部的对象不应该访问到备忘录对象的内部数据
[Originator] 原发器,使用备忘录来保存某个时刻原发器自身的状态,也可以使用备忘录来恢复内部状态
[Caretaker] 备忘录管理者,或者称为备忘录负责人.主要负责保存备忘录对象,但是不能对备忘录对象的内容进行操作或检查
19.2.3 备忘录模式示例代码
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { } } public interface Memento { } // 原发器 public class Originator { private string state = ""; public Memento createMemento() { return new MementoImpl(state); } public void setMemento(Memento memento) { MementoImpl mementoImpl = (MementoImpl) memento; state = mementoImpl.getState(); } private class MementoImpl : Memento { private string state = ""; public MementoImpl(string state) { this.state = state; } public string getState() { return state; } } } // 负责保存备忘录 public class Caretaker { private Memento memento = null; public void saveMemento(Memento memento) { this.memento = memento; } public Memento retriveMemento() { return this.memento; } } }
19.2.4 使用备忘录模式重写示例
using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace ConsoleApplication1 { public class Program { public static void Main() { FlowAMock mock = new FlowAMock("TestFlow"); mock.runPhaseOne(); FlowAMementoCareTaker careTaker = new FlowAMementoCareTaker(); FlowAMockMemento memento = mock.createMemento(); careTaker.saveMemento(memento); mock.schema1(); mock.setMemento(careTaker.retriveMemento()); mock.schema2(); } } public interface FlowAMockMemento { } public class FlowAMock { private string flowName; private int tempResult; private string tempState; public FlowAMock(string flowName) { this.flowName = flowName; } // 阶段一 public void runPhaseOne() { tempResult = 3; tempState = "PhaseOne"; } // 阶段一后的方案一 public void schema1() { tempState += ",Schema1"; Console.WriteLine(tempState + " : now run " + tempResult); tempResult += 11; } // 阶段一后的方案二 public void schema2() { tempState += ",Schema2"; Console.WriteLine(tempState + " : now run " + tempResult); tempResult += 22; } public FlowAMockMemento createMemento() { return new MementoImpl(tempResult,tempState); } public void setMemento(FlowAMockMemento memento) { MementoImpl mementoImpl = (MementoImpl) memento; tempResult = mementoImpl.getTempResult(); tempState = mementoImpl.getTempState(); } // 真正的备忘录对象, private class MementoImpl : FlowAMockMemento { private int tempResult; private string tempState; public MementoImpl(int tempResult,string tempState) { this.tempResult = tempResult; this.tempState = tempState; } public int getTempResult() { return tempResult; } public string getTempState() { return tempState; } } } public class FlowAMementoCareTaker { private FlowAMockMemento memento; public void saveMemento(FlowAMockMemento memento) { this.memento = memento; } public FlowAMockMemento retriveMemento() { return this.memento; } } }
19.3 模式讲解
19.3.1 认识备忘录模式
Q:备忘录模式的功能
A:备忘录模式的功能,首先是在不破坏封装性的前提下,捕获一个对象的内部状态.这里要注意两点,一个是不破坏封装性,也就是对象不能暴露它不应该暴露的细节;另外一个是捕获的是对象的内部状态,而且通常还是运行期间某个时刻对象的内部状态
为什么要捕获这个对象的内部状态呢?捕获这个内部状态有什么用呢?
是为了在以后的某个时候,将该对象的状态恢复到备忘录所保存的状态,这才是备忘录真正的目的.前面保存状态就是为了后面恢复,虽然不是一定要恢复,但是目的是为了恢复,这也是很多人理解备忘录模式的时候,忽视掉的地方,它们太关注备忘,而忽视了恢复,这是不全面的理解
捕获的状态存放在哪里
备忘录模式中,捕获的内部状态存储在备忘录对象中,而备忘录对象通常会被存储在原发器对象之外,也就是被保存状态的对象的外部,通常是存放在管理者对象那里
备忘录对象
在备忘录模式中,备忘录对象通常就是用来记录原发器需要保存的状态的对象,简单点的实现,也就是封装数据的对象
但是备忘录对象和普通的封装数据的对象还是有区别的,主要是备忘录对象一般只让原发器对象来操作,而不是像普通的封装数据的对象那样,谁都可以使用.为了保证这一点,通常会把备忘录对象作为原发器对象的内部类来实现,而且实现成私有的,这就断绝了外部来访问这个备忘录对象的途径
备忘录对象需要保存在原发器对象之外,为了与外部交互,通常备忘录对象都会实现一个窄接口,来标识对象的类型.
原发器对象
原发器对象就是需要被保存状态的对象,也是有可能需要恢复状态的对象,原发器一般会包含备忘录对象的实现
通常原发器对象应该提供捕获某个时刻对象内部状态的方法,在这个方法中,原发器对象会创建备忘录对象,把需要保存的状态数据设置到备忘录对象中,然后把备忘录对象提供给管理者对象来保存
当然,原发器对象也应该提供这样的方法:按照外部要求来恢复内部状态到某个备忘录对象记录的状态
管理者对象
在备忘录模式中,管理者对象主要是负责保存备忘录对象
1.并不一定要特别的做出一个管理者对象来,广义地说,调用原发器获得备忘录对象后,备忘录对象放在哪里,哪个对象就可以算是管理者对象
2.管理者对象并不是只能管理一个备忘录对象,一个管理者对象可以管理很多的备忘录对象,
3.狭义的管理者对象只是管理同一类的备忘录对象,但广义的管理者对象是可以管理不同类型的备忘录对象的
4.管理者对象需要实现的基本功能主要是:存入备忘录对象,保存备忘录对象和获取备忘录对象.如果从功能上看,就是一个缓存功能的实现,或者是一个简单的对象实例池的实现
5.管理者虽然能存取备忘录对象,但是不能访问备忘录对象内部的数据
窄接口和宽接口
1.窄接口:管理者只能看到备忘录的窄接口,窄接口的实现中通常没有任何的方法,只是一个类型标识,窄接口使得管理者只能将备忘录传递给其他对象
2.宽接口:原发器能够看到一个宽接口,允许它访问所需的所有数据,来返回到先前的状态.理想情况是:只允许生成备忘录的原发器来访问该备忘录的内部状态,通常实现成为原发器内的一个私有内部类
19.3.2 结合原型模式
19.3.3 离线存储
19.3.4 再次实现可撤销操作
19.3.5 备忘录模式的优缺点
优点
1.更好的封装性
2.简化了原发器
3.窄接口和宽接口
缺点
1.可能会导致高开销
19.3.6 思考备忘录模式
Q:备忘录模式的本质
A:保存和恢复内部状态
根据备忘录模式的本质,从广义上讲,进行数据库存取操作;或者是Web应用中的request,session,servletContext等的attribugte数据存取;更进一步,大多数基于缓存功能的数据操作都可以视为广义的备忘录模式.不过广义到这个地步,还提备忘录模式已经没有什么意义了.所以对于备忘录模式还是多从狭义上来说
Q:何时选用备忘录模式
A: 1.如果必须保存一个对象在某一个时刻的全部或者部分状态,方便在以后需要的时候,可以把该对象恢复到先前的状态,可以使用备忘录模式.使用备忘录对象来封装和保存需要保存的内部状态,然后把备忘录对象保存到管理者对象中,在需要的时候,再从管理者对象中获取备忘录对象,来恢复对象的状态
2.如果需要保存一个对象的内部状态,但是如果用接口来让其他对象直接得到这些需要保存的状态,将会暴露对象的实现细节并破坏对象的封装性,这时可以使用备忘录模式,把备忘录对象实现成为原发器对象的内部类,而且还是私有的,从而保证只有原发器对象才能访问该备忘录对象.这样既保存了需要保存的状态,又不会暴露原发器对象的内部实现细节
19.3.7 相关模式
1.备忘录模式和命令模式
这两个模式可以组合使用
命令模式实现中,在实现命令的撤销和重做的时候,可以使用备忘录模式,在命令操作的时候记录下操作前后的状态,然后在命令撤销和重做的时候,直接使用相应的备忘录对象来恢复状态就可以了
在这种撤销的执行顺序和重做的执行顺序可控的情况下,备忘录对象还可以采用增量式记录的方式,有效减少缓存的数据量
2.备忘录模式和原型模式
这两个模式可以组合使用
在原发器对象创建备忘录对象的时候,如果原发器对象中全部或者大部分的状态都需要保存,一个简洁的方式就是直接克隆一个原发器对象.也就是说,这个时候备忘录对象里面存放的是一个原发器对象的实例
第20章 享元模式(Flyweight)
20.1 场景问题
20.1.1 加入权限控制
几乎所有的权限系统都分成两个部分,一个是授权部分,一个是验证部分,为了理解它们,首先解释两个基本的名词
1.安全实体:就是被权限系统检测的对象,比如工资数据
2.权限:就是需要被校验的权限对象,比如查看,修改等
安全实体和权限通常要一起描述才有意义,比如有这样一个描述,"现在要检测登录人员对工资数据是否有查看的权限","工资数据"就是安全实体,"查看"就是权限
1.授权:是指把对某些安全实体的某些权限分配给某些人员的过程
2.验证:是指判断某个人员对某个安全实体是否拥有某个或某些权限的过程
权限的另外两个特征
1.权限的继承性:如果多个安全实体存在包含关系,而某个安全实体没有相应的权限限制,那么它会继承包含它的安全实体的相应权限
2.权限的最近匹配原则:如果多个安全实体存在包含关系,而某个安全实体没有相应的权限限制,那么它会向上寻找并匹配相应的权限限制,直到找到一个离这个安全实体最近的拥有相应权限限制的安全实体为止.如果把整个层次结构都寻找完了仍没有匹配到相应权限限制的话,那就说明所有人对这个安全实体都拥有这个相应的权限限制.
20.1.2 不使用模式的解决方案
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Threading; namespace ConsoleApplication1 { public class Program { public static void Main() { SecurityMgr mgr = SecurityMgr.getInstance(); mgr.login("张三"); mgr.login("李四"); bool f1 = mgr.hasPermit("张三", "薪资数据", "查看"); bool f2 = mgr.hasPermit("李四", "薪资数据", "查看"); Console.WriteLine("f1 == " + f1); Console.WriteLine("f2 == " + f2); for (int i = 0; i < 3; i++) { mgr.login("张三" + i); mgr.hasPermit("张三" + i, "薪资数据", "查看"); } } } public class TestDB { public static List<string> colDB = new List<string>(); static TestDB() { colDB.Add("张三,人员列表,查看"); colDB.Add("李四,人员列表,查看"); colDB.Add("李四,薪资数据,查看"); colDB.Add("李四,薪资数据,修改"); for (int i = 0; i < 3; i++) { colDB.Add("张三" + i + ",人员列表,查看"); } } } public class AuthorizationModel { private string user; private string securityEntity; private string permit; public string getUser() { return user; } public void setUser(string user) { this.user = user; } public string getSecurityEntity() { return securityEntity; } public void setSecurityEntity(string securityEntity) { this.securityEntity = securityEntity; } public string getPermit() { return permit; } public void setPermit(string permit) { this.permit = permit; } } public class SecurityMgr { private static SecurityMgr securityMgr = new SecurityMgr(); private Dictionary<string,List<AuthorizationModel>> map = new Dictionary<string, List<AuthorizationModel>>(); private SecurityMgr() { } public static SecurityMgr getInstance() { return securityMgr; } public void login(string user) { List<AuthorizationModel> col = queryByUser(user); map.Add(user, col); } public bool hasPermit(string user, string securityEntity, string permit) { List<AuthorizationModel> col; if (!map.TryGetValue(user, out col) || col.Count == 0) { Console.WriteLine(user + "没有登录或是没有被分配任何权限"); return false; } foreach (AuthorizationModel authorizationModel in col) { Console.WriteLine("am == " + authorizationModel); if (authorizationModel.getSecurityEntity() == securityEntity && authorizationModel.getPermit() == permit) { return true; } } return false; } private List<AuthorizationModel> queryByUser(string user) { List<AuthorizationModel> col = new List<AuthorizationModel>(); foreach (string s in TestDB.colDB) { string[] ss = s.Split(','); if (ss[0] == user) { AuthorizationModel am = new AuthorizationModel(); am.setUser(ss[0]); am.setSecurityEntity(ss[1]); am.setPermit(ss[2]); col.Add(am); } } return col; } } }
20.1.3 有何问题
Q:有何问题
A:在系统当中,存在大量的细粒度对象,而且存在大量的重复数据,严重耗费内存,如何解决呢
20.2 解决方案
20.2.1 使用享元模式来解决问题
Q:享元模式的定义
A:运用共享技术有效地支持大量细粒度的对象
Q:应用享元模式来解决的思路
A:需要分离出被缓存对象实例中,那些数据是不变且重复出现的,那些数据是经常变化的,真正应该被缓存的数据是那些不变且重复出现的数据,把它们称为对象的内部状态,而那些变化的数据就不缓存了,把它们称为对象的外部状态
这样在实现的时候,把内部状态分离出来共享,称之为享元,通过共享享元对象来减少对内存的占用.把外部状态分离出来,放到外部,让应用在使用的时候进行维护,并在需要的时候传递给享元对象使用.为了控制对内部状态的共享,并且让外部能简单地使用共享数据,提供一个工厂来管理享元,把它称为享元工厂
20.2.2 享元模式的结构和说明
[Flyweight] 享元接口,通过这个接口Flyweight可以接受并作用于外部状态,通过这个接口传入外部的状态,在享元对象的方法处理中可能会使用这些外部的数据
[ConcreteFlyweight] 具体的享元实现对象,必须是可共享的,需要封装Flyweight的内部状态
[UnsharedConcreteFlyweight] 非共享的享元实现对象,并不是所有的Flyweight实现对象都需要共享,非共享的享元实现对象通常是对共享享元对象的组合对象.
[FlyweightFactory] 享元工厂,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口
[Client] 享元客户端,主要的工作是维持一个对Flyweight的引用,计算或存储享元对象的外部状态,当然这里可以访问共享和不共享的Flyweight对象.
20.2.3 享元模式示例代码
using System; using System.Collections; using System.Collections.Generic; using System.IO; namespace Test2 { public class Program { static void Main(string[] args) { SecurityMgr mgr = SecurityMgr.getInstance(); mgr.login("张三"); mgr.login("李四"); bool f1 = mgr.hasPermit("张三", "薪资数据", "查看"); bool f2 = mgr.hasPermit("李四", "薪资数据", "查看"); Console.WriteLine("f1 == " + f1); Console.WriteLine("f2 == " + f2); for (int i = 0; i < 3; i++) { mgr.login("张三" + i); mgr.hasPermit("张三" + i, "薪资数据", "查看"); } } } public class TestDB { public static List<string> colDB = new List<string>(); static TestDB() { colDB.Add("张三,人员列表,查看"); colDB.Add("李四,人员列表,查看"); colDB.Add("李四,薪资数据,查看"); colDB.Add("李四,薪资数据,修改"); for (int i = 0; i < 3; i++) { colDB.Add("张三" + i + ",人员列表,查看"); } } } public class AuthorizationModel { private string user; private string securityEntity; private string permit; public string getUser() { return user; } public void setUser(string user) { this.user = user; } public string getSecurityEntity() { return securityEntity; } public void setSecurityEntity(string securityEntity) { this.securityEntity = securityEntity; } public string getPermit() { return permit; } public void setPermit(string permit) { this.permit = permit; } } public class SecurityMgr { private static SecurityMgr securityMgr = new SecurityMgr(); private Dictionary<string,List<AuthorizationModel>> map = new Dictionary<string, List<AuthorizationModel>>(); private SecurityMgr() { } public static SecurityMgr getInstance() { return securityMgr; } public void login(string user) { List<AuthorizationModel> col = queryByUser(user); map.Add(user, col); } public bool hasPermit(string user, string securityEntity, string permit) { List<AuthorizationModel> col; if (!map.TryGetValue(user, out col) || col.Count == 0) { Console.WriteLine(user + "没有登录或是没有被分配任何权限"); return false; } foreach (AuthorizationModel authorizationModel in col) { Console.WriteLine("am == " + authorizationModel + " " + authorizationModel.GetHashCode()); if (authorizationModel.getSecurityEntity() == securityEntity && authorizationModel.getPermit() == permit) { return true; } } return false; } private List<AuthorizationModel> queryByUser(string user) { List<AuthorizationModel> col = new List<AuthorizationModel>(); foreach (string s in TestDB.colDB) { string[] ss = s.Split(','); if (ss[0] == user) { AuthorizationModel am = new AuthorizationModel(); am.setUser(ss[0]); am.setSecurityEntity(ss[1]); am.setPermit(ss[2]); col.Add(am); } } return col; } } }
20.2.4 使用享元模式重写示例
using System; using System.Collections; using System.Collections.Generic; using System.IO; namespace Test2 { public class Program { static void Main(string[] args) { SecurityMgr mgr = SecurityMgr.getInstance(); mgr.login("张三"); mgr.login("李四"); bool f1 = mgr.hasPermit("张三", "薪资数据", "查看"); bool f2 = mgr.hasPermit("李四", "薪资数据", "查看"); Console.WriteLine("f1 == " + f1); Console.WriteLine("f2 == " + f2); for (int i = 0; i < 3; i++) { mgr.login("张三" + i); mgr.hasPermit("张三" + i, "薪资数据", "查看"); } } } public class TestDB { public static List<string> colDB = new List<string>(); static TestDB() { colDB.Add("张三,人员列表,查看"); colDB.Add("李四,人员列表,查看"); colDB.Add("李四,薪资数据,查看"); colDB.Add("李四,薪资数据,修改"); for (int i = 0; i < 3; i++) { colDB.Add("张三" + i + ",人员列表,查看"); } } } public interface Flyweight { bool match(string securityEntity, string permit); } public class AuthorizationFlyweight : Flyweight { private string securityEntity; private string permit; public AuthorizationFlyweight(string state) { string[] ss = state.Split(','); securityEntity = ss[0]; permit = ss[1]; } public string getSecurityEntity() { return securityEntity; } public string getPermit() { return permit; } public bool match(string securityEntity, string permit) { if (this.securityEntity == securityEntity && this.permit == permit) { return true; } return false; } } public class FlyweightFactory { private static FlyweightFactory factory = new FlyweightFactory(); private Dictionary<string,Flyweight> fsMap = new Dictionary<string, Flyweight>(); private FlyweightFactory() { } public static FlyweightFactory getInstance() { return factory; } public Flyweight getFlyweight(string key) { Flyweight f; if (!fsMap.TryGetValue(key, out f)) { f = new AuthorizationFlyweight(key); fsMap[key] = f; } return f; } } // 充当 Client 角色 public class SecurityMgr { private static SecurityMgr securityMgr = new SecurityMgr(); private Dictionary<string,List<Flyweight>> map = new Dictionary<string, List<Flyweight>>(); private SecurityMgr() { } public static SecurityMgr getInstance() { return securityMgr; } public void login(string user) { List<Flyweight> col = queryByUser(user); map[user] = col; } public bool hasPermit(string user, string securityEntity, string permit) { List<Flyweight> col; if (!map.TryGetValue(user, out col) || col.Count == 0) { Console.WriteLine(user + "没有登录或是没有被分配任何权限"); return false; } foreach (Flyweight flyweight in col) { Console.WriteLine("fm == " + flyweight + " " + flyweight.GetHashCode()); if (flyweight.match(securityEntity, permit)) { return true; } } return false; } private List<Flyweight> queryByUser(string user) { List<Flyweight> col = new List<Flyweight>(); foreach (string s in TestDB.colDB) { string[] ss = s.Split(','); if (ss[0] == user) { Flyweight fm = FlyweightFactory.getInstance().getFlyweight(ss[1] + "," + ss[2]); col.Add(fm); } } return col; } } }
20.3 模式讲解
20.3.1 认识享元模式
Q:变与不变
A:享元模式设计的重点就在于分离变与不变.把一个对象的状态分成内部状态和外部状态,内部状态是不变的,外部状态是可变的.然后通过共享不变的部分,达到减少对象数量并节约内存的目的.在享元对象需要的时候,可以从外部传入外部状态给共享的对象,共享对象会在功能处理的时候,使用自己内部的状态和这些外部的状态
Q:内部状态和外部状态
A:享元模式的内部状态,通常指的是包含在享元对象内部的,对象本身的状态,是独立于使用享元的场景的信息,一般创建后就不再变化的状态,因此可以共享
外部状态指的是享元对象之外的状态,取决于使用享元的场景,会根据使用场景而变化,因此不可共享.如果享元对象需要这些外部状态的话,可以从外部传递到享元对象中,比如通过方法的参数来传递
也就是说享元模式真正缓存和共享的数据是享元的内部状态,而外部状态是不应该被缓存共享的
还有一点,内部状态和外部状态是独立的,外部状态的变化不应该影响到内部状态
Q:实例池
A:所谓实例池,指的是缓存和管理对象实例的程序,通常实例池会提供对象实例的运行环境,并控制对象实例的生命周期
20.3.2 不需要共享的享元模式
20.3.3 对享元对象的管理
20.3.4 享元模式的优缺点
优点
1.减少对象数量,节省内存空间
缺点
1.维护共享对象,需要额外开销
20.3.5 思考享元模式
Q:享元模式的本质
A:分离与共享
分离的是对象状态中变与不变的部分,共享的是对象中不变的部分.享元模式的关键之处就在于分离变与不变,把不变的部分作为享元对象的内部状态,而变化部分则作为外部状态,由外部来维护,这样享元对象就能够被共享,从而减少对象数量,并节省大量的内存空间
理解了这个本质后,在使用享元模式的时候,就会考虑,那些状态需要分离?如何分离?分离后如何处理?哪些需要共享?如何管理共享的对象?外部如何使用共享的享元对象?是否需要不共享的对象?等等
把这些问题都思考清楚,找到相应的解决方法,那么享元模式也就应用起来了,可能是标准的应用,也可能是变形的应用,但万变不离其宗
Q:何时选用享元模式
A: 1.如果一个应用程序使用了大量的细粒度对象,可以使用享元模式来减少对象数量.
2.如果由于使用大量的对象,造成很大的存储开销,可以使用享元模式来减少对象数量,并节约内存
3.如果对象的大多数状态都可以转变为外部状态,比如通过计算得到,或是从外部传入等,可以使用享元模式l来实现内部状态和外部状态的分离
4.如果不考虑对象的外部状态,可以用相对较少的共享对象取代很多组合对象,可以使用享元模式来共享对象,然后组合对象来使用这些共享对象
20.3.6 相关模式
1.享元模式与单例模式
这两个模式可以组合使用
通常情况下,享元模式中的享元工厂可以实现成为单例.另外,享元工厂中缓存的享元对象,都是单实例的, 可以看成是单例模式的一种变形控制,在享元工厂中来单例享元模式
2.享元模式与组合模式
这两个模式可以组合使用
在享元模式中,存在不需要共享的享元实现,这些不需要共享的享元通常是对共享的享元对象的组合对象.也就是说,享元模式通常会和组合模式组合使用,来实现更复杂的对象层次结构
3.享元模式与状态模式
这两个模式可以组合使用
可以使用享元模式来共享状态模式中的状态对象.通常在状态模式中,会存在数量很大的,细粒度的状态对象,而且它们基本上都是可以重复使用的,都是用来处理某一个固定的状态的,它们需要的数据通常都是由上下文传入,也就是变化部分都分离出去了,所以可以用享元模式来实现这些状态对象
4.享元模式与策略模式
这两个模式可以组合使用
可以使用享元模式来实现策略模式中的策略对象.和状态模式一样,在策略模式中也存在大量细粒度的策略对象,它们需要的数据同样是从上下文传入的,所以可以使用享元模式来实现这些策略对象
第21章 解释器模式(Interpreter)
21.1 场景问题
21.1.1 读取配置文件
21.1.2 不用模式的解决方案
<?xml version="1.0" encoding="utf-8"?> <root> <jdbc> <driver-class>驱动类名</driver-class> <url>连接数据库的URL</url> <user>连接数据库的用户名</user> <password>连接数据库的密码</password> </jdbc> <application-xml>缺省读取的Spring配置的文件名称</application-xml> </root>
using System; using System.Collections; using System.Collections.Generic; using System.Xml; namespace ConsoleApplication1 { public class Program { public static void Main() { ReadAppXml rax = new ReadAppXml();; rax.read(@"test1.xml"); } } public class ReadAppXml { public void read(string filePathName) { XmlDocument doc = new XmlDocument(); doc.Load(filePathName); XmlNode root = doc.SelectSingleNode("root"); XmlNode jdbc = root.SelectSingleNode("jdbc"); XmlNode driverClass = jdbc.SelectSingleNode("driver-class"); Console.WriteLine("driverClass == " + driverClass.InnerText); XmlNode urlNode = jdbc.SelectSingleNode("url"); Console.WriteLine("url == " + urlNode.InnerText); XmlNode userNode = jdbc.SelectSingleNode("user"); Console.WriteLine("user == " + userNode.InnerText); XmlNode passwordNode = jdbc.SelectSingleNode("password"); Console.WriteLine("password == " + passwordNode.InnerText); XmlNode applicationXmlNode = root.SelectSingleNode("application-xml"); Console.WriteLine("applicationXml == " + applicationXmlNode.InnerText); } } }
21.1.3 有何问题
Q:有何问题
A:当xml的结构发生改变后,能够很方便地获取相应元素或者是属性的值,而不用再去修改解析xml的程序
21.2 解决方案
21.2.1 使用解释器模式来解决问题
Q:解释器模式的定义
A:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
Q:应用解释器模式来解决问题的思路
A:想要解决当xml的结构发生改变后,不用修改解析部分的代码,一个自然的思路就是要把解析部分的代码写成公共的,而且还要是通用的,能够满足各种xml取值的需要.
要解决这些问题,其中的一个解决方案就是解释器模式.在描述这个模式的解决思路之前,先解释两个概念,一个是解析器(不是指xml的解析器),另一个是解释器
1.这里的解析器,指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序,不是指xml的解析器
2.这里的解释器,指的是解释抽象语法树,并执行每个节点对应的功能的程序
要解决通用解析xml的问题,第一步:需要先设计一个简单的表达式语言,在客户端调用解析程序的时候,传入用这个表达式语言描述的一个表达式,然后把这个表达式通过解析器的解析,形成一个抽象的语法树
第二步:解析完成后,自动调用解释器来解释抽象语法树,并执行每个节点所对应的功能,从而完成通用的xml解析
这样一来,每次当xml结构发生了更改,也就是在客户端调用的时候,传入不同的表达式即可,整个解析xml过程的代码都不需要再修改了
21.2.2 解释器模式的结构和说明
[AbstractExpression] 定义解释器的接口,约定解释器的解释操作
[TerminalExpression] 终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器
[NonterminalExpression] 非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的组合对象.可以有多种非终结符解释器
[Context] 上下文,通常包含各个解释器需要的数据或是公共的功能
[Client] 客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达转换成为使用解释器对象描述的抽象语法树,然后调用解释操作
21.2.3 解释器模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { } } public class Context { } public abstract class AbstractExpression { public abstract void interpret(Context ctx); } public class TerminalExpression : AbstractExpression { public override void interpret(Context ctx) { } } public class NonterminalExpression : AbstractExpression { public override void interpret(Context ctx) { } } public class Client { } }
21.2.4 使用解释器模式重写代码
using System; using System.Collections; using System.Collections.Generic; using System.Xml; namespace Test2 { public class Program { static void Main(string[] args) { Context c = new Context("test1.xml"); ElementExpression root = new ElementExpression("root"); ElementExpression aEle = new ElementExpression("a"); ElementExpression bEle = new ElementExpression("b"); ElementTerminalExpression cEle = new ElementTerminalExpression("c"); root.addEle(aEle); aEle.addEle(bEle); bEle.addEle(cEle); string[] ss = root.interpret(c); Console.WriteLine("c的值是 = " + ss[0]); } } public abstract class ReadXmlExpression { public abstract string[] interpret(Context c); } public class ElementExpression : ReadXmlExpression { private List<ReadXmlExpression> eles = new List<ReadXmlExpression>(); private string eleName = ""; public ElementExpression(string eleName) { this.eleName = eleName; } public bool addEle(ReadXmlExpression ele) { this.eles.Add(ele); return true; } public bool removeEle(ReadXmlExpression ele) { this.eles.Remove(ele); return true; } public override string[] interpret(Context c) { XmlElement pEle = c.getPreEle(); if (pEle == null) { c.setPreEle(c.getDocument().DocumentElement); } else { XmlElement nowEle = c.getNowEle(pEle, eleName); c.setPreEle(nowEle); } string[] ss = null; foreach (ReadXmlExpression readXmlExpression in eles) { ss = readXmlExpression.interpret(c); } return ss; } } public class ElementTerminalExpression : ReadXmlExpression { private string eleName = ""; public ElementTerminalExpression(string name) { this.eleName = name; } public override string[] interpret(Context c) { XmlElement pEle = c.getPreEle(); XmlElement ele = null; if (pEle == null) { ele = c.getDocument().DocumentElement; } else { ele = c.getNowEle(pEle, eleName); c.setPreEle(ele); } string[] ss = new string[1]; ss[0] = ele.FirstChild.Value; return ss; } } public class PropertyTerminalExpression : ReadXmlExpression { private string propName; public PropertyTerminalExpression(string propName) { this.propName = propName; } public override string[] interpret(Context c) { string[] ss = new string[1]; ss[0] = c.getPreEle().GetAttribute(this.propName); return ss; } } public class Context { private XmlElement preEle; private XmlDocument document; public Context(string filePathName) { this.document = XmlUtil.getRoot(filePathName); } public void reInit() { preEle = null; } public XmlElement getNowEle(XmlElement pEle, string eleName) { XmlNodeList tempNodeList = pEle.ChildNodes; for (int i = 0; i < tempNodeList.Count; i++) { if (tempNodeList.Item(i) is XmlElement) { XmlElement nowEle = (XmlElement) tempNodeList.Item(i); if (nowEle.Name == eleName) { return nowEle; } } } return null; } public XmlElement getPreEle() { return preEle; } public void setPreEle(XmlElement preEle) { this.preEle = preEle; } public XmlDocument getDocument() { return document; } } public class XmlUtil { public static XmlDocument getRoot(string filePathName) { XmlDocument doc = new XmlDocument(); doc.Load(filePathName); return doc; } } }
21.3 模式讲解
21.3.1 认识解释器模式
21.3.2 读取多个元素或属性的值
21.3.3 解析器
21.3.4 解释器模式的优缺点
优点
1.易于实现语法
在解释器模式中,一条语法规则用一个解释器对象来解释执行.对于解释器的实现来讲,功能就变得比较简单,只需要考虑这一条语法规则的实现就可以了.其他的都不用管
2.易于扩展新的语法
正是由于采用一个解释器对象负责一条语法规则的方式,使得扩展新的语法非常容易.扩展了新的语法,只需要创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了
缺点
1.解释器模式的缺点是不适合复杂的语法
如果语法特别复杂,构建解释器模式需要的抽象语法树的工作是非常艰巨的,再加上有可能会需要构建多个抽象语法树.所以解释器模式不太适合于复杂的语法,对于复杂的语法,使用语法分析程序或编译器生成器可能会更好一些
21.3.5 思考解释器模式
Q:解释器模式的本质
A:分离实现,解释执行
解释器模式通过一个解释器对象处理一个语法规则的方式,把复杂的功能分离开,然后选择需要被执行的功能,并把这些功能组合成为需要被解释执行的抽象语法树;再按照抽象语法树来解释执行,实现相应的功能
认识这个本质对于识别和变形使用解释器模式是很有作用的.从表面上看,解释器模式关注的是我们平时不太用到的自定义语法的处理;但从实质上看,解释器模式的思路仍然是分离,封装,简化,和很多模式是一样的
比如,可以使用解释器模式模拟状态模式的功能,如果把解释器模式要处理的语法简化到只有一个状态标记,把解释器看成是对状态的处理对象,对同一个表示状态的语法,可以有很多不同的解释器,也就是有很多不同的处理状态的对象,然后在创建抽象语法树的时候,简化成根据状态的标记来创建相应的解释器,不用再构建树了.看看这样简化下来,是不是可以用解释器模拟出状态模式的功能呢?
同理,解释器模式可以模拟实现策略模式的功能,装饰器模式的功能等,尤其是模拟装饰器模式的功能,构建抽象语法树的过程,自然就对应成为组合装饰器的过程
Q:何时选用解释器模式
A:建议在以下情况中选用解释器模式
当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式
在使用解释器模式的时候,还有两个特点需要考虑,一个是语法相对应该比较简单,太复杂的语法不适合使用解释器模式;另一个是效率要求不是很高,对效率要求很高的情况下,不适合使用解释器模式
21.3.6 相关模式
1.解释器模式和组合模式
这两个模式可以组合使用
通常解释器模式都会使用组合模式来实现,这样能够方便地构建抽象语法树.一般非终结符解释器相当于组合模式的组合对象,终结符解释器就相当于叶子对象
2.解释器模式和迭代器模式
这两个模式可以组合使用
由于解释器模式通常使用组合模式来实现,因此在遍历整个对象结构的时候,自然可以使用迭代器模式
3.解释器模式和享元模式
这两个模式可以组合使用
在使用解释器模式的时候,可能会造成多个细粒度对象,比如,会有各种各样的终结符解释器,而这些终结符解释器对不同的表达式来说是一样的,是可以共用的,因此可以引入享元模式来共享这些对象
4.解释器模式和访问者模式
这两个模式可以组合使用
在解释器模式中,语法规则和解释器对象是有对应关系的,语法规则的变动意味着功能的变化,自然会导致使用不同的解释器对象;而且一个语法规则可以被不同的解释器解释执行
因此在构建抽象语法树的时候,如果每个节点所对应的解释器对象是固定的,这就意味着该节点对应的功能是固定的,那么就不得不根据需要来构建不同的抽象语法树
为了让构建的抽象语法树较为通用,那就要求解释器的功能不要那么固定,要能很方便地改变解释器的功能,这个时候问题就变成了如何能够很方便地更改树形结构中节点对象的功能了,访问者模式可以很好地实现这个功能
第22章 装饰模式(Decorator)
22.1 场景问题
22.1.1 复杂的奖金计算
22.1.2 简化后的奖金计算体系
22.1.3 不用模式的解决方案
using System; using System.Collections; using System.Collections.Generic; using System.Xml; namespace Test2 { public class Program { static void Main(string[] args) { Prize p = new Prize(); double zs = p.calcPrize("张三", DateTime.Now, DateTime.Now); Console.WriteLine("======张三应得奖金: " + zs); double ls = p.calcPrize("李四", DateTime.Now, DateTime.Now); Console.WriteLine("======李四应得奖金: " + ls); double ww = p.calcPrize("王五", DateTime.Now, DateTime.Now); Console.WriteLine("======王五应得奖金: " + ww); } } public class TempDB { public static Dictionary<string,double> mapMonthSaleMoney = new Dictionary<string, double>(); private TempDB() { } static TempDB() { mapMonthSaleMoney.Add("张三", 10000.0); mapMonthSaleMoney.Add("李四", 20000.0); mapMonthSaleMoney.Add("王五", 30000.0); } } public class Prize { public double calcPrize(string user, DateTime begin, DateTime end) { double prize = 0.0; prize = this.monthPrize(user, begin, end); prize += this.sumPrize(user, begin, end); if (this.isManager(user)) { prize += this.groupPrize(user, begin, end); } return prize; } private double monthPrize(string user, DateTime begin, DateTime end) { double prize = TempDB.mapMonthSaleMoney[user] * 0.03; Console.WriteLine(user + "当月业务奖金" + prize); return prize; } public double sumPrize(string user, DateTime begin, DateTime end) { double prize = 1000000 * 0.001; Console.WriteLine(user + "累计奖金" + prize); return prize; } private bool isManager(string user) { if (user == "王五") { return true; } return false; } public double groupPrize(string user, DateTime begin, DateTime end) { double group = 0.0; foreach (double value in TempDB.mapMonthSaleMoney.Values) { group += value; } double prize = group * 0.01; Console.WriteLine(user + "当月团队业务奖金" + prize); return prize; } } }
22.1.4 有何问题
Q:有何问题
A:若有一个计算奖金的对象,现在需要能够灵活地给它增加和减少功能,还需要能够动态地组合功能,每个功能就相当于在计算奖金的某个部分.现在的问题是,如何才能够透明地给一个对象增加功能,并实现功能的动态组合
22.2 解决方案
22.2.1 使用装饰模式l来解决问题
Q:装饰模式的定义
A:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活
Q:应用装饰模式来解决问题的思路
A:虽然经过简化,业务简单了很多,但是需要解决的问题仍不少,还需要解决:透明地给一个对象增加功能,并实现功能的动态组合
所谓透明地给一个对象增加功能,换句话说就是要给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象.而实现了给一个对象透明地增加功能,自然就实现了功能的动态组合,比如,原来的对象有A功能,现在透明地给它增加了一个B功能,是不是就相当于动态组合了A和B功能呢
在装饰模式的实现中,为了能够实现和原来使用被装饰对象的代码无缝结合,是通过定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类中,转调被装饰的对象,在转调的前后添加新的功能,这就实现了给被装饰对象增加功能,这个思路和"对象组合"非常相似.
在转调的时候,如果觉得被装饰对象的功能不再需要了,还可以直接替换掉,也就是不再转调.而是在装饰对象中完成全新的实现
22.2.2 装饰模式的结构和说明
[Component] 组件对象的接口,可以给这些对象动态地添加职责
[ConcreteComponent] 具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职责
[Decorator] 所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象
[ConcreteDecorator] 实际的装饰对象,实现具体要向被装饰对象添加的功能
22.2.3 装饰模式示例代码
using System; using System.Collections; using System.Collections.Generic; using System.Net; namespace Test2 { public class Program { static void Main(string[] args) { } } public abstract class Component { public abstract void operation(); } public class ConcreteComponent : Component { public override void operation() { } } public abstract class Decorator : Component { protected Component component; public Decorator(Component component) { this.component = component; } public override void operation() { component.operation(); } } public class ConcreteDecoratorA : Decorator { private string addedState; public ConcreteDecoratorA(Component component) : base(component) { } public string getAddedState() { return addedState; } public void setAddedState(string addedState) { this.addedState = addedState; } public override void operation() { base.operation(); } } public class ConcreteDecoratorB : Decorator { public ConcreteDecoratorB(Component component) : base(component) { } private void addedBehavior() { } public override void operation() { base.operation(); addedBehavior(); } } }
22.2.4 使用装饰模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { Component c1 = new ConcreteComponent(); Decorator d1 = new MonthPrizeDecorator(c1); Decorator d2 = new SumPrizeDecorator(d1); double zs = d2.calcPrize("张三", DateTime.Now, DateTime.Now); Console.WriteLine("======张三应得奖金: " + zs); double ls = d2.calcPrize("李四", DateTime.Now, DateTime.Now); Console.WriteLine("======李四应得奖金: " + ls); Decorator d3 = new GroupPrizeDecorator(d2); double ww = d3.calcPrize("王五", DateTime.Now, DateTime.Now); Console.WriteLine("======王经理应得奖金: " + ww); } } public class TempDB { public static Dictionary<string,double> mapMonthSaleMoney = new Dictionary<string, double>(); private TempDB() { } static TempDB() { mapMonthSaleMoney.Add("张三", 10000.0); mapMonthSaleMoney.Add("李四", 20000.0); mapMonthSaleMoney.Add("王五", 30000.0); } } public abstract class Component { public abstract double calcPrize(string user, DateTime begin, DateTime end); } public class ConcreteComponent : Component { public override double calcPrize(string user, DateTime begin, DateTime end) { return 0; } } public abstract class Decorator : Component { protected Component c; public Decorator(Component c) { this.c = c; } public override double calcPrize(string user, DateTime begin, DateTime end) { return c.calcPrize(user, begin, end); } } public class MonthPrizeDecorator : Decorator { public MonthPrizeDecorator(Component c) : base(c) { } public override double calcPrize(string user, DateTime begin, DateTime end) { double money = base.calcPrize(user, begin, end); double prize = TempDB.mapMonthSaleMoney[user] * 0.03; Console.WriteLine(user + "当月业务奖金" + prize); return money + prize; } } public class SumPrizeDecorator : Decorator { public SumPrizeDecorator(Component c) : base(c) { } public override double calcPrize(string user, DateTime begin, DateTime end) { double money = base.calcPrize(user, begin, end); double prize = 1000000 * 0.001; Console.WriteLine(user + "累计奖金" + prize); return money + prize; } } public class GroupPrizeDecorator : Decorator { public GroupPrizeDecorator(Component c) : base(c) { } public override double calcPrize(string user, DateTime begin, DateTime end) { double money = base.calcPrize(user, begin, end); double group = 0.0; foreach (double value in TempDB.mapMonthSaleMoney.Values) { group += value; } double prize = group * 0.01; Console.WriteLine(user + "当月团队业务奖金" + prize); return money + prize; } } }
22.3 模式讲解
22.3.1 认识装饰模式
Q:装饰模式的功能
A:装饰模式能够实现动态地为对象添加功能,是从一个对象外部来给对象增加功能,相当于是改变了对象的外观.当装饰过后,从外部使用系统的角度看,就不再是使用原始的那个对象了,而是使用被一系列的装饰器装饰过后的对象
这样就能够灵活地改变一个对象的功能,只要动态组合的装饰器发生了改变,那么最终所得到的对象的功能也就发生了改变
变相地还得到了另外一个好处,那就是装饰器功能的复用,可以给一个对象多次增加同一个装饰器,也可以用同一个装饰器装饰不同的对象
Q:对象组合
A:前面已经讲到了,一个类的功能的扩展方式,可以是继承,也可以是功能更强大,更灵活的对象组合的方式
其实,现在在面向对象的设计中,有一条基本的规则就是"尽量使用对象组合,而不是对象继承"来扩展和复用功能.装饰模式的思考起点就是这个规则
什么是对象组合?
假若有一个对象A,实现了一个a1的方法,而C对象想要来扩展A的功能,给它增加一个c11的方法,那么一个方案是继承
using System; using System.Collections; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { } } public class A { public void a1() { Console.WriteLine("now in A.a1"); } } public class C1 : A { public void c11() { Console.WriteLine("now in C1.c11"); } } }
另外一个方案就是使用对象组合,怎么组合呢?就是在C1对象中不再继承A对象了,而是去组合使用A对象的实例,通过转调A对象的功能来实现A对象已有的功能
using System; using System.Collections; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { } } public class A { public void a1() { Console.WriteLine("now in A.a1"); } } public class C2 { private A a = new A(); public void a1() { a.a1(); } public void c11() { Console.WriteLine("now in C2.c11"); } } }
对象组合是不是很简单,而且更灵活了
1.首先可以有选择地复用功能,不是所有A的功能都会被复用,在C2中少调用几个A定义的功能就可以了
2.其次在转调前后,可以实现一些功能处理,而且对于A对象是透明的,也就是A对象并不知道在a1方法处理的时候被追加了功能
3.还有一个额外的好处,就是可以组合拥有多个对象的功能
using System; using System.Collections; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { } } public class A { public void a1() { Console.WriteLine("now in A.a1"); } } public class B { public void b1() { Console.WriteLine("now in B.b1"); } } public class C3 { private A a = new A(); private B b = new B(); public void a1() { a.a1(); } public void b1() { b.b1(); } public void c11() { Console.WriteLine("now in C3.c11"); } } }
最后再说一点,就是关于对象组合中,何时创建被组合对象的实例
1.一种方案是在属性值上直接定义并创建需要组合的对象实例
2.另外一种方案是在属性上定义一个变量,来表示持有被组合对象的实例,具体实例从外部传入,也可以通过IoC/DI容器来注入
using System; using System.Collections; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { } } public class A { public void a1() { Console.WriteLine("now in A.a1"); } } public class B { public void b1() { Console.WriteLine("now in B.b1"); } } public class C4 { private A a = new A(); private B b; public void setB(B b) { this.b = b; } public void a1() { a.a1(); } public void b1() { b.b1(); } public void c11() { Console.WriteLine("now in C4.c11"); } } }
Q:装饰器
A:装饰器实现了对被装饰对象的某些装饰功能,可以在装饰器中调用被装饰对象的功能,获取相应的值,这其实是一种递归调用
在装饰器中不仅仅是可以给被装饰对象增加功能,还可以根据需要选择是否调用被装饰对象的功能,如果不调用被装饰对象的功能,那就变成完全重新实现了,相当于动态修改了被装饰对象的功能
Q:装饰器和组件类的关系
A:装饰器是用来装饰组件的,装饰器一定要实现和组件类一致的接口,保证它们是同一个类型,并具有同一个外观,这样组合完成的装饰才能够递归调用下去
组件类是不知道装饰器的存在的,装饰器为组件添加功能是一种透明的包装,组件类毫不知情.需要改变的是外部使用组件类的地方,现在需要使用包装后的类,接口是一样的,但是具体的实现类发生了改变
Q:退化形式
A:如果仅仅只是想要添加一个功能,就没有必要再设计装饰器的抽象类了,直接在装饰器中实现跟组件一样的接口,然后实现相应的装饰功能就可以了.但是建议最好还是设计上装饰器的抽象类,这样有利于程序的扩展
22.3.2 Java中的装饰模式应用
22.3.3 装饰模式和AOP
22.3.4 装饰模式的优缺点
优点
1.比继承更灵活
从为对象添加功能的角度来看,装饰模式比继承更灵活,继承是静态的,而且一旦继承所有子类都有一样的功能.而装饰模式采用把功能分离到每个装饰器当中,然后通过对象组合的方式,在运行时动态地组合功能,每个被装饰的对象最终有哪些功能,是由运行期动态组合的功能来决定的
2.更容易复用功能
装饰模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,使实现装饰器变得简单,更重要的是这样有利于装饰器功能的复用,可以给一个对象增加多个同样的装饰器,也可以把一个装饰器用来装饰不同的对象,从而实现复用装饰器的功能
3.简化高层定义
装饰模式可以通过组合装饰器的方式,为对象增添任意多的功能.因此在进行高层定义的时候,不用把所有的功能都定义出来,而是定义最基本的就可以了,可以在需要使用的时候,组合相应的装饰器来完成所需的功能.
缺点
1.会产生很多细粒度对象
前面说了,装饰模式是把一系列复杂的功能,分散到每个装饰器当中,一般一个装饰器只实现一个功能,这样会产生很多细粒度的对象,而且功能越复杂,需要的细粒度对象越多
22.3.5 思考装饰模式
Q:装饰模式的本质
A:动态组合
动态是手段,组合才是目的,这里的组合有两个意思,一个是动态功能的组合,也就是动态进行装饰器的组合;另外一个是指对象组合,通过对象组合来实现为被装饰对象透明地增加功能
但是要注意,装饰模式不仅可以增加功能,而且也可以控制功能的访问,完全实现新的功能,还可以控制装饰的功能是在被装饰功能之前还是之后来运行等
总之,装饰模式是通过把复杂功能简单化,分散化,然后在运行期间,根据需要来动态组合的这样一个模式
Q:何时选用装饰模式
A: 1.如果需要在不影响其他对象的情况下,以动态,透明的方式给对象添加职责,可以使用装饰模式,这几乎就是装饰模式的主要功能
2.如果不适合使用子类来进行扩展的时候,可以考虑使用装饰模式.因为装饰模式是使用的"对象组合"的方式.所谓不适合用子类扩展的方式,比如,扩展功能需要的子类太多,造成子类数目呈爆炸性增长
22.3.6 相关模式
1.装饰模式和适配器模式
这是两个没有什么关联的模式,放到一起来说,是因为它们有一个共同的别名:Wrapper;
这两个模式功能上是不一样的,适配器模式是用来改变接口的,而装饰模式是用来改变对象功能的
2.装饰模式与组合模式
这两个模式有相似之处,都涉及到对象的递归调用,从某个角度来说,可以把装饰看做是只有一个组件的组合
但是它们的目的完全不一样,装饰模式是要动态地给对象增加功能;而组合模式是想要管理组合对象和叶子对象,为它们提供过一个一致的操作接口给客户端,方便客户端的使用
3.装饰模式和策略模式
这两个模式可以组合使用
策略模式也可以实现动态地改变对象的功能,但是策略模式只是一层选择,也就是根据策略选择一下具体的实现类而已.而装饰模式不是一层,而是递归调用,无数层都可以,只要组合好装饰器的对象组合,那就可以依次调用下去.所以装饰模式更灵活
而且策略模式改变的是原始对象的功能,不像装饰模式,后面一个装饰器,改变的是经过前一个装饰器装饰后的对象.也就是策略模式改变的是对象的内核,而装饰模式改变的是对象的外壳
这两个模式可以组合使用,可以在一个具体的装饰器中使用策略模式来选择更具体的实现方式.比如前面计算奖金的另外一个问题就是参与计算的基数不同,奖金的计算方式也是不同的.举例来说:假设张三和李四参与同一个奖金的计算,张三的销售总额是2万元,而李四的销售总额是8万元,它们的计算公式是不一样的,假设奖金的计算规则是,销售额在5万以下,统一3%,而5万以上,5万内是4%,超过部分是6%
参与同一个奖金的计算,这就意味着可以使用同一个装饰器,但是在装饰器的内部,不同条件下计算公式不一样,那么怎么选择具体的实现策略呢?自然使用策略模式就可以了,也就是装饰模式和策略模式组合来使用
4.装饰模式与模板方法模式
这是两个功能上有点相似的模式
模板方法模式主要应用在算法骨架固定的情况,那么要是算法步骤不固定呢,也就是一个相对动态的算法步骤,就可以使用装饰模式了,因为在使用装饰模式的时候,进行装饰器的组装,其实也相当于是一个调用算法步骤的组装,相当于是一个动态的算法骨架
既然装饰模式可以实现动态的算法步骤的组装和调用,那么把这些算法步骤固定下来,那就是模板方法模式实现的功能了,因此装饰模式可以模拟实现模板方法模式的功能
第23章 职责链模式(Chain of Responsibility)
23.1 场景问题
23.1.1 申请聚餐费用
23.1.2 不用模式的解决方案
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { FeeRequest request = new FeeRequest(); string ret1 = request.requestToProjectManager("小李", 300); Console.WriteLine("the ret = " + ret1); string ret2 = request.requestToProjectManager("小张", 300); Console.WriteLine("the ret = " + ret2); string ret3 = request.requestToProjectManager("小李", 600); Console.WriteLine("the ret = " + ret3); string ret4 = request.requestToProjectManager("小张", 600); Console.WriteLine("the ret = " + ret4); string ret5 = request.requestToProjectManager("小李", 1200); Console.WriteLine("the ret = " + ret5); string ret6 = request.requestToProjectManager("小张", 1200); Console.WriteLine("the ret = " + ret6); } } public class FeeRequest { public string requestToProjectManager(string user, double fee) { string str = ""; if (fee < 500) { str = projectHandle(user, fee); } else if (fee < 1000) { str = depManagerHandle(user, fee); } else if (fee >= 1000) { str = generalManagerHandle(user, fee); } return str; } private string projectHandle(string user, double fee) { string str = ""; if (user == "小李") { str = "项目经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "项目经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } private string depManagerHandle(string user, double fee) { string str = ""; if (user == "小李") { str = "部门经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "部门经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } private string generalManagerHandle(string user, double fee) { string str = ""; if (user == "小李") { str = "总经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "总经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } } }
23.1.3 有何问题
Q:有何问题
A:把问题抽象一下,客户端发出一个请求,会有很多对象都可以来处理这个请求,而且不同对象的处理逻辑是不一样的.对于客户端而言,无所谓谁来处理,反正有对象处理就可以了.而且在上述处理中,还希望处理流程是可以灵活变动的,而处理请求的对象需要能方便地修改或者是被替换掉,以适应新的业务功能的需要.
23.2 解决方案
23.2.1 使用职责链模式来解决问题
Q:职责链模式的定义
A:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
Q:应用职责链模式来解决问题的思路
A:仔细分析上面的场景,当客户端提出一个聚餐费用的申请,后续处理这个申请的对象项目经理,部门经理和总经理,自然地形成了一个链,从项目经理->部门经理->总经理,客户端的申请请求就在这个链中传递,直到有领导处理为止.看起来,上面的功能要求很适合采用职责链来处理这个业务
要相让处理请求的流程可以灵活地变动,一个基本的思路,那就是动态构建流程步骤,这样随时都可以重新组合出新的流程来.而要让处理请求的对象也要很灵活,那就要让它足够简单,最好是只实现单一的功能,或者是有限的功能,这样更有利于修改和复用
职责链模式就很好地体现了上述的基本思路,首先职责链模式会定义一个所有处理请求的对象都要继承实现的抽象类,这样就有利于随时切换新的实现,其次每个处理请求对象只实现业务流程中的一步业务处理,这样使其变得简单;最后职责链模式会动态地来组合这些处理请求的对象,把它们按照流程动态地组合起来,并要求它们依次调用,这样就动态地实现了流程
这样一来,如果流程发生了变化,只要重新组合就可以了,如果某个处理的业务功能发生了变化,一个方案是修改该处理对应的处理对象,另一个方案是直接提供一个新的实现,然后在组合流程的时候,用新的实现替换掉旧的实现就可以了
23.2.2 职责链模式的结构和说明
[Handler] 定义职责的接口,通常在这里定义处理请求的方法,可以在这里实现后继链
[ConcreteHandler] 实现职责的类,在这个类中,实现对在它职责范围内请求的处理,如果不处理,就继续转发请求给后继者
[Client] 职责链的客户,向链上的具体处理对象提交请求,让职责链负责处理
23.2.3 职责链模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace ConsoleApplication1 { public class Program { public static void Main() { Handler h1 = new ConcreteHandler1(); Handler h2 = new ConcreteHandler2(); h1.setSuccessor(h2); h1.handleRequest(); } } public abstract class Handler { protected Handler successor; public void setSuccessor(Handler successor) { this.successor = successor; } public abstract void handleRequest(); } public class ConcreteHandler1 : Handler { public override void handleRequest() { bool someCondition = false; if (someCondition) { Console.WriteLine("ConcreteHandler1 handle request"); } else { if (this.successor != null) { this.successor.handleRequest(); } } } } public class ConcreteHandler2 : Handler { public override void handleRequest() { bool someCondition = false; if (someCondition) { Console.WriteLine("ConcreteHandler2 handle request"); } else { if (this.successor != null) { this.successor.handleRequest(); } } } } }
22.2.4 使用职责链模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { Handler h1 = new GeneralManager(); Handler h2 = new DepManager(); Handler h3 = new ProjectManager(); h3.setSuccessor(h2); h2.setSuccessor(h1); string ret1 = h3.handleFeeRequest("小李", 300); Console.WriteLine("the ret1 = " + ret1); string ret2 = h3.handleFeeRequest("小张", 300); Console.WriteLine("the ret2 = " + ret2); string ret3 = h3.handleFeeRequest("小李", 600); Console.WriteLine("the ret3 = " + ret3); string ret4 = h3.handleFeeRequest("小张", 600); Console.WriteLine("the ret4 = " + ret4); string ret5 = h3.handleFeeRequest("小李", 1200); Console.WriteLine("the ret5 = " + ret5); string ret6 = h3.handleFeeRequest("小张", 1200); Console.WriteLine("the ret6 = " + ret6); } } public abstract class Handler { protected Handler successor; public void setSuccessor(Handler successor) { this.successor = successor; } public abstract string handleFeeRequest(string user, double fee); } public class ProjectManager : Handler { public override string handleFeeRequest(string user, double fee) { string str = ""; if (fee < 500) { if (user == "小李") { str = "项目经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "项目经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } else { if (this.successor != null) { return successor.handleFeeRequest(user, fee); } } return str; } } public class DepManager : Handler { public override string handleFeeRequest(string user, double fee) { string str = ""; if (fee < 1000) { if (user == "小李") { str = "部门经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "部门经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } else { if (this.successor != null) { return successor.handleFeeRequest(user, fee); } } return str; } } public class GeneralManager : Handler { public override string handleFeeRequest(string user, double fee) { string str = ""; if (fee >= 1000) { if (user == "小李") { str = "总经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "总经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } else { if (successor != null) { return successor.handleFeeRequest(user, fee); } } return str; } } }
23.3 模式讲解
23.3.1 认识职责链模式
Q:职责链模式的功能
A:职责链模式主要用来处理,“客户端发出一个请求,有多个对象都有机会来处理这一个请求,但是客户端不知道究竟谁会来处理他的请求”这样的情况.也就是需要让请求者和接收者解耦,这样就可以动态地切换和组合接收者了
如果要变形使用职责链,就可以让这个请求继续传递,每个职责对象对这个请求进行一定的功能处理,从而形成一个处理请求的功能链
23.3.2 处理多种请求
简单方式
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { Handler h1 = new GeneralManager(); Handler h2 = new DepManager(); Handler h3 = new ProjectManager(); h3.setSuccessor(h2); h2.setSuccessor(h1); string ret1 = h3.handleFeeRequest("小李", 300); Console.WriteLine("the ret1=" + ret1); string ret2 = h3.handleFeeRequest("小李", 600); Console.WriteLine("the ret2=" + ret2); string ret3 = h3.handleFeeRequest("小李", 1200); Console.WriteLine("the ret3=" + ret3); h3.handlePreFeeRequest("小张", 3000); h3.handlePreFeeRequest("小张", 6000); h3.handlePreFeeRequest("小张", 32000); } } public abstract class Handler { protected Handler successor; public void setSuccessor(Handler successor) { this.successor = successor; } public abstract string handleFeeRequest(string user, double fee); public abstract bool handlePreFeeRequest(string user, double requestFee); } public class ProjectManager : Handler { public override string handleFeeRequest(string user, double fee) { string str = ""; if (fee < 500) { if (user == "小李") { str = "项目经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "项目经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } else { if (this.successor != null) { return successor.handleFeeRequest(user, fee); } } return str; } public override bool handlePreFeeRequest(string user, double requestNum) { if (requestNum < 5000) { Console.WriteLine("项目经理同意" + user+ "预支差旅费用" + requestNum + "元的请求"); return true; } else { if (this.successor != null) { return this.successor.handlePreFeeRequest(user, requestNum); } } return false; } } public class DepManager : Handler { public override string handleFeeRequest(string user, double fee) { string str = ""; if (fee < 1000) { if (user == "小李") { str = "部门经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "部门经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } else { if (this.successor != null) { return successor.handleFeeRequest(user, fee); } } return str; } public override bool handlePreFeeRequest(string user, double requestNum) { if (requestNum < 10000) { Console.WriteLine("部门经理同意" + user+ "预支差旅费用" + requestNum + "元的请求"); return true; } else { if (this.successor != null) { return this.successor.handlePreFeeRequest(user, requestNum); } } return false; } } public class GeneralManager : Handler { public override string handleFeeRequest(string user, double fee) { string str = ""; if (fee >= 1000) { if (user == "小李") { str = "总经理同意" + user + "聚餐费用" + fee + "元的请求"; } else { str = "总经理不同意" + user + "聚餐费用" + fee + "元的请求"; } return str; } else { if (successor != null) { return successor.handleFeeRequest(user, fee); } } return str; } public override bool handlePreFeeRequest(string user, double requestNum) { if (requestNum < 50000) { Console.WriteLine("总经理同意" + user+ "预支差旅费用" + requestNum + "元的请求"); return true; } else { if (this.successor != null) { return this.successor.handlePreFeeRequest(user, requestNum); } } return false; } } }
通用方式
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { Handler h1 = new GeneralManager(); Handler h2 = new DepManager(); Handler h3 = new ProjectManager(); h3.setSuccessor(h2); h2.setSuccessor(h1); FeeRequestModel frm = new FeeRequestModel(); frm.setFee(300); frm.setUser("小李"); string ret1 = (string) h3.handleRequest(frm); Console.WriteLine("the ret1=" + ret1); frm.setFee(800); string ret2 = (string) h3.handleRequest(frm); Console.WriteLine("the ret2=" + ret2); frm.setFee(1600); string ret3 = (string) h3.handleRequest(frm); Console.WriteLine("the ret3=" + ret3); } } public class RequestModel { private string type; public RequestModel(string type) { this.type = type; } public string getType() { return type; } } public class FeeRequestModel : RequestModel { public static string FEE_TYPE = "fee"; private double fee; private string user; public FeeRequestModel() : base(FEE_TYPE) { } public string getUser() { return user; } public void setUser(string user) { this.user = user; } public double getFee() { return fee; } public void setFee(double fee) { this.fee = fee; } } public class PreFeeRequestModel : RequestModel { public static string FEE_TYPE = "preFee"; private double fee; private string user; public PreFeeRequestModel() : base(FEE_TYPE) { } public string getUser() { return user; } public void setUser(string user) { this.user = user; } public double getFee() { return fee; } public void setFee(double fee) { this.fee = fee; } } public abstract class Handler { protected Handler successor; public void setSuccessor(Handler successor) { this.successor = successor; } public virtual object handleRequest(RequestModel rm) { if (successor != null) { return this.successor.handleRequest(rm); } else { Console.WriteLine("没有后续处理或者暂时不支持这样的功能处理"); return false; } } } public class ProjectManager : Handler { public override object handleRequest(RequestModel rm) { if (FeeRequestModel.FEE_TYPE == rm.getType()) { return handleFeeRequest(rm); } else { return base.handleRequest(rm); } } private object handleFeeRequest(RequestModel rm) { FeeRequestModel frm = (FeeRequestModel) rm; string str = ""; if (frm.getFee() < 500) { if (frm.getUser() == "小李") { str = "项目经理同意" + frm.getUser() + "聚餐费用" + frm.getFee() + "元的请求"; } else { str = "项目经理不同意" + frm.getUser() + "聚餐费用" + frm.getFee() + "元的请求"; } return str; } else { if (this.successor != null) { return successor.handleRequest(rm); } } return str; } // private bool handlePreFeeRequest(string user, double requestNum) { // if (requestNum < 5000) { // Console.WriteLine("项目经理同意" + user+ "预支差旅费用" + requestNum + "元的请求"); // return true; // } else { // if (this.successor != null) { // return this.successor.handlePreFeeRequest(user, requestNum); // } // } // // return false; // } } public class DepManager : Handler { public override object handleRequest(RequestModel rm) { if (FeeRequestModel.FEE_TYPE == rm.getType()) { return handleFeeRequest(rm); } else { return base.handleRequest(rm); } } private object handleFeeRequest(RequestModel rm) { FeeRequestModel frm = (FeeRequestModel)rm; string str = ""; if (frm.getFee() < 1000) { if (frm.getUser() == "小李") { str = "部门经理同意" + frm.getUser() + "聚餐费用" + frm.getFee() + "元的请求"; } else { str = "部门经理不同意" + frm.getUser() + "聚餐费用" + frm.getFee() + "元的请求"; } return str; } else { if (this.successor != null) { return successor.handleRequest(rm); } } return str; } // public override bool handlePreFeeRequest(string user, double requestNum) { // if (requestNum < 10000) { // Console.WriteLine("部门经理同意" + user+ "预支差旅费用" + requestNum + "元的请求"); // return true; // } else { // if (this.successor != null) { // return this.successor.handlePreFeeRequest(user, requestNum); // } // } // // return false; // } } public class GeneralManager : Handler { public override object handleRequest(RequestModel rm) { if (FeeRequestModel.FEE_TYPE == rm.getType()) { return handleFeeRequest(rm); } else { return base.handleRequest(rm); } } private object handleFeeRequest(RequestModel rm) { FeeRequestModel frm = (FeeRequestModel)rm; string str = ""; if (frm.getFee() >= 1000) { if (frm.getUser() == "小李") { str = "总经理同意" + frm.getUser() + "聚餐费用" + frm.getFee() + "元的请求"; } else { str = "总经理不同意" + frm.getUser() + "聚餐费用" + frm.getFee() + "元的请求"; } return str; } else { if (successor != null) { return successor.handleRequest(rm); } } return str; } // public override bool handlePreFeeRequest(string user, double requestNum) { // if (requestNum < 50000) { // Console.WriteLine("总经理同意" + user+ "预支差旅费用" + requestNum + "元的请求"); // return true; // } else { // if (this.successor != null) { // return this.successor.handlePreFeeRequest(user, requestNum); // } // } // // return false; // } } }
23.3.3 功能链
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { GoodsSaleEbo ebo = new GoodsSaleEbo(); SaleModel saleModel = new SaleModel(); saleModel.setGoods("张学友怀旧经典"); saleModel.setSaleNum(10); ebo.sale("小李", "张三", saleModel); ebo.sale("小张", "李四", saleModel); } } public class SaleModel { private string goods; private int saleNum; public string getGoods() { return goods; } public void setGoods(string goods) { this.goods = goods; } public int getSaleNum() { return saleNum; } public void setSaleNum(int saleNum) { this.saleNum = saleNum; } public override string ToString() { return "商品名称=" + goods + ",销售数量=" + saleNum; } } public abstract class SaleHandler { protected SaleHandler successor; public void setSuccessor(SaleHandler successor) { this.successor = successor; } public abstract bool sale(string user, string customer, SaleModel saleModel); } public class SaleSecurityCheck : SaleHandler { public override bool sale(string user, string customer, SaleModel saleModel) { if (user == "小李") { return this.successor.sale(user, customer, saleModel); } else { Console.WriteLine("对不起" + user + ",你没有保存销售信息的权限"); return false; } } } public class SaleDataCheck : SaleHandler { public override bool sale(string user, string customer, SaleModel saleModel) { if (user == null || user.Trim().Length == 0) { Console.WriteLine("申请人不能为空"); return false; } if (customer == null || customer.Trim().Length == 0) { Console.WriteLine("客户不能为空"); return false; } if (saleModel == null) { Console.WriteLine("销售商品的数据不能为空"); return false; } if (saleModel.getGoods() == null || saleModel.getGoods().Trim().Length == 0) { Console.WriteLine("销售的商品不能为空"); return false; } if (saleModel.getSaleNum() == 0) { Console.WriteLine("销售商品的数量不能为0"); return false; } return this.successor.sale(user, customer, saleModel); } } public class SaleLogicCheck : SaleHandler { public override bool sale(string user, string customer, SaleModel saleModel) { return this.successor.sale(user, customer, saleModel); } } public class SaleMgr : SaleHandler { public override bool sale(string user, string customer, SaleModel saleModel) { Console.WriteLine(user + "保存了" + customer + "购买" + saleModel + " 的销售收据"); return true; } } public class GoodsSaleEbo { public bool sale(string user, string customer, SaleModel saleModel) { SaleSecurityCheck ssc = new SaleSecurityCheck(); SaleDataCheck sdc = new SaleDataCheck(); SaleLogicCheck slc = new SaleLogicCheck(); SaleMgr sd = new SaleMgr(); ssc.setSuccessor(sdc); sdc.setSuccessor(slc); slc.setSuccessor(sd); return ssc.sale(user, customer, saleModel); } } }
23.3.4 职责链模式的优缺点
优点
1.请求者和接收者松散耦合
在职责链模式中,请求者并不知道接收者是谁,也不知道具体如何处理,请求者只是负责向职责链发出请求就可以了,而每个职责对象也不用管请求者或者是其他的职责对象,只负责处理自己的部分,其他的就交给其他的职责对象去处理.也就是说,请求者和接收者是完全解耦的
2.动态组合职责
职责链模式会把功能处理分散到单独的职责对象中,然后在使用的时候,可以动态组合职责形成职责链,从而可以灵活地给对象分配职责,也可以灵活地实现和改变对象的职责
缺点
1.产生很多细粒度对象
职责链模式会把功能处理分散到单独的职责对象中,也就是每个职责对象只处理一个方面的功能,要把整个业务处理完,需要很多职责对象的组合,这样会产生大量的细粒度职责对象
2.不一定能被处理
职责链模式的每个职责对象只负责自己处理的那一部分,因此可能会出现某个请求,把整个链传完了,都没有职责对象处理它,这就需要在使用职责链模式的时候,需要提供默认的处理,并且注意构建的链的有效性
23.3.5 思考职责链模式
Q:职责链模式的本质
A:分离职责,动态组合
分离职责是前提,只有先把复杂的功能分开,拆分成很多的步骤和小的功能处理,然后才能合理规划和定义职责类.可以有很多的职责类来负责处理某一个功能,让每个职责类负责处理功能的某一个方面,在运行期间进行动态组合,形成一个处理的链,把这个链运行完,功能也就处理完了
动态组合才是职责链模式的精华所在,因为要实现请求对象和处理对象的解耦,请求对象不知道谁才是真正的处理对象,因此要动态地把可能的处理对象组合起来.由于组合的方式是动态的,这就意味着可以很方便地修改和添加新的处理对象,从而让系统更加灵活和具有更好的扩展性
Q:何时选用职责链模式
A: 1.如果有多个对象可以处理同一个请求,但是具体由哪个对象来处理该请求,是运行时刻动态确定的,这种情况可以使用职责链模式,把处理请求的对象实现成为职责对象,然后把它们构成一个职责链,当请求在这个链中传递的时候,具体由哪个职责对象来处理,会在运行时动态判断
2.如果你想在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式.职责链模式实现了请求者和接收者之间的解耦,请求者不需要知道究竟是哪一个接收者对象来处理请求
3.如果想要动态指定处理一个请求的对象集合,可以使用职责链模式.职责链模式能动态地构建职责链,也就是动态地来决定到底哪些职责对象来参与到处理请求中来,相当于是动态地指定了处理一个请求的职责对象集合
23.3.6 相关模式
1.职责链模式和组合模式
这两个模式可以组合使用
可以把职责对象通过组合模式来组合,这样可以通过组合对象自动递归地向上调用,由父组件作为子组件的后继,从而形成链
这也就是前面提到过的使用外部已有的链接,这种情况在客户端使用的时候,就不用再构造链了,虽然不构造链,但是需要构造组合对象树,是一样的
2.职责链模式和装饰模式
这两个模式相似,从某个角度讲,可以相互模拟实现对方的功能
装饰模式能够动态地给被装饰对象添加功能,要求装饰器对象和被装饰的对象实现相同的接口.而职责链模式可以实现动态的职责组合,标准的功能是有一个对象处理就结束,但是如果处理完本职责不急于结束,而是继续向下传递请求,那么其功能就和装饰模式的功能差不多了,每个职责对象就类似于装饰器,可以实现某种功能
而且两个模式的本质也相似,都需要在运行期间动态组合,装饰模式是动态组合装饰器,而职责链是动态组合处理请求的职责对象的链
但是从标准的设计模式上来讲,这两个模式还是有较大区别的,这点要注意.
首先是目的不同,装饰模式是要实现透明的为对象添加功能,而职责链模式是要实现发送者和接收者解耦,另外一个,装饰模式是无限递归调用的,可以有任意多个对象来装饰功能,但是职责链模式是有一个处理就结束
3.职责链模式和策略模式
这两个模式可以组合使用
这两个模式有相似之处,如果把职责链简化到直接就能选择到相应的处理对象,那就跟策略模式的选择差不多,因此可以用职责链来模拟策略模式的功能.只是如果把职责链简化到这个地步,也就不存在链了,也就称不上是职责链了.两个模式可以组合使用,可以在职责链模式的某个职责实现的时候,使用策略模式来选择具体的实现,同样也可以在策略模式的某个策略实现中,使用职责链模式来实现功能处理
同理职责链模式也可以和状态模式组合使用
第24章 桥接模式(Bridge)
24.1 场景问题
24.1.1 发送提示消息
24.1.2 不用模式的解决方案
实现简化版本
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { } } public interface Message { void send(string message, string toUser); } public class CommonMessageSMS : Message { public void send(string message, string toUser) { Console.WriteLine("使用站内短消息的方式,发送消息'" + message + "'给" + toUser); } } public class CommonMessageEmail : Message { public void send(string message, string toUser) { Console.WriteLine("使用E-mail的方式,发送消息'" + message + "'给" + toUser); } } }
实现发送加急消息
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { } } public interface Message { void send(string message, string toUser); } public class CommonMessageSMS : Message { public void send(string message, string toUser) { Console.WriteLine("使用站内短消息的方式,发送消息'" + message + "'给" + toUser); } } public class CommonMessageEmail : Message { public void send(string message, string toUser) { Console.WriteLine("使用E-mail的方式,发送消息'" + message + "'给" + toUser); } } public interface UrgencyMessage : Message { object watch(string messageId); } public class UrgencyMessageSMS : UrgencyMessage { public void send(string message, string toUser) { message = "加急: " + message; Console.WriteLine("使用站内短消息的方式,发送消息'" + message + "'给" + toUser); } public object watch(string messageId) { return null; } } public class UrgencyMessageEmail : UrgencyMessage { public void send(string message, string toUser) { message = "加急:" + message; Console.WriteLine("使用E-mail的方式,发送消息'" + message + "'给" + toUser); } public object watch(string messageId) { return null; } } }
24.1.3 有何问题
继续添加特急消息的处理
继续添加发送手机消息的处理方式
Q:有何问题
A:采用通过继承来扩展的实现方式,有个明显的缺点,扩展消息的种类不太容易.不同种类的消息具有不同的业务,也就是有不同的实现,在这种情况下,每个种类的消息,需要实现所有不同的消息发送方式
更可怕的是,如果要新加入一种消息的发送方式,那么会要求所有的消息种类都要加入这种新的发送方式的实现
要是考虑业务功能上再扩展一下呢?比如,要求实现群发消息,也就是一次可以发送多条消息,这就意味着很多地方都得修改,太恐怖了
那么究竟该如何实现才能既实现功能,又能灵活地扩展呢?
24.2 解决方案
24.2.1 使用桥接模式来解决问题
Q:桥接模式的定义
A:将抽象部分与它的实现部分分离,使它们都可以独立地变化
Q:应用桥接模式来解决问题的思路
A:仔细分析上面的示例,根据示例的功能要求,示例的变化具有两个维度,一个纬度是抽象的消息这边,包括普通消息,加急消息和特急消息,这几个抽象的消息本身就具有一定的关系,加急消息和特急消息会扩展普通消息;另一个维度是在具体的消息发送方式上,包括站内短消息,E-mail和手机短信息,这几个方式是平等的,可被切换的方式.这两个维度一共可以组合出9种不同的可能性来
想要解决这问题,就必须要把这两个维度分开,也就是将抽象部分和实现部分分开,让它们相互独立,这样就可以实现独立的变化,使扩展变得简单
桥接模式通过引入实现的接口,把实现部分从系统种分离出去.那么,抽象这边如何使用具体的实现呢?肯定是用面向实现的接口来编程了,为了让抽象这边能够很方便地与实现结合起来,把顶层的抽象接口改成抽象类,在其中持有一个具体的实现部分的示例
这样一来,对于需要发送消息的客户端而言,就只需要创建相应的消息对象,然后调用整个消息对象的方法就可以了,这个消息对象会调用持有的真正的消息发送方式来把消息发送出去.也就是说客户端只是想要发送消息而已,并不想关心具体如何发送.
24.2.2 桥接模式的结构和说明
[Abstraction] 抽象部分的接口,通常在这个对象中,要维护一个实现部分的对象引用,抽象对象里面的部分,需要调用实现部分的对象来完成.这个对象中的方法,通常都是和具体的业务相关的方法
[RefinedAbstraction] 扩展抽象部分的接口,通常在这些对象中,定义跟实际业务相关的方法,这些方法的实现通常会使用Abstraction中定义的方法,也可能需要调用实现部分的对象来完成
[Implementor] 定义实现部分的接口,这个接口不用和Abstraction中的方法一致,通常是由Implementor接口提供基本的操作,而Abstraction中定义的是基于这些基本操作的业务方法,也就是说Abstraction定义了基于这些基本操作的较高层次的操作
[ConcreteImplementor] 真正实现Implementor接口的对象
24.2.3 桥接模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { } } public interface Implementor { void operationImpl(); } public abstract class Abstraction { protected Implementor impl; public Abstraction(Implementor impl) { this.impl = impl; } public virtual void operation() { impl.operationImpl(); } } public class ConcreteImplementorA : Implementor { public void operationImpl() { } } public class ConcreteImplementorB : Implementor { public void operationImpl() { } } public class RefinedAbstraction : Abstraction { public RefinedAbstraction(Implementor impl) : base(impl) { } public void otherOperation() { } } }
24.2.4 使用桥接模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { MessageImplementor impl = new MessageSMS(); AbstractMessage m = new CommonMessage(impl); m.sendMessage("请喝一杯茶","小李"); m = new UrgencyMessage(impl); m.sendMessage("请喝一杯茶","小李"); m = new SpecialUrgencyMessage(impl); m.sendMessage("请喝一杯茶","小李"); impl = new MessageMobile(); m = new CommonMessage(impl); m.sendMessage("请喝一杯茶","小李"); m = new UrgencyMessage(impl); m.sendMessage("请喝一杯茶","小李"); m = new SpecialUrgencyMessage(impl); m.sendMessage("请喝一杯茶","小李"); } } public interface MessageImplementor { void send(string message, string toUser); } public abstract class AbstractMessage { protected MessageImplementor impl; public AbstractMessage(MessageImplementor impl) { this.impl = impl; } public virtual void sendMessage(string message, string toUser) { impl.send(message,toUser); } } public class MessageSMS : MessageImplementor { public void send(string message, string toUser) { Console.WriteLine("使用站内短消息的方式,发送消息'" + message + "'给" + toUser); } } public class MessageEmail : MessageImplementor { public void send(string message, string toUser) { Console.WriteLine("使用E-mail的方式,发送消息'" + message + "'给" + toUser); } } public class MessageMobile : MessageImplementor { public void send(string message, string toUser) { Console.WriteLine("使用手机短消息的方式,发送消息'" + message + "'给" + toUser); } } public class CommonMessage : AbstractMessage { public CommonMessage(MessageImplementor impl) : base(impl) { } public override void sendMessage(string message, string toUser) { base.sendMessage(message,toUser); } } public class UrgencyMessage : AbstractMessage { public UrgencyMessage(MessageImplementor impl) : base(impl) { } public override void sendMessage(string message, string toUser) { message = "加急: " + message; base.sendMessage(message,toUser); } public object watch(string messageId) { return null; } } public class SpecialUrgencyMessage : AbstractMessage { public SpecialUrgencyMessage(MessageImplementor impl) : base(impl) { } public override void sendMessage(string message, string toUser) { message = "特急: " + message; base.sendMessage(message,toUser); } public void hurry(string messageId) { } } }
24.3 模式讲解
24.3.1 认识桥接模式
Q:什么是桥接
A:在桥接模式中,不太好理解的就是桥接概念,什么是桥接?为何需要桥接?如何桥接?把这些问题搞清楚了,也就基本明白桥接的含义了
一个一个来,先看看什么是桥接?所谓桥接,通俗点说就是在不同的东西之间搭一个桥,让它们能够连接起来,可以相互通讯和使用.那么在桥接模式中到底是给什么东西来搭桥呢?就是为了被分离了的抽象部分和实现部分来搭桥,比如前面示例中在抽象的消息和具体消息发送之间搭个桥
但是这里要注意一个问题,在桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,也就是个单向桥
Q:为何需要桥接
A:为了达到让抽象部分和实现部分都可以独立变化的目的,在桥接模式中,是把抽象部分和实现部分分离开来的,虽然从程序结构上是分开了,但是在抽象部分实现的时候,还是需要使用具体的实现的,这可怎么办?抽象部分如何才能调用到具体实现部分的功能呢?搭个桥,让抽象部分通过这个桥就可以调用到实现部分的功能了,因此需要桥接
Q:如何桥接
A:这个在理解上也很简单,只要让抽象部分拥有实现部分的接口对象,就桥接上了,在抽象部分即可通过这个接口来调用具体实现部分的功能,也就是说,桥接在程序上体现了在抽象部分拥有实现部分的接口对象.维护桥接就是维护这个关系
Q:独立变化
A:桥接模式的意图是使得抽象和实现可以独立变化,都可以分别扩充,也就是说抽象部分和实现部分是一种非常松散的关系.从某个角度来讲,抽象部分和实现部分是可以完全分开的,独立的,抽象部分不过是一个使用实现部分对外接口的独立程序罢了
那抽象和实现为何还要组合在一起呢?原因是在抽象部分和实现部分还是存在内部联系的,抽象部分的实现通常是需要调用实现部分的功能来实现的
24.3.2 谁来桥接
24.3.3 典型例子-JDBC
24.3.4 广义桥接-Java中无处不桥接
使用Java编写程序,一个很重要的原则就是"面向接口编程",说得准确点应该是"面向抽象编程",由于在Java开发中,更多地使用接口而非抽象类,因此通常就说成"面向接口编程"了
接口把具体的实现和使用接口的客户程序分离开来,从而使得具体的实现和使用接口的客户程序可以分别扩展,而不会相互影响
可能有些朋友会觉得,听起来怎么像是桥接模式的功能呢?没错,如果把桥接模式的抽象部分先稍微简化一下,暂时不要RefinedAbstraction部分,那么这两个图是差不多的
是不是差不多呢?有朋友可能会觉得还是有很大差异,差异主要在前面接口的客户程序是直接使用接口对象,不像桥接模式的抽象部分那样是持有具体实现部分的接口,这就导致画出来的结构图一个是依赖,一个是聚合关联
请思考它们的本质功能,桥接模式中的抽象部分持有具体实现部分的接口,最终目的是什么?是为了通过调用具体实现部分的接口中的方法来完成一定的功能,这和直接使用接口差不多,只是表现形式有点不一样,再说,前面那个使用接口的客户程序也可以持有相应的接口对象,这样从形式上就一样了
也就是说,从某个角度来讲,桥接模式就是对"面向抽象编程"设计原则的扩展.正是通过具体实现的接口,把抽象部分和具体的实现部分分离开来,抽象部分相当于是使用实现部分接口的客户程序.这样抽象部分和实现部分就松散耦合了,从而可以实现相互独立的变化
这样一来,几乎可以把所有面向抽象编写的程序,都视作是桥接模式的体现,至少也是简化的桥接模式,就算是广义的桥接吧.而Java编程很强调"面向抽象编程",因此,广义的桥接,在Java中可以说是无处不在
24.3.5 桥接模式的优点
1.分离抽象和实现部分
桥接模式分离了抽象部分和实现部分,从而极大地提高了系统的灵活性.让抽象部分和实现部分独立开来,分别定义接口,这有助于对系统进行分层,从而产生更好的结构化的系统.对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了
2.更好的扩展性
由于桥接模式把抽象部分和实现部分分离开了,而且分别定义接口,这就使得抽象部分和实现部分可以分别独立地扩展,而不会相互影响,从而大大地提高了系统的可扩展性
3.可动态地切换实现
由于桥接模式把抽象部分和实现部分分离开了,所以在实现桥接的时候,就可以实现动态的选择和使用具体的实现,也就是说一个实现不再是固定的绑定在一个抽象接口上了,可以实现运行期间动态地切换
4.可减少子类的个数
根据前面的讲述,对于有两个变化维度的情况,如果采用继承的实现方式,大约需要两个维度上的可变化数量的乘积个子类;而采用桥接模式来实现,大约需要两个维度上的可变化数量的和个子类.可以明显地减少子类的个数
24.3.6 思考桥接模式
Q:桥接模式的本质
A:分离抽象和实现
桥接模式的最重要的工作就是分离抽象和部分和实现部分,这是解决问题的关键,只有把抽象部分和实现部分分离开了,才能够让它们独立地变化;只有抽象部分和实现部分可以独立地变化,系统才会有更好的可扩展性和可维护性
还有其他的好处,比如,可以动态地切换实现,可以减少子类个数等.都是把抽象部分和实现部分分离以后带来的.如果不把抽象部分和实现部分分离开,那一切就无从谈起.所以综合来说,桥接模式的本质在于"分离抽象和实现"
Q:对设计原则的体现
A: 1.桥接模式很好地实现了开闭原则
通常应用桥接模式的地方,抽象部分和实现部分都是可变化的,也就是应用会有两个变化维度,桥接模式就是找到这两个变化,并分别封装起来,从而合理地实现OCP
在使用桥接模式的时候,通常情况下,顶层的Abstraction和Implementor是不变的,而具体的Implementor的实现是可变的.由于Abstraction是通过接口来操作具体的实现,因此具体的Implementor的实现是可以扩展的,根据需要可以有多个具体的实现
同样地,RefinedAbstraction也是可变的,它继承并扩展Abstraction,通常在RefinedAbstraction的实现中,会调用Abstraction中的方法,通过组合使用来完成更多的功能,这些功能常常是与具体业务有关系的
2.桥接模式还很好地体现了:多用对象组合,少用对象继承
在前面的实例中,如果使用对象继承来扩展功能,不但让对象之间有很强的耦合性,而且会需要很多的子类才能够完成相应的功能,前面已经讲述过了,需要两个维度上的可变化数量的乘积个子类
而采用对象的组合,松散了对象之间的耦合性,不但使每个对象变得简单和可维护,还大大减少了子类的个数,根据前面的讲述,大约需要两个维度上的可变化数量的和这么多个子类
Q:何时选用桥接模式
1.如果你不希望在抽象部分和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象部分和实现部分分开,然后在程序运行期间来动态地设置抽象部分需要用到的具体的实现,还可以动态地切换具体的实现
2.如果出现抽象部分和实现部分都能够扩展的情况,可以采用桥接模式,让抽象部分和实现部分独立地变化,从而灵活地进行单独扩展,而不是搅在一起,扩展一边就会影响到另一边
3.如果希望实现部分的修改不会对客户产生影响,可以采用桥接模式.由于客户是面向抽象的接口在运行,实现部分的修改可以独立于抽象部分,并不会对客户产生影响,也可以说对客户是透明的
4.如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的维度,然后通过桥接模式来分离它们,从而减少子类的数目
24.3.7 相关模式
1.桥接模式和策略模式
这两个模式有很大的相似之处
如果把桥接模式的抽象部分简化来看,暂时不去扩展Abstraction,也就是去掉RefinedAbstraction.可以把策略模式的Context当做是使用接口的对象,而Strategy就是某个接口,具体的策略实现就相当于接口的具体实现.这样看来的话,某些情况下,可以使用桥接模式来模拟实现策略模式的功能
这两个模式虽然相似,但也还是有区别的.最主要的是模式的目的不一样,策略模式的目的是封装一系列的算法,使得这些算法可以相互替换;而桥接模式的目的是分离抽象部分和实现部分,使得它们可以独立的变化
2.桥接模式和状态模式
由于从模式结构上看,状态模式和策略模式是一样的,因此这两个模式的关系也基本上类似于桥接模式和策略模式的关系,只不过状态模式的目的是封装状态对应的行为,并在内部状态改变的时候改变对象的行为
3.桥接模式和模板方法模式
这两个模式有相似之处
虽然标准的模板方法模式是采用继承来实现的,但是模板方法也可以通过回调接口的方式来实现.如果把接口的实现独立出去.那就类似于模板方法通过接口去调用具体的实现方法了,这样的结构就和简化的桥接模式类似了
可以使用桥接模式来模拟实现模板方法模式的功能,如果在实现Abstraction对象的时候,在其中定义方法,方法中就是某个固定的算法骨架,也就是说这个方法就相当于模板方法,.在模板方法模式中,是把不能确定实现的步骤线延迟到子类去实现;现在在桥接模式中,把不能确定实现的步骤委托给具体实现部分去完成,通过回调实现部分的接口,来完成算法骨架中的某些步骤,这样一来,就可以实现使用桥接模式来模拟实现模板方法模式的功能
使用桥接模式来模拟实现模板方法模式的功能,还有一个潜在的好处,就是模板方法也可以很方便地扩展和变化.在标准的模板方法中,一个问题就是当模板发生变化的时候,所有的子类都要变化,非常不方便.而使用桥接模式来实现类似的功能,就没有这个问题
4.桥接模式和抽象工厂模式
这两个模式可以组合使用
桥接模式中,抽象部分需要获取相应的实现部分的接口对象,那么谁来创建实现部分的具体实现对象呢?这就是抽象工厂模式派上用场的地方.也就是使用抽象工厂模式来创建和配置一个特定的具体的实现对象
事实上,抽象工厂主要是用来创建一系列对象的,如果创建的对象很少,或者是很简单,还可以采用简单工厂,也能达到同样的效果,但是会比抽象工厂来得简单
5.桥接模式和适配器模式
这两个模式可以组合使用
这两个模式功能是完全不一样的,适配器模式的功能主要是用来帮助无关的类协同工作,重点在解决原本由接口不兼容而不能一起工作的那些类,使得它们可以一起工作.而桥接模式则重点在分离抽象部分和实现部分
所以在使用上,通常在系统设计完成后,才会考虑使用适配器模式;而桥接模式是在系统开始的时候就要考虑使用
虽然功能上不一样,这两个模式还是可以组合使用的,比如,已有实现部分的接口,但是有些不太适应现在新的功能对接口的需要,完全抛弃把,有些功能还用得上,该怎么办呢?那就使用适配器来进行适配,使得旧得接口能够适应新得功能的需要
第25章 访问者模式(Visitor)
25.1 场景问题
25.1.1 扩展客户管理的功能
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { } } public abstract class Customer { private string customerId; private string name; public string getName() { return this.name; } public void setName(string name) { this.name = name; } public string getCustomerId() { return this.customerId; } public void setCustomerId(string customerId) { this.customerId = customerId; } public abstract void serviceRequest(); } public class EnterpriseCustomer : Customer { private string linkman; private string linkTelephone; private string registgerAddress; public override void serviceRequest() { Console.WriteLine(this.getName() + "企业提出服务请求"); } } public class PersonalCustomer : Customer { private string telephone; private int age; private string registerAddress; public override void serviceRequest() { Console.WriteLine("客户" + this.getName() + "提出服务请求"); } } }
25.1.2 不用模式的解决方案
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { List<Customer> colCustomer = preparedTestData(); foreach (Customer customer in colCustomer) { customer.predilectionAnalyze(); customer.worthAnalyze(); } } private static List<Customer> preparedTestData() { List<Customer> colCustomer = new List<Customer>(); Customer cm1 = new EnterpriseCustomer(); cm1.setName("ABC集团"); colCustomer.Add(cm1); Customer cm2 = new EnterpriseCustomer(); cm2.setName("CDE公司"); colCustomer.Add(cm2); Customer cm3 = new PersonalCustomer(); cm3.setName("张三"); colCustomer.Add(cm3); return colCustomer; } } public abstract class Customer { private string customerId; private string name; public string getName() { return this.name; } public void setName(string name) { this.name = name; } public string getCustomerId() { return this.customerId; } public void setCustomerId(string customerId) { this.customerId = customerId; } public abstract void serviceRequest(); public abstract void predilectionAnalyze(); public abstract void worthAnalyze(); } public class EnterpriseCustomer : Customer { private string linkman; private string linkTelephone; private string registgerAddress; public override void serviceRequest() { Console.WriteLine(this.getName() + "企业提出服务请求"); } public override void predilectionAnalyze() { Console.WriteLine("现在对企业客户" + this.getName() + "进行产品偏好分析"); } public override void worthAnalyze() { Console.WriteLine("现在对企业客户" + this.getName() + "进行j价值分析"); } } public class PersonalCustomer : Customer { private string telephone; private int age; private string registerAddress; public override void serviceRequest() { Console.WriteLine("客户" + this.getName() + "提出服务请求"); } public override void predilectionAnalyze() { Console.WriteLine("现在对个人客户" + this.getName() + "进行产品偏好分析"); } public override void worthAnalyze() { Console.WriteLine("现在对个人客户" + this.getName() + "进行价值分析"); } } }
25.1.3 有何问题
Q:有何问题
A: 1.在企业客户和个人客户的类中,都分别实现了提出服务请求,进行产品偏好分析,进行客户价值分析等功能,也就是说,这些功能的实现代码是混杂在同一个类中的;而且相同的功能分散到了不同的类中去实现,会导致整个系统难以理解,难以维护
2.更为痛苦的是,采用这样的实现方式,如果要给客户扩展新的功能,比如前面提到的针对不同的客户进行需求调查,针对不同的客户进行满意度分析,客户消费预期分析等.每次扩展,都需要改动企业客户的类和个人客户的类,当然也可以通过为它们扩展子类的方式,但是这样可能会造成过多的对象层次
那么有没有办法,能够在不改变客户对象结构中各元素类的前提下,为这些类定义新的功能?也就是要求不改变企业客户和个人客户类,就能为企业客户和个人客户类定义新的功能?
25.2 解决方案
25.2.1 使用访问者模式来解决问题
Q:访问者模式的定义
A:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
25.2.2 访问者模式的结构和说明
[Visitor] 访问者接口,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能
[ConcreteVisitor] 具体的访问者实现对象,实现要真正被添加到对象结构中的功能.
[Element] 抽象的元素对象,对象结构的顶层接口,定义接受访问的操作
[ConcreteElement] 具体元素对象,对象结构中具体的对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用
[ObjectStructure] 对象结构,通常包含多个被访问的对象,它可以遍历多个被访问的对象,也可以让访问者访问它的元素.可以是一个复合或是一个集合,如一个列表或无序集合
25.2.3 访问者模式示例代码
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { ObjectStructure os = new ObjectStructure(); Element eleA = new ConcreteElementA(); Element eleB = new ConcreteElementB(); os.addElement(eleA); os.addElement(eleB); Visitor visitor = new ConcreteVisitor1(); os.handleRequest(visitor); } } public interface Visitor { void visitConcreteElementA(ConcreteElementA elementA); void visitConcreteElementB(ConcreteElementB elementB); } public abstract class Element { public abstract void accept(Visitor visitor); } public class ConcreteElementA : Element { public override void accept(Visitor visitor) { visitor.visitConcreteElementA(this); } public void operationA() { } } public class ConcreteElementB : Element { public override void accept(Visitor visitor) { visitor.visitConcreteElementB(this); } public void operationB() { } } public class ConcreteVisitor1 : Visitor { public void visitConcreteElementA(ConcreteElementA element) { element.operationA(); } public void visitConcreteElementB(ConcreteElementB element) { element.operationB(); } } public class ObjectStructure { private List<Element> col = new List<Element>(); public void handleRequest(Visitor visitor) { foreach (Element element in col) { element.accept(visitor); } } public void addElement(Element ele) { col.Add(ele); } } }
25.2.4 使用访问者模式重写示例
using System; using System.Collections; using System.Collections.Generic; namespace Test2 { public class Program { static void Main(string[] args) { ObjectStructure os = new ObjectStructure(); Customer cm1 = new EnterpriseCustomer(); cm1.setName("ABC集团"); os.addElement(cm1); Customer cm2 = new EnterpriseCustomer(); cm2.setName("CDE公司"); os.addElement(cm2); Customer cm3 = new PersonalCustomer(); cm3.setName("张三"); os.addElement(cm3); ServiceRequestVisitor srVisitor = new ServiceRequestVisitor(); os.handleRequest(srVisitor); PredilectionAnalyzeVisitor paVisitor = new PredilectionAnalyzeVisitor(); os.handleRequest(paVisitor); WorthAnalyzeVisitor waVisitor = new WorthAnalyzeVisitor(); os.handleRequest(waVisitor); } } public abstract class Customer { private string customerId; private string name; public string getCustomerId() { return this.customerId; } public void setCustomerId(string customerId) { this.customerId = customerId; } public string getName() { return name; } public void setName(string name) { this.name = name; } public abstract void accept(Visitor visitor); } public class EnterpriseCustomer : Customer { private string linkman; private string linkTelephone; private string registerAddress; public override void accept(Visitor visitor) { visitor.visitEnterpriseCustomer(this); } } public class PersonalCustomer : Customer { private string telephone; private int age; public override void accept(Visitor visitor) { visitor.visitPersonalCustomer(this); } } public interface Visitor { void visitEnterpriseCustomer(EnterpriseCustomer ec); void visitPersonalCustomer(PersonalCustomer pc); } public class ServiceRequestVisitor : Visitor { public void visitEnterpriseCustomer(EnterpriseCustomer ec) { Console.WriteLine(ec.getName() + "企业提出服务请求"); } public void visitPersonalCustomer(PersonalCustomer pc) { Console.WriteLine("客户" + pc.getName() + "提出服务请求"); } } public class PredilectionAnalyzeVisitor : Visitor { public void visitEnterpriseCustomer(EnterpriseCustomer ec) { Console.WriteLine("现在对企业客户" + ec.getName() + "进行产品偏好分析"); } public void visitPersonalCustomer(PersonalCustomer pc) { Console.WriteLine("现在对个人客户" + pc.getName() + "进行产品偏好分析"); } } public class WorthAnalyzeVisitor : Visitor { public void visitEnterpriseCustomer(EnterpriseCustomer ec) { Console.WriteLine("现在对企业客户" + ec.getName() + "进行价值分析"); } public void visitPersonalCustomer(PersonalCustomer pc) { Console.WriteLine("现在对个人客户" + pc.getName() + "进行价值分析"); } } public class ObjectStructure { private List<Customer> col = new List<Customer>(); public void handleRequest(Visitor visitor) { foreach (Customer customer in col) { customer.accept(visitor); } } public void addElement(Customer ele) { col.Add(ele); } } }
25.3 模式讲解
25.3.1 认识访问者模式
25.3.2 操作组合对象结构
25.3.3 谁负责遍历所有元素对象
25.3.4 访问者模式的优缺点
优点
1.好的扩展性
能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
2.好的复用性
可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度
3.分离无关行为
可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一
缺点
1.对象结构变化很困难
不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高
2.破坏封装
访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructure,这破坏了对象的封装性
25.3.5 思考访问者模式
Q:访问者模式的本质
A:预留通路,回调实现
仔细思考访问者模式,它的实现主要是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者的对象上定义visit方法;然后在调用真正发生的时候,通过两次分发技术,利用预先定义好的通路,回调到访问者具体的实现上
明白了访问者模式的本质,就可以在定义一些通用功能,或者设计工具类的时候让访问者模式派上大用场,你可以把已经实现好的一些功能作为已有的对象结构,因为在今后可能会根据实际需要为它们增加新的功能,甚至希望开放接口来让其他开发人员扩展这些功能,所以你可以用访问者模式来设计,在这个对象结构上预留好通用的调用通路,在以后添加功能,或者是其他开发人员来扩展的时候,只需要提供新的访问者实现,就能够很好地加入到系统中来了
Q:何时选用访问者模式
A: 1.如果想对一个对象结构实施一些依赖于对象结构中具体类的操作,可以使用访问者模式
2.如果想对一个对象结构中的各个元素进行很多不同的而且不相关的操作,为了避免这些操作使类变得杂乱,可以使用访问者模式,把这些操作分散到不同的访问者对象中去,每个访问者对象实现同一类功能
3.如果对象结构很少变动,但是需要经常给对象结构中的元素对象定义新的操作,可以使用访问者模式
25.3.6 相关模式
1.访问者模式和组合模式
这两个模式可以组合使用
如同前面示例的那样,通过访问者模式给组合对象预留下扩展功能的接口,使得为组合模式的对象结构添加功能非常容易
2.访问者模式和装饰模式
这两个模式从表面上看功能有些相似,都能够实现在不修改原对象结构的情况下修改原对象的功能,但是装饰模式更多的是实现对已有功能的加强,修改或者完全全新实现;而访问者模式更多的是实现为对象结构添加新的功能
3.访问者模式和解释器模式
这两个模式可以组合使用
解释器模式在构建抽象语法树的时候,是使用组合模式来构建的,也就是说解释器模式解释并执行的抽象语法树是一个组合对象结构,实现对同一对象结构的不同解释和执行的功能,这正是访问者模式的优势所在,因此在使用解释器模式的时候通常会组合访问者模式来使用
附录A 常见面向对象设计原则
A.1 设计模式和设计原则
A.1.1 设计模式和设计原则的关系
面向对象的分析设计有很多原则,这些原则大多从思想层面给我们指出了面向对象分析设计的正确方向,是我们进行面向对象分析设计时应该尽力遵守的准则
而设计模式已经是针对某个场景下某些问题的某个解决方案.也就是说这些设计原则是思想上的指导,而设计模式是实现上的手段,因此设计模式也应该遵守这些原则,换句话说,设计模式就是这些设计原则的一些具体体现
A.1.2 为何不重点讲解设计原则
1.设计原则本身是从思想层面上进行指导,本身是高度概括和原则性的,只是一个设计上的大体方向,其具体实现并非只有设计模式这一种,可以在相同的原则指导下,做出很多不同的实现来
2.每一种设计模式并发不是单一地体现某一个设计原则.事实上,很多设计模式都是融合了很多个设计原则的思想,并不好特别强调设计模式对某个或者是某些设计原则的体现.而且每个设计模式在应用的时候也会有很多的考量,不同使用场景下,突出体现的设计原则也可能是不一样的
3.这些设计原则只是一个建议指导,事实上,在实际开发中,很少做到完全遵守,总是在有意无意地违反一些或者是部分设计原则.设计工作本来就是一个不断权衡的工作,有句话说得很好:"设计是一种危险的平衡艺术".设计原则只是一个指导,有些时候,还要综合考虑业务功能,实现的难度,系统性能,时间与空间等很多方面的问题
A.2 常见的面向对象设计原则
A.2.1 单一职责原则 SRP(Single Responsbility Principle)
所谓单一职责原则,指的是,一个类应该仅有一个引起它变化的原因
这里变化的原因就是所说的"职责",如果一个类有多个引起它变化的原因,那么也就意味着这个类有多个职责,再进一步说,就是把多个职责耦合在一起了
这会造成职责的相互影响,可能一个职责的变化,会影响到其他职责的实现,甚至引起其他职责随着变化,这种设计是很脆弱的
这个原则看起来是最简单和最好理解的,但是实际上是很难完全做到的,难点在于如何区分"职责".这个是没有标准量化的东西,哪些算职责,到底这个职责有多大的粒度,这个职责如何细化等.因此,这个原则也是最容易违反的
A.2.2 开放-关闭原则 OCP(Open Close Principle)
所谓开放-关闭原则,指的是,一个类应该对扩展开放,对修改关闭,一般也被简称为开闭原则,开闭原则是设计中非常核心的一个原则
开闭原则要求的是,类的行为是可以扩展的,而且是在不修改已有代码的情况下进行扩展,也不必改动已有的源代码或者二进制代码
看起来好像是矛盾的,怎么样才能实现呢?
实现开闭原则的关键就在于合理地抽象,分离出变化与不变化的部分,为变化的部分预留下可扩展的方式,比如,钩子方法或者是动态组合对象等
这个原则看起来也很简单.但事实上,一个系统要全部做到遵守开闭原则,几乎是不可能的,也没这个必要,适度的抽象可以提高系统的灵活性,使其可扩展,可维护,但是过度地抽象,会大大增加系统的复杂程度,应该在需要改变的地方应用开闭原则就可以了,而不用到处使用,从而陷入过度设计
A.2.3 里氏替换原则 LSP(Liskov Substitution Principle)
所谓里氏替换原则,指的是,子类型必须能够替换掉它们的父类型,这很明显是一种多态的使用情况,它可以避免在多态的应用中,出现某些隐蔽的错误
事实上,当一个类继承了另外一个类,那么子类就拥有了父类中可以继承下来的属性和操作.理论上来说,此时使用子类型去替换掉父类型,应该不会引起原来使用父类型的程序出现错误
但是,很不幸的是,在某些情况下是会出现问题的.比如,如果子类型覆盖了父类型的某些方法,或者是子类型修改了父类型某些属性的值,那么原来使用父类型的程序就可能会出现错误,因为在运行期间,从表面上看,它调用的是父类型的方法,需要的是父类型方法实现的功能,但是实际运行调用的却是子类型覆盖实现的方法,而该方法和父类型的方法并不一样,于是导致错误的产生
从另外一个角度来说,里氏替换原则是实现开闭的主要原则之一.开闭原则要求对扩展开放,扩展的一个实现手段就是使用继承;而里氏替换原则是保证子类型能够正确替换父类型,只有能正确替换,才能实现扩展,否则扩展了也会出现错误
A.2.4 依赖倒置原则 DIP(Dependence Inversion Principle)
所谓依赖倒置原则,指的是,要依赖于抽象,不要依赖于具体类,要做到依赖导致,典型的应该做到
1.高层模块不应该依赖于底层模块,二者都应该依赖于抽象
2.抽象不应该依赖于具体实现,具体实现应该依赖于抽象
很多人觉得,层次化调用的时候,应该是高层调用"底层所拥有的接口",这是一种典型的误解.事实上,一般高层模块包含对业务功能的处理和业务策略选择,应该被重用,是高层模块去影响底层的具体实现
因此,这个底层的接口应该是由高层提出的,然后由底层实现的,也就是说底层的接口的所有权在高层模块,因此是一种所有权的倒置
倒置接口所有权,这就是著名的Hollywood(好莱坞)原则:不要找我们,我们会联系你
A.2.5 接口隔离原则 ISP(Interface Segregation Principle)
所谓接口隔离原则,指的是,不应该强迫客户依赖于它们不用的方法
这个原则用来处理那些些比较"庞大"的接口,这种接口通常会有比较多的操作声明,涉及到很多的职责.客户在使用这样的接口的时候,通常会有很多他不需要的方法,这些方法对于客户来讲,就是一种接口污染,相当于强迫用户在一大堆"垃圾方法"中去寻找他需要的方法
因此,这样的接口应该被分离,应该按照不同的客户需要来分离成为针对客户的接口,这样的接口中,只包含客户需要的操作声明,这样既方便了客户的使用,也可以避免因误用接口而导致的错误
A.2.6 最少知识原则 LKP(Least Knowledge Principle)
所谓最少知识原则,指的是,只和你的朋友谈话
那么究竟哪些对象才能被当作朋友呢?
1.当前对象本身
2.通过方法的参数传递进来的对象
3.当前对象所创建的对象
4.当前对象的实例变量所引用的对象
5.方法内所创建或实例化的对象
A.2.7 其他原则
除了上面提到的这些原则,还有一些大家都熟知的原则,比如:
1.面向接口编程
2.优先使用组合,而非继承
当然也还有很多大家不是很熟悉的原则,比如
1.一个类需要的数据应该隐藏在类的内部
2.类之间应该零耦合,或者只是传导耦合,换句话说,类之间要么没有关系,要么只使用另一个类的接口提供的操作;
3.在水平方向上尽可能统一地分布系统功能