第34章 命令模式+责任链模式
34.1 搬移UNIX的命令
在操作系统的世界里,有两大阵营一直在PK着:*nix(包括UNIX和Linux)和Windows。从目前的统计数据来看,*nix在应用服务器领域占据相对优势,不过Windows也不甘示弱,国内某些小型银行已经在使用PC Server(安装Windows操作系统的服务器)集群来进行银行业务运算,而且稳定性、性能各方面的效果不错;而在个人桌面方面,Windows是占绝对优势的,大家应该基本上都在用这个操作系统,它的诸多优点这里就不多说了,我们今天就来解决一个习惯问题。如果你负责过UNIX系统维护,你自己的笔记本又是Windows操作系统的话,我想你肯定有这样的经验,如图34-1所示。
![](https://box.kancloud.cn/2016-08-14_57b0036f9c36d.jpg)
图34-1 时常犯的错误
是不是经常把UNIX上的命令敲到Windows系统了?为了避免这种情况发生,可以把UNIX上的命令移植到Windows上,也就是Windows下的shell工具,有很多类似的工具,比如cygwin、GUN Bash等,这些都是非常完美的工具,我们今天的任务就是自己写一个这样的工具。怎么写呢?我们学了这么多的模式,当然要融会贯通了,可以使用命令模式、责任链模式、模板方法模式设计一个方便扩展、稳定的工具。
我们先说说UNIX下的命令,一条命令分为命令名、选项和操作数,例如命令"ls-l/usr",其中,ls是命令名,l是选项,/usr是操作数,后两项都是可选项,根据实际情况而定。UNIX命令一定遵守以下几个规则:
● 命令名为小写字母。
● 命令名、选项、操作数之间以空格分隔,空格数量不受限制。
● 选项之间可以组合使用,也可以单独拆分使用。
● 选项以横杠(-)开头。
在UNIX世界中,我们最常用的就是ls这个命令,它用于显示目录或文件信息,下面我们先来看看这个命令。常用的有以下几条组合命令:
● ls:简单列出一个目录下的文件。
● ls-l:详细列出目录下的文件。
● ls-a:列出目录下包含的隐藏文件,主要是点号(.)开头的文件。
● ls-s:列出文件的大小。
除此之外,还有一些非常常用的组合命令,如"ls-la"、"ls-ls"等。ls命令名确定了,但是其后连接的选项和操作数是不确定的。操作数我们不用关心它,每个命令必然有一个操作数,若没有则是当前的目录。问题的关键是选项,用哪个选项以及什么时候使用都是由用户决定的,也就是从设计上考虑。设计者需要完全解析所有的参数,需要很多个类来处理如此多的选项,客户输入一个参数,立刻返回一个结果。针对一个ls命令族,要求如下:
● 每一个ls命令都有操作数,默认操作数为当前目录。
● 选项不可重复,例如对于"ls-l-l-s",解析出的选项应该只有两个:l选项和s选项。
● 每个选项返回不同的结果,也就是说每个选项应该由不同的业务逻辑来处理。
● 为提高扩展性,ls命令族内的运算应该是对外封闭的,减少外界访问ls命令族内部细节的可能性。
针对一个命令族的分析结果,我们可以使用什么模式?责任链模式!对,只要把一个参数传递到链首,就可以立刻获得一个结果,中间是如何传递的以及由哪个逻辑解析都不需要外界(高层)模块关心,该模块的类图如图34-2所示。
![](https://box.kancloud.cn/2016-08-14_57b0036fb751c.jpg)
图34-2 命令族的解析类图
类图还是比较清晰的,UNIX的命令有上百个,我们定义一个CommandName抽象类,所有的命令都继承于该类,它就是责任链模式的handler类,负责链表控制;每个命令族都有一个独立的抽象类,因为每个命令族都有其独特的个性,比如ls命令和df命令,其后可加的参数是不一样的,这就可以在抽象类AbstractLS中定义,而且它还有标示作用,标示其下的实现类都是实现ls命令的,只是命令的选项不同;Context负责建立一条命令的链表,比如ls命令族、df命令族等,它组装出一个处理一个命令族的责任链,并返回首节点供高层模块调用,这是非常典型的责任链模式。
分析完毕一个具体的命令族,已经确定可以采用责任链模式,我们继续往下分析。UNIX命令非常多,敲一个命令返回一个结果,每个具体的命令可以由相关的命令族(也就是责任链)来解析,但是如此多的命令还是需要有一个派发的角色,输入一个命令,不管后台谁来解析,返回一个结果就成,这就要用到命令模式。命令模式负责协调各个命令正确地传递到各个责任链的首节点,这就是它的任务,其类图如图34-3所示。
![](https://box.kancloud.cn/2016-08-14_57b0036fccb28.jpg)
图34-3 命令传递类图
是不是典型的命令模式类图?其中Chain是一个标示符,表示的就是我们上面分析的责任链,每一个具体的命令负责调用责任链的首节点,获得返回值,结束命令的执行。两个核心模块都分析完毕了,就可以把类图融合在一起,完整的类图如图34-4所示。
这个类图还是比较简单的,我们来看一下各个类的职责。
● ClassUtils
ClassUtils是工具类,其主要职责是根据一个接口、父类查找到所有的子类。在不考虑效率的应用中,使用该类可以带来非常好的扩展性。
● CommandVO
CommandVO是命令的值对象,它把一个命令解析为命令名、选项、操作数,例如"ls-l/usr"命令分别解析为getCommandName、getParam、getData三个方法的返回值。
● CommandEnum
CommandEnum是枚举类型,是主要的命令配置文件。为什么需要枚举类型?这是JDK 1.5提供的一个非常好的功能,我们在程序中再讲解如何使用它。
所有的分析都已经完成了,我们来看看程序。程序不复杂,看看类图,应该先写命令的解释,这是项目的核心。我们先来看CommandName抽象类,如代码清单34-1所示。
![](https://box.kancloud.cn/2016-08-14_57b00370022dd.jpg)
图34-4 完整类图
代码清单34-1 抽象命令名类
public abstract class CommandName {
private CommandName nextOperator;
public final String handleMessage(CommandVO vo){
//处理结果
String result = "";
//判断是否是自己处理的参数
if(vo.getParam().size() == 0 || vo.getParam().contains (this.getOperateParam())){
result = this.echo(vo);
}else{
if(this.nextOperator !=null){
result = this.nextOperator.handleMessage(vo);
}else{
result = "命令无法执行";
}
}
return result;
}
//设置剩余参数由谁来处理
public void setNext(CommandName _operator){
this.nextOperator = _operator;
}
//每个处理者都要处理一个后缀参数
protected abstract String getOperateParam();
//每个处理者都必须实现处理任务
protected abstract String echo(CommandVO vo);
}
很简单,就是责任链模式中的handler,也就是中控程序,控制一个链应该如何建立。我们再来看3个ls命令族,先看AbstractLS抽象类,如代码清单34-2所示。
代码清单34-2 抽象ls命令
public abstract class AbstractLS extends CommandName{
//默认参数
public final static String DEFAULT_PARAM = "";
//参数a
public final static String A_PARAM ="a";
//参数l
public final static String L_PARAM = "l";
}
很惊讶,是吗?怎么是个空的抽象类?是的,确实是一个空类,只定义了3个参数名称,它有两个职责:
● 标记ls命令族。
● 个性化处理。
因为现在还没有思考清楚ls有什么个性(可以把命令的选项也认为是其个性化数据),所以先写个空类放在这里,以后想清楚了再填写上去,留下一些可扩展的类也许会给未来带来不可估量的优点。
我们再来看ls不带任何参数的命令处理,如代码清单34-3所示。
代码清单34-3 ls命令
public class LS extends AbstractLS {
//最简单的ls命令
protected String echo(CommandVO vo) {
return FileManager.ls(vo.formatData());
}
//参数为空
protected String getOperateParam() {
return super.DEFAULT_PARAM;
}
}
太简单了,首先定义了自己能处理什么样的参数,即只能处理不带参数的ls命令,getOperateParam返回一个长度为零的字符串,就是说该类作为链上的一个节点,只处理没有参数的ls命令。echo方法是执行ls命令,通过调用操作系统相关的命令返回结果。我们再来看ls -l命令,如代码清单34-4所示。
代码清单34-4 ls-l命令
public class LS_L extends AbstractLS {
protected String echo(CommandVO vo) {
return FileManager.ls_l(vo.formatData());
}
//l选项
protected String getOperateParam() {
return super.L_PARAM;
}
}
该类只处理选项为"l"的命令,也非常简单。ls-a命令的处理与此类似,如代码清单34-5所示。
代码清单34-5 ls-a命令
public class LS_A extends AbstractLS {
//ls -a命令
protected String echo(CommandVO vo) {
return FileManager.ls_a(vo.formatData());
}
protected String getOperateParam() {
return super.A_PARAM;
}
}
这3个实现类都关联到了FileManager,这个类有什么用呢?它是负责与操作系统交互的。要把UNIX的命令迁移到Windows上运行,就需要调用Windows的低层函数,实现起来较复杂,而且和我们本章要讲的内容没有太大关系,所以这里采用示例性代码代替,如代码清单34-6所示。
代码清单34-6 文件管理类
public class FileManager {
//ls命令
public static String ls(String path){
return "file1\nfile2\nfile3\nfile4";
}
//ls -l命令
public static String ls_l(String path){
String str = "drw-rw-rw root system 1024 2009-8-20 10:23 file1\n";
str = str + "drw-rw-rw root system 1024 2009-8-20 10:23 file2\n";
str = str + "drw-rw-rw root system 1024 2009-8-20 10:23 file3";
return str;
}
//ls -a命令
public static String ls_a(String path){
String str = ".\n..\nfile1\nfile2\nfile3";
return str;
}
}
以上都是比较简单的方法,大家有兴趣可以自己实现一下,以下提供3种思路:
● 通过java.io.File类自己封装出类似UNIX的返回格式。
● 通过java.lang.Runtime类的exec方法执行dos的dir命令,产生类似的ls结果。
● 通过JNI(Java Native Interface)来调用与操作系统有关的动态链接库,当然前提是需要自己写一个动态链接库文件。
3个具体的命令都已经解析完毕,我们再来看看如何建立一条处理链,由于建链的任务已经移植到抽象命令类,我们就先来看抽象类Command,如代码清单34-7所示。
代码清单34-7 抽象命令
public abstract class Command {
public abstract String execute(CommandVO vo);
//建立链表
protected final List<? extends CommandName> buildChain(Class<? extends CommandName> abstractClass){
//取出所有的命令名下的子类
List<Class> classes = ClassUtils.getSonClass(abstractClass);
//存放命令的实例,并建立链表关系
List<CommandName> commandNameList = new ArrayList<CommandName>();
for(Class c:classes){
CommandName commandName =null;
try {
//产生实例
commandName = (CommandName)Class.forName (c.getName()) .newInstance();
} catch (Exception e){
// TODO 异常处理
}
//建立链表
if(commandNameList.size()>0){
commandNameList.get(commandNameList.size()-1).setNext (commandName);
}
commandNameList.add(commandName);
}
return commandNameList;
}
}
Command抽象类有两个作用:一是定义命令的执行方法,二是负责命令族(责任链)的建立。其中buildChain方法负责建立一个责任链,它通过接收一个抽象的命令族类就可以建立一条命令解析链,如传递AbstarctLS类就可以建立一条解析ls命令族的责任链,请读者注意如下这句代码:
commandName = (CommandName)Class.forName(c.getName()).newInstance();
在一个遍历中,类中的每个元素都是一个类名,然后根据类名产生一个实例,它会抛出异常,例如类文件不存在、初始化失败等,读者在设计时要实现该部分的异常。我们再来想一下,每个实现类的类名是如何取得的呢?看下面这句代码:
List<Class> classes = ClassUtils.getSonClass(abstractClass);
根据一个父类取得所有子类,是一个非常好的工具类,其实现如代码清单34-8所示。
代码清单34-8 根据父类获得子类
public class ClassUtils {
//根据父类查找到所有的子类,默认情况是子类和父类都在同一个包名下
public static List<Class> getSonClass(Class fatherClass){
//定义一个返回值
List<Class> returnClassList = new ArrayList<Class>();
//获得包名称
String packageName = fatherClass.getPackage().getName();
//获得包中的所有类
List<Class> packClasses = getClasses(packageName);
//判断是否是子类
for(Class c:packClasses){
if(fatherClass.isAssignableFrom(c) && !fatherClass.equals(c)){
returnClassList.add(c);
}
}
return returnClassList;
}
//从一个包中查找出所有的类,在jar包中不能查找
private static List<Class> getClasses(String packageName) {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
String path = packageName.replace('.', '/');
Enumeration<URL> resources = null;
try {
resources = classLoader.getResources(path);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes;
}
private static List<Class> findClasses(File directory, String packageName) {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
try {
classes.add(Class.forName(packageName + '.' + file.getName() .substring(0, file.getName().length() - 6)));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
return classes;
}
}
这个类请大家谨慎使用,在核心的应用中尽量不要使用该工具,它会严重影响性能。
再来看LSCommand类的实现,如代码清单34-9所示。
代码清单34-9 具体的ls命令
public class LSCommand extends Command{
public String execute(CommandVO vo){
//返回链表的首节点
CommandName firstNode = super.buildChain(AbstractLS.class).get(0);
return firstNode.handleMessage(vo);
}
}
很简单的方法,先建立一个命令族的责任链,然后找到首节点调用。在该类中我们使用CommandVO类,它是一个封装对象,其代码如代码清单34-10所示。
代码清单34-10 命令对象
public class CommandVO {
//定义参数名与参数的分隔符号,一般是空格
public final static String DIVIDE_FLAG =" ";
//定义参数前的符号,Unix一般是-,如ls -la
public final static String PREFIX="-";
//命令名,如ls、du
private String commandName = "";
//参数列表
private ArrayList<String> paramList = new ArrayList<String>();
//操作数列表
private ArrayList<String> dataList = new ArrayList<String>();
//通过构造函数传递进来命令
public CommandVO(String commandStr){
//常规判断
if(commandStr != null && commandStr.length() !=0){
//根据分隔符号拆分出执行符号
String[] complexStr = commandStr.split(CommandVO.DIVIDE_FLAG);
//第一个参数是执行符号
this.commandName = complexStr[0];
//把参数放到List中
for(int i=1;i<complexStr.length;i++){
String str = complexStr[i];
//包含前缀符号,认为是参数
if(str.indexOf(CommandVO.PREFIX)==0){
this.paramList.add(str.replace
(CommandVO.PREFIX, "").trim());
}else{
this.dataList.add(str.trim());
}
}
}else{
//传递的命令错误
System.out.println("命令解析失败,必须传递一个命令才能执行!");
}
}
//得到命令名
public String getCommandName(){
return this.commandName;
}
//获得参数
public ArrayList<String> getParam(){
//为了方便处理空参数
if(this.paramList.size() ==0){
this.paramList.add("");
}
return new ArrayList(new HashSet(this.paramList));
}
//获得操作数
public ArrayList<String> getData(){
return this.dataList;
}
}
CommandVO解析一个命令,规定一个命令必须有3项:命令名、选项、操作数。如果没有呢?那就以长度为零的字符串代替,通过这样的一个约定可以大大降低命令解析的开发工作。注意getParam参数中的返回值:
new ArrayList(new HashSet(this.paramList));
为什么要这么处理?HashSet具有值唯一的优点,这样处理就是为了避免出现两个相同的参数,比如对于"ls-l-l-s"这样的命令,通过getParam返回的参数是几个呢?回答是两个:l选项和s选项。
我们再来看Invoker类,它是负责命令分发的类,如代码清单34-11所示。
代码清单34-11 命令分发
public class Invoker {
//执行命令
public String exec(String _commandStr){
//定义返回值
String result = "";
//首先解析命令
CommandVO vo = new CommandVO(_commandStr);
//检查是否支持该命令
if(CommandEnum.getNames().contains(vo.getCommandName())){
//产生命令对象
String className = CommandEnum.valueOf (vo.getCommandName()) .getValue();
Command command;
try {
command = (Command)Class.forName(className).newInstance();
result = command.execute(vo);
}catch(Exception e){
// TODO 异常处理
}
}else{
result = "无法执行命令,请检查命令格式";
}
return result;
}
}
实现也是比较简单的,从CommandEnum中获得命令与命令类的配置信息,然后建立一个命令实例,调用其execute方法,完成命令的执行操作。CommandEnum类是一个枚举类型,如代码清单34-12所示。
代码清单34-12 命令配置对象
public enum CommandEnum {
ls("com.cbf4life.common.command.LSCommand");
private String value = "";
//定义构造函数,目的是Data(value)类型的相匹配
private CommandEnum(String value){
this.value = value;
}
public String getValue(){
return this.value;
}
//返回所有的enum对象
public static List<String> getNames(){
CommandEnum[] commandEnum = CommandEnum.values();
List<String> names = new ArrayList<String>();
for(CommandEnum c:commandEnum){
names.add(c.name());
}
return names;
}
}
为什么要用枚举类型?用一个接口来管理也是很容易实现的。注意CommandEnum中的构造函数CommandEnum(String value)和getValue类,没有新建一个Enum对象,但是可以直接使用CommandEnum.ls.getValue方法获得值,这就是Enum类型的独特地方。再看下面:
ls("com.cbf4life.common.command.LSCommand");
是不是很特别?是的,枚举的基本功能就是定义默认可选值,但是Java中的枚举功能又增强了很多,可以添加方法和属性,基本上就是一个特殊的类。若要详细了解Enum,读者可以翻阅一下相关语法书。
现在剩下的工作就是写一个Client类,然后看看运行情况如何,如代码清单34-13所示。
代码清单34-13 场景类
public class Client {
public static void main(String[] args) throws IOException {
Invoker invoker = new Invoker();
while(true){
//UNIX下的默认提示符号
System.out.print("#");
//捕获输出
String input = (new BufferedReader(new InputStreamReader (System.in))).readLine();
//输入quit或exit则退出
if(input.equals("quit") || input.equals("exit")){
return;
}
System.out.println(invoker.exec(input));
}
}
}
Client也很简单,通过一个while循环允许使用者持续输入,然后打印出返回值,运行结果如下:
#ls
file1
file2
file3
file4
#ls -l
drw-rw-rw root system 1024 2009-8-20 10:23 file1
drw-rw-rw root system 1024 2009-8-20 10:23 file2
drw-rw-rw root system 1024 2009-8-20 10:23 file3
#ls -a
.
..
file1
file2
file3
#quit
我们已经实现了在Windows下操作UNIX命令的功能,但是仅仅一个ls命令族是不够的,我们要扩展,把一百多个命令都扩展出来,怎么扩展呢?现在增加一个df命令族,显示磁盘的大小,只要增加类图就成,如图34-5所示。
![](https://box.kancloud.cn/2016-08-14_57b003701921f.jpg)
图34-5 扩展df命令后的类图
仅仅增加了粗框的部分,也就是增加DFCommand、AbstractDF以及实现类就可以完成扩展功能。先看AbstractDF代码,如代码清单34-14所示。
代码清单34-14 df命令的抽象类
public abstract class AbstractDF extends CommandName {
//默认参数
public final static String DEFAULT_PARAM = "";
//参数k
public final static String K_PARAM = "k";
//参数g
public final static String G_PARAM = "g";
}
与前面一样的功能,定义选项名称。接下来是三个实现类,都非常简单,如代码清单34-15所示。
代码清单34-15 df命令的具体实现类
public class DF extends AbstractDF{
//定义一下自己能处理什么参数
protected String getOperateParam() {
return super.DEFAULT_PARAM;
}
//命令处理
protected String echo(CommandVO vo) {
return DiskManager.df();
}
}
public class DF_K extends AbstractDF{
//定义一下自己能处理什么参数
protected String getOperateParam() {
return super.K_PARAM;
}
//命令处理
protected String echo(CommandVO vo) {
return DiskManager.df_k();
}
}
public class DF_G extends AbstractDF{
//定义一下自己能处理什么参数
protected String getOperateParam() {
return super.G_PARAM;
}
//命令处理
protected String echo(CommandVO vo) {
return DiskManager.df_g();
}
}
每个选项的实现类都定义了自己能解析什么命令,然后通过echo方法返回执行结果。在三个实现类中都与DiskManager类有关联关系,该类负责与操作系统有关的功能,是必须要实现的,其示例代码如代码清单34-16所示。
代码清单34-16 磁盘管理
public class DiskManager {
//默认的计算大小
public static String df(){
return "/\t10485760\n/usr\t104857600\n/home\t1048576000\n";
}
//按照kb来计算
public static String df_k(){
return "/\t10240\n/usr\t102400\n/home\tt10240000\n";
}
//按照gb计算
public static String df_g(){
return "/\t10\n/usr\t100\n/home\tt10000\n";
}
}
以上为示例代码,若要实际计算磁盘大小,可以使用JNI的方式或者执行操作系统的命令的方式获得,特别是JDK 1.6提供了获得一个root目录大小的方法。
然后再增加一个DFCommand命令,负责执行命令,如代码清单34-17所示。
代码清单34-17 可执行的df命令
public class DFCommand extends Command {
public String execute(CommandVO vo) {
return super.buildChain(AbstractDF.class).get(0).handleMessage(vo);
}
}
最后一步,修改一下CommandEnum配置,增加一个枚举项,如代码清单34-18所示。
代码清单34-18 增加后的枚举项
public enum CommandEnum {
ls("com.cbf4life.common.command.LSCommand"),
df("com.cbf4life.common.command.DFCommand");
private String value = "";
//定义构造函数,目的是Data(value)类型的相匹配
private CommandEnum(String value){
this.value = value;
}
public String getValue(){
return this.value;
}
//返回所有的enum对象
public static List<String> getNames(){
CommandEnum[] commandEnum = CommandEnum.values();
List<String> names = new ArrayList<String>();
for(CommandEnum c:commandEnum){
names.add(c.name());
}
return names;
}
}
运行结果如下所示:
#ls
file1
file2
file3
file4
#df
/ 10485760
/usr 104857600
/home 1048576000
#df -k
/ 10240
/usr 102400
/home t10240000
#df -g
/ 10
/usr 100
/home t10000
#
仅仅增加类就完成了变更,这才是我们要的结果:对修改关闭,对扩展开放。
- 前言
- 第一部分 大旗不挥,谁敢冲锋——6大设计原则全新解读
- 第1章 单一职责原则
- 1.2 绝杀技,打破你的传统思维
- 1.3 我单纯,所以我快乐
- 1.4 最佳实践
- 第2章 里氏替换原则
- 2.2 纠纷不断,规则压制
- 2.3 最佳实践
- 第3章 依赖倒置原则
- 3.2 言而无信,你太需要契约
- 3.3 依赖的三种写法
- 3.4 最佳实践
- 第4章 接口隔离原则
- 4.2 美女何其多,观点各不同
- 4.3 保证接口的纯洁性
- 4.4 最佳实践
- 第5章 迪米特法则
- 5.2 我的知识你知道得越少越好
- 5.3 最佳实践
- 第6章 开闭原则
- 6.2 开闭原则的庐山真面目
- 6.3 为什么要采用开闭原则
- 6.4 如何使用开闭原则
- 6.5 最佳实践
- 第二部分 真刀实枪 ——23种设计模式完美演绎
- 第7章 单例模式
- 7.2 单例模式的定义
- 7.3 单例模式的应用
- 7.4 单例模式的扩展
- 7.5 最佳实践
- 第8章 工厂方法模式
- 8.2 工厂方法模式的定义
- 8.3 工厂方法模式的应用
- 8.4 工厂方法模式的扩展
- 8.5 最佳实践
- 第9章 抽象工厂模式
- 9.2 抽象工厂模式的定义
- 9.3 抽象工厂模式的应用
- 9.4 最佳实践
- 第10章 模板方法模式
- 10.2 模板方法模式的定义
- 10.3 模板方法模式的应用
- 10.4 模板方法模式的扩展
- 10.5 最佳实践
- 第11章 建造者模式
- 11.2 建造者模式的定义
- 11.3 建造者模式的应用
- 11.4 建造者模式的扩展
- 11.5 最佳实践
- 第12章 代理模式
- 12.2 代理模式的定义
- 12.3 代理模式的应用
- 12.4 代理模式的扩展
- 12.5 最佳实践
- 第13章 原型模式
- 13.2 原型模式的定义
- 13.3 原型模式的应用
- 13.4 原型模式的注意事项
- 13.5 最佳实践
- 第14章 中介者模式
- 14.2 中介者模式的定义
- 14.3 中介者模式的应用
- 14.4 中介者模式的实际应用
- 14.5 最佳实践
- 第15章 命令模式
- 15.2 命令模式的定义
- 15.3 命令模式的应用
- 15.4 命令模式的扩展
- 15.5 最佳实践
- 第16章 责任链模式
- 16.2 责任链模式的定义
- 16.3 责任链模式的应用
- 16.4 最佳实践
- 第17章 装饰模式
- 17.2 装饰模式的定义
- 17.3 装饰模式应用
- 17.4 最佳实践
- 第18章 策略模式
- 18.2 策略模式的定义
- 18.3 策略模式的应用
- 18.4 策略模式的扩展
- 18.5 最佳实践
- 第19章 适配器模式
- 19.2 适配器模式的定义
- 19.3 适配器模式的应用
- 19.4 适配器模式的扩展
- 19.5 最佳实践
- 第20章 迭代器模式
- 20.2 迭代器模式的定义
- 20.3 迭代器模式的应用
- 20.4 最佳实践
- 第21章 组合模式
- 21.2 组合模式的定义
- 21.3 组合模式的应用
- 21.4 组合模式的扩展
- 21.5 最佳实践
- 第22章 观察者模式
- 22.2 观察者模式的定义
- 22.3 观察者模式的应用
- 22.4 观察者模式的扩展
- 22.5 最佳实践
- 第23章 门面模式
- 23.2 门面模式的定义
- 23.3 门面模式的应用
- 23.4 门面模式的注意事项
- 23.5 最佳实践
- 第24章 备忘录模式
- 24.2 备忘录模式的定义
- 24.3 备忘录模式的应用
- 24.4 备忘录模式的扩展
- 24.5 最佳实践
- 第25章 访问者模式
- 25.2 访问者模式的定义
- 25.3 访问者模式的应用
- 25.4 访问者模式的扩展
- 25.5 最佳实践
- 第26章 状态模式
- 26.2 状态模式的定义
- 26.3 状态模式的应用
- 第27章 解释器模式
- 27.2 解释器模式的定义
- 27.3 解释器模式的应用
- 27.4 最佳实践
- 第28章 享元模式
- 28.2 享元模式的定义
- 28.3 享元模式的应用
- 28.4 享元模式的扩展
- 28.5 最佳实践
- 第29章 桥梁模式
- 29.2 桥梁模式的定义
- 29.3 桥梁模式的应用
- 29.4 最佳实践
- 第三部分 谁的地盘谁做主 ——设计模式PK
- 第30章 创建类模式大PK
- 30.1 工厂方法模式VS建造者模式
- 30.2 抽象工厂模式VS建造者模式
- 第31章 结构类模式大PK
- 31.1 代理模式VS装饰模式
- 31.2 装饰模式VS适配器模式
- 第32章 行为类模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS状态模式
- 32.3 观察者模式VS责任链模式
- 第33章 跨战区PK
- 33.1 策略模式VS桥梁模式
- 33.2 门面模式VS中介者模式
- 33.3 包装模式群PK
- 第四部分 完美世界 ——设计模式混编
- 第34章 命令模式+责任链模式
- 34.2 混编小结
- 第35章 工厂方法模式+策略模式
- 35.2 混编小结
- 第36章 观察者模式+中介者模式
- 36.2 混编小结
- 第五部分 扩展篇
- 第37章 MVC框架
- 37.2 最佳实践
- 第38章 新模式
- 38.1 规格模式
- 38.2 对象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空对象模式
- 附录 23种设计模式彩图