细数23种设计模式以及Java代码实现
设计模式是在软件开发中,经过验证的,用于解决在特定环境下、重复出现的、特定问题的解决方案。
创建型
创建型模式是抽象对象实例化的过程,用于帮助创建对象的实例。
工厂模式
简单工厂
描述
定义:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。简单工厂方法的功能是选择合适的实现类并创建。本质是选择实现。简单工厂也称为静态工厂,可以把简单工厂类实现成一个工具类,直接提供静态创建对象的方法。
简单工厂也称为万能工厂,一个简单工厂理论上可以构造任何对象,因此又称为万能工厂。可配置的简单工厂,使用反射加上配置文件,实现添加新的实现类后,无须修改代码,即可把新的实现类加入应用中进行使用。
结构
角色 | 说明 |
---|---|
Api | 定义客户所需要的功能接口 |
Impl | Api的实现类,可能有多个 |
Factory | 工厂,选择合适的实现类来创建Api接口对象 |
Client | 客户端,通过Factory获取Api接口对象,面向Api接口编程 |
命名
- 类名为“模块名称+Factory”,如:用户模块的工厂命名为UserFactory。
- 方法名称为“get+接口名称”或“create+接口名称”。
优缺点
优点
- 帮助封装
- 解耦
缺点
- 可能增加客户端的复杂度
- 不方便扩展子工厂
使用场景
1.完全封装隔离具体实现,使外部接口只能通过接口操作封装体。
2.将对外创建对象的职责集中管理和控制。
示例
//示例:静态工厂
//Client角色
/*** 客户端类* @author liaocan**/
public class Client {public static void main(String[] args) {// 用户不需知道具体的实现(隔离封装、面向接口编程)Api api = Factory.createApi();api.hello("man");}}
//Factory角色
/*** 简单工厂类* 范围:独立的组件或独立的模块级别* 类名:“模块名称+Factory”* 方法名:“get+接口名称”或“create+接口名称”* @author liaocan**/
public class Factory {private Factory() {}public static Api createApi() {// 主要实现选择合适的实现类创建实例对象return new Impl1();} public static Api createApi(int type) { // 缺点,暴露参数的含义// 主要实现选择合适的实现类创建实例对象// 根据type进行选择,1和2应为常量Api api = null;if (type==1) {api = new Impl1();} else if (type==2) {api = new Impl2();}// 参数来源// 1:client// 2:配置文件// 3:系统自身,比如运行期间的某个值return api;} }
//Api角色
/*** 接口* @author liaocan**/
public interface Api {void hello(String name);}
//Impl角色Impl1
/*** 实现类* @author liaocan**/
public class Impl1 implements Api {public void hello(String name) {System.out.println("hello "+name);}}
//Impl角色Impl2
/*** 实现类* @author liaocan**/
public class Impl2 implements Api {public void hello(String name) {System.out.println("hello "+name);}}
工厂方法
描述
定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式的本质是延迟到子类来选择实现。
结构
角色 | 说明 |
---|---|
Product | 定义工厂方法所创建的对象的接口,即实际需要使用的对象的接口 |
ConcreteProduct | Product接口的实现类 |
Creator | 抽象的创建器类,声明工厂方法 |
ConcreteCreator | 具体的创建器类,覆盖实现Creator定义的工厂方法,返回具体的Product实例 |
可以使用工厂方法模式连接平行的类层次。
优缺点
优点
- 可以在不知具体实现的情况下编程
- 更容易扩展对象的新版本
- 连接平行的类层次
缺点
- 具体产品对象和工厂方法耦合
使用场景
- 明确接口但不知具体的实现时,可以使用工厂方法对象将创建Product对象的工作延迟到子类实现
- 一个类本身期望由其子类创建所需对象
示例
//导出数据
//示例:导出数据到各种文件,如:数据库备份文件和TXT文件
//Client
public class Client {public static void main(String[] args) {/*//创建需要使用的Creator对象ExportOperate operate = new ExportDBOperate();//调用输出数据的功能方法operate.export("测试数据");*/// 当有默认实现时ExportOperate operate = new ExportOperate();operate.export("测试数据");operate = new ExportDBOperate();operate.export("测试数据");}
}
//Creator角色,导出的操作类
/*** 实现导出数据的业务功能对象*/
public /*abstract*/ class ExportOperate {/*** 导出文件* @param data 需要保存的数据* @return 是否成功导出文件*/public boolean export(String data){//使用工厂方法ExportFileApi api = factoryMethod();return api.export(data);}/*** 工厂方法,创建导出的文件对象的接口对象* @return 导出的文件对象的接口对象*///protected abstract ExportFileApi factoryMethod();protected ExportFileApi factoryMethod() {return new ExportTxtFile();}
}
//ConcreteCreator角色,导出到数据库备份文件的类
/*** 具体的创建器实现对象,实现创建导出成数据库备份文件形式的对象*/
public class ExportDBOperate extends ExportOperate{protected ExportFileApi factoryMethod() {//创建导出成数据库备份文件形式的对象return new ExportDB();}
}
//ConcreteCreator角色,导出到TXT文件的类
/*** 具体的创建器实现对象,实现创建导出成文本文件格式的对象*/
public class ExportTxtFileOperate extends ExportOperate{protected ExportFileApi factoryMethod() {//创建导出成文本文件格式的对象return new ExportTxtFile();}}
//Product角色,导出数据到文件的接口
/*** 导出的文件对象的接口*/
public interface ExportFileApi {/*** 导出内容成为文件* @param data 示意:需要保存的数据* @return 是否导出成功*/public boolean export(String data);
}
//ConcreteProduct角色,导出数据到数据库备份文件的类
/*** 导出成数据库备份文件形式的对象*/
public class ExportDB implements ExportFileApi{public boolean export(String data) {//简单示意一下,这里需要操作数据库和文件System.out.println("导出数据"+data+"到数据库备份文件");return true;}
}
//ConcreteProduct角色,导出数据到TXT文件的类
/*** 导出成文本文件格式的对象*/
public class ExportTxtFile implements ExportFileApi{public boolean export(String data) {//简单示意一下,这里需要操作文件System.out.println("导出数据"+data+"到文本文件");return true;}
}
抽象工厂
描述
定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。本质是选择产品簇的实现。
结构
角色 | 说明 |
---|---|
AbstractFactory | 抽象工厂,定义创建一系列产品对象的操作接口 |
ConcreteFactory | 具体工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建 |
AbstractProduct | 定义一类产品对象的接口 |
ConcreteProduct | 具体的产品实现对象 |
Client | 客户端 |
优缺点
优点
- 分离接口和实现
- 容易切换产品簇
缺点
- 不容易扩展新的产品
- 容易造成类层次复杂
使用场景
- 一个系统只知道产品的接口而不关心实现时
- 需要动态切换产品簇时
- 需要强调一系列相关产品的接口,以便联合使用它们时
示例
//示例:组装电脑//Client
public class Client {public static void main(String[] args) {//创建装机工程师对象ComputerEngineer engineer = new ComputerEngineer();//客户选择并创建需要使用的装机方案对象AbstractFactory schema = new Schema1();//告诉装机工程师自己选择的装机方案,让装机工程师组装电脑engineer.makeComputer(schema);//客户选择并创建需要使用的装机方案对象schema = new Schema2();//告诉装机工程师自己选择的装机方案,让装机工程师组装电脑engineer.makeComputer(schema);}
}
//装机工程师类
/*** 装机工程师的类*/
public class ComputerEngineer {/*** 定义组装机器需要的CPU*/private CPUApi cpu= null;/*** 定义组装机器需要的主板*/private MainboardApi mainboard = null;/*** 装机过程* @param schema 客户选择的装机方案*/public void makeComputer(AbstractFactory schema){//1:首先准备好装机所需要的配件prepareHardwares(schema);//2:组装机器//3:测试机器//4:交付客户}/*** 准备装机所需要的配件* @param schema 客户选择的装机方案*/private void prepareHardwares(AbstractFactory schema){//这里要去准备CPU和主板的具体实现,为了示例简单,这里只准备这两个//可是,装机工程师并不知道如何去创建,怎么办呢?//使用抽象工厂来获取相应的接口对象this.cpu = schema.createCPUApi();this.mainboard = schema.createMainboardApi();//测试一下配件是否好用this.cpu.calculate();this.mainboard.installCPU();}
}
//抽象工厂类
/*** 抽象工厂的接口,声明创建抽象产品对象的操作*/
public interface AbstractFactory {/*** 创建CPU的对象* @return CPU的对象*/public CPUApi createCPUApi();/*** 创建主板的对象* @return 主板的对象*/public MainboardApi createMainboardApi();
}
//抽象工厂实现类1
/*** 装机方案一:Intel 的CPU + 技嘉的主板* 这里创建CPU和主板对象的时候,是对应的,能匹配上的*/
public class Schema1 implements AbstractFactory{public CPUApi createCPUApi() {return new IntelCPU(1156);}public MainboardApi createMainboardApi() {return new GAMainboard(1156);}
}
//抽象工厂实现类2
/*** 装机方案二:AMD的CPU + 微星的主板* 这里创建CPU和主板对象的时候,是对应的,能匹配上的*/
public class Schema2 implements AbstractFactory{public CPUApi createCPUApi() {return new AMDCPU(939);}public MainboardApi createMainboardApi() {return new MSIMainboard(939);}
}
//CPU接口
/*** CPU的接口*/
public interface CPUApi {/*** 示意方法,CPU具有运算的功能*/public void calculate();
}
//CPU实现类1
/***Intel的CPU实现*/
public class IntelCPU implements CPUApi{/*** CPU的针脚数目*/private int pins = 0;/*** 构造方法,传入CPU的针脚数目* @param pins CPU的针脚数目*/public IntelCPU(int pins){this.pins = pins;}public void calculate() {System.out.println("now in Intel CPU,pins="+pins);}
}
//CPU实现类2
/*** AMD的CPU实现*/
public class AMDCPU implements CPUApi{/*** CPU的针脚数目*/private int pins = 0;/*** 构造方法,传入CPU的针脚数目* @param pins CPU的针脚数目*/public AMDCPU(int pins){this.pins = pins;}public void calculate() {System.out.println("now in AMD CPU,pins="+pins);}
}
//主板接口
/*** 主板的接口*/
public interface MainboardApi {/*** 示意方法,主板都具有安装CPU的功能*/public void installCPU();
}
//主板实现类1
/*** 技嘉的主板 */
public class GAMainboard implements MainboardApi {/*** CPU插槽的孔数*/private int cpuHoles = 0;/*** 构造方法,传入CPU插槽的孔数* @param cpuHoles CPU插槽的孔数*/public GAMainboard(int cpuHoles){this.cpuHoles = cpuHoles;}public void installCPU() {System.out.println("now in GAMainboard,cpuHoles="+cpuHoles);}
}
//主板实现类2
/*** 微星的主板*/
public class MSIMainboard implements MainboardApi{/*** CPU插槽的孔数*/private int cpuHoles = 0;/*** 构造方法,传入CPU插槽的孔数* @param cpuHoles CPU插槽的孔数*/public MSIMainboard(int cpuHoles){this.cpuHoles = cpuHoles;}public void installCPU() {System.out.println("now in MSIMainboard,cpuHoles="+cpuHoles);}
}
单例模式
描述
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。本质是控制实例的数目。
结构
角色 | 说明 |
---|---|
Singleton | 负责创建Singleton类自身的唯一实例,并提供一个可以被外部调用的getInstance的方法 |
优缺点
优点
- 懒汉式是时间换空间
- 饿汉式是空间换时间
缺点
- 双重检查加锁的懒汉式需要确保线程安全
使用场景
- 需控制一个类的实例只有一个且客户只能从一个全局访问点访问其时
示例
//控制类的实例个数(未考虑线程安全)
/*** 简单演示如何扩展单例模式,控制实例数目为3个 */
public class OneExtend {/*** 定义一个缺省的key值的前缀*/private final static String DEFAULT_PREKEY = "Cache";/*** 缓存实例的容器*///实例调度的问题private static Map<String,OneExtend> map = new HashMap<String,OneExtend>();/*** 用来记录当前正在使用第几个实例,到了控制的最大数目,就返回从1开始*/private static int num = 1;/*** 定义控制实例的最大数目*/private final static int NUM_MAX = 3; private OneExtend(){}public static OneExtend getInstance(){String key = DEFAULT_PREKEY+num;OneExtend oneExtend = map.get(key);if(oneExtend==null){oneExtend = new OneExtend();map.put(key, oneExtend);}//把当前实例的序号加1num++;if(num > NUM_MAX){//如果实例的序号已经达到最大数目了,那就重复从1开始获取num = 1;}return oneExtend; }public static void main(String[] args) {OneExtend t1 = getInstance();OneExtend t2 = getInstance();OneExtend t3 = getInstance();OneExtend t4 = getInstance();OneExtend t5 = getInstance();OneExtend t6 = getInstance();System.out.println("t1=="+t1);System.out.println("t2=="+t2);System.out.println("t3=="+t3);System.out.println("t4=="+t4);System.out.println("t5=="+t5);System.out.println("t6=="+t6);}
}//饿汉式
//示例:饿汉式单例(以空间换时间)
/*** 饿汉式单例实现的示例*/
public class Singleton {/*** 定义一个变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次*/private final static Singleton uniqueInstance = new Singleton();/*** 私有化构造方法,好在内部控制创建实例的数目*/private Singleton(){//}/*** 定义一个方法来为客户端提供类实例* @return 一个Singleton的实例*/public static Singleton getInstance(){//直接使用已经创建好的实例return uniqueInstance;}/*** 示意方法,单例可以有自己的操作*/public void singletonOperation(){//功能处理}/*** 示意属性,单例可以有自己的属性*/private String singletonData;/*** 示意方法,让外部通过这些方法来访问属性的值* @return 属性的值*/public String getSingletonData(){return singletonData;}
}
//懒汉式
//示例:双重检查加锁的懒汉式单例(以时间换空间)
public class Singleton {/*** 对保存实例的变量添加volatile的修饰*/private volatile static Singleton instance = null;private Singleton(){}public static Singleton getInstance(){//先检查实例是否存在,如果不存在才进入下面的同步块if(instance == null){//同步块,线程安全的创建实例synchronized(Singleton.class){//再次检查实例是否存在,如果不存在才真的创建实例if(instance == null){instance = new Singleton();}}}return instance;}
}//示例:使用类的内部类实现的单例
public class Singleton {/*** 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,* 而且只有被调用到才会装载,从而实现了延迟加载*/private static class SingletonHolder{/*** 静态初始化器,由JVM来保证线程安全*/private static Singleton instance = new Singleton();}/*** 私有化构造方法*/private Singleton(){}public static Singleton getInstance(){return SingletonHolder.instance;}
}//示例:使用枚举实现单例(效率高)
/*** 使用枚举来实现单例模式的示例*/
public enum EnumSingleton { /*** 定义一个枚举的元素,它就代表了Singleton的一个实例*/uniqueInstance;public static EnumSingleton getInstance(){return uniqueInstance;}}
生成器模式
描述
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。本质是分离整体构建算法和部件构造。
结构
角色 | 说明 |
---|---|
Builder | 生成器接口,定义创建一个Product对象所需的各个部件的操作 |
ConcreteBuilder | 生成器类,实现各个部件的创建,并负责组装Product对象的各个部件,同时提供一个让客户获取组装完成后的产品对象的方法 |
Diector | 指导者,也被称为导向者,主要用于调用Builder接口,以一个统一的过程构建所需的Product对象 |
Product | 产品,表示生成器构建的复杂对象,包含多个部件 |
优缺点
优点
- 松散耦合
- 便于变换产品的内部表示
- 更好的复用性
2.1.3.3 使用场景
使用场景
- 同一个构建过程有着不同的表示
- 创建对象的算法独立于该对象的组成部分以及它们的装配方式
示例
//构建构建部件类似的对象
//示例:导出数据的应用框架//Client类
public class Client {public static void main(String[] args) {//准备测试数据ExportHeaderModel ehm = new ExportHeaderModel();ehm.setDepId("一分公司");ehm.setExportDate("2010-05-18");Map<String,Collection<ExportDataModel>> mapData = new HashMap<String,Collection<ExportDataModel>>();Collection<ExportDataModel> col = new ArrayList<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.put("销售记录表", col);ExportFooterModel efm = new ExportFooterModel();efm.setExportUser("张三");//测试输出到文本文件TxtBuilder txtBuilder = new TxtBuilder();//创建指导者对象Director director = new Director(txtBuilder);director.construct(ehm, mapData, efm);//把要输出的内容输出到控制台看看System.out.println("输出到文本文件的内容:\n"+txtBuilder.getResult());//测试输出到xml文件XmlBuilder xmlBuilder = new XmlBuilder();Director director2 = new Director(xmlBuilder);director2.construct(ehm, mapData, efm);//把要输出的内容输出到控制台看看System.out.println("输出到XML文件的内容:\n"+xmlBuilder.getResult());}
}
//Director类
/*** 指导者,指导使用构建器的接口来构建输出的文件的对象*/
public class Director {/*** 持有当前需要使用的构建器对象*/private Builder builder;/*** 构造方法,传入构建器对象* @param builder 构建器对象*/public Director(Builder builder) {this.builder = builder;}/*** 指导构建器构建最终的输出的文件的对象* @param ehm 文件头的内容* @param mapData 数据的内容* @param efm 文件尾的内容*/public void construct(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm) {//1:先构建Headerbuilder.buildHeader(ehm);//2:然后构建Bodybuilder.buildBody(mapData);//3:然后构建Footerbuilder.buildFooter(efm);}
}
//Builder接口
/*** 构建器接口,定义创建一个输出文件对象所需的各个部件的操作*/
public interface Builder {/*** 构建输出文件的Header部分* @param ehm 文件头的内容*/public void buildHeader(ExportHeaderModel ehm);/*** 构建输出文件的Body部分* @param mapData 要输出的数据的内容*/public void buildBody(Map<String,Collection<ExportDataModel>> mapData);/*** 构建输出文件的Footer部分* @param efm 文件尾的内容*/public void buildFooter(ExportFooterModel efm);
}
//TxtBuilder类
/*** 实现导出数据到文本文件的的构建器对象*/
public class TxtBuilder implements Builder {/*** 用来记录构建的文件的内容,相当于产品*/private StringBuffer buffer = new StringBuffer();public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {for(String tblName : mapData.keySet()){//先拼接表名称buffer.append(tblName+"\n");//然后循环拼接具体数据for(ExportDataModel edm : mapData.get(tblName)){buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n");}}}public void buildFooter(ExportFooterModel efm) {buffer.append(efm.getExportUser());}public void buildHeader(ExportHeaderModel ehm) {buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n");} public StringBuffer getResult(){return buffer;}
}
//XmlBuilder类
/*** 实现导出数据到XML文件的的构建器对象*/
public class XmlBuilder implements Builder {/*** 用来记录构建的文件的内容,相当于产品*/private StringBuffer buffer = new StringBuffer();public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {buffer.append(" <Body>\n");for(String tblName : mapData.keySet()){//先拼接表名称buffer.append(" <Datas TableName=\""+tblName+"\">\n");//然后循环拼接具体数据for(ExportDataModel edm : mapData.get(tblName)){buffer.append(" <Data>\n");buffer.append(" <ProductId>"+edm.getProductId()+"</ProductId>\n");buffer.append(" <Price>"+edm.getPrice()+"</Price>\n");buffer.append(" <Amount>"+edm.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 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 StringBuffer getResult(){return buffer;}}
//导出的头模型类
/*** 描述输出到文件头的内容的对象*/
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 exportDate) {this.exportDate = exportDate;}
}
//导出的数据体模型类
/*** 描述输出数据的对象*/
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;}}//构建复杂对象
//示例:构建保险合同对象
//Client类
public class Client {public static void main(String[] args) {//创建构建器ConcreteBuilder builder = InsuranceContract.createConcreteBuilder("001",12345L,67890L);//设置需要的数据,然后构建保险合同对象InsuranceContract contract = builder.setPersonName("张三").setOtherData("test").build();//操作保险合同对象的方法contract.someOperation();}
}
//保险合同类
/*** 保险合同的对象*/
public class InsuranceContract {/*** 保险合同编号*/private String contractId;/*** 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订,* 也就是说,"被保险人员"和"被保险公司"这两个属性,不可能同时有值*/private String personName;/*** 被保险公司的名称*/private String companyName;/*** 保险开始生效的日期*/private long beginDate;/*** 保险失效的日期,一定会大于保险开始生效的日期*/private long endDate;/*** 示例:其它数据*/private String otherData;/*** 构造方法,访问级别是私有的*/private InsuranceContract(ConcreteBuilder builder){this.contractId = builder.contractId;this.personName = builder.personName;this.companyName = builder.companyName;this.beginDate = builder.beginDate;this.endDate = builder.endDate;this.otherData = builder.otherData;}/*** 工厂方法* @param contractId* @param beginDate* @param endDate* @return*/public static ConcreteBuilder createConcreteBuilder(String contractId,long beginDate,long endDate) {return new ConcreteBuilder(contractId,beginDate,endDate);}/*** 构造保险合同对象的构建器*/public static class ConcreteBuilder {private String contractId;private String personName;private String companyName;private long beginDate;private long endDate;private String otherData;/*** 构造方法,访问级别是私有的父类的createConcreteBuilder方法创建,必须由,传入必须要有的参数* @param contractId 保险合同编号* @param beginDate 保险开始生效的日期* @param endDate 保险失效的日期*/private ConcreteBuilder(String contractId,long beginDate,long endDate){this.contractId = contractId;this.beginDate = beginDate;this.endDate = endDate;}/*** 选填数据,被保险人员的名称* @param personName 被保险人员的名称* @return 构建器对象*/public ConcreteBuilder setPersonName(String personName){this.personName = personName;return this;}/*** 选填数据,被保险公司的名称* @param companyName 被保险公司的名称* @return 构建器对象*/public ConcreteBuilder setCompanyName(String companyName){this.companyName = companyName;return this;}/*** 选填数据,其它数据* @param otherData 其它数据* @return 构建器对象*/public ConcreteBuilder setOtherData(String otherData){this.otherData = otherData;return this;}/*** 构建真正的对象并返回* @return 构建的保险合同的对象*/public InsuranceContract build(){if(contractId==null || contractId.trim().length()==0){throw new IllegalArgumentException("合同编号不能为空");}boolean signPerson = personName!=null && personName.trim().length()>0;boolean signCompany = companyName!=null && companyName.trim().length()>0;if(signPerson && signCompany){throw new IllegalArgumentException("一份保险合同不能同时与人和公司签订");} if(signPerson==false && signCompany==false){throw new IllegalArgumentException("一份保险合同不能没有签订对象");}if(beginDate<=0){throw new IllegalArgumentException("合同必须有保险开始生效的日期");}if(endDate<=0){throw new IllegalArgumentException("合同必须有保险失效的日期");}if(endDate<=beginDate){throw new IllegalArgumentException("保险失效的日期必须大于保险生效日期");}return new InsuranceContract(this);}}/*** 示意:保险合同的某些操作*/public void someOperation(){System.out.println("Now in Insurance Contract someOperation=="+this.contractId);}
}
原型模式
描述
定义:用原型实例指定创建对象的种类,并通过复制其原型创建新的对象。本质是克隆生成对象。
结构
角色 | 说明 |
---|---|
Prototype | 声明一个克隆自身的接口,用于约束克隆自身的类,要求它们都要实现原型接口定义的克隆方法 |
ConcretePrototype | 实现Prototype接口的类 |
Client | 使用原型的客户端 |
浅度克隆:只负责克隆按值传递的数据。
深度克隆:既克隆按值传递的数据,也克隆引用类型的数据(有可能需要递归克隆)。
优缺点
优点
- 对客户端隐藏具体的实现类型
- 在运行时动态改变具体的实现类型
缺点
- 难以实现深度克隆
使用场景
- 新的对象实例需要复用已存在的实例部分或全部属性时,可以使用原型模式
示例
//订单拆分
//示例:订单拆分//客户端类
public class OrderClient {public static void main(String[] args) {//创建订单对象,这里为了演示简单,直接new了PersonalOrder op = new PersonalOrder();//设置订单数据op.setOrderProductNum(3925);op.setCustomerName("张三");op.setProductId("P0001");//这里获取业务处理的类,也直接new了,为了简单,连业务接口都没有做OrderBusiness ob = new OrderBusiness();//调用业务来保存订单对象ob.saveOrder(op);}
}
//业务类
/*** 处理订单的业务对象*/
public class OrderBusiness {/*** 创建订单的方法* @param order 订单的接口对象*/public void saveOrder(OrderApi order){//根据业务要求,当订单的预定的产品数量超过1000的时候,就需要把订单拆成两份订单//当然如果要做好,这里的1000应该做成常量,这么做是为了演示简单//1:判断当前的预定产品数量是否大于1000while(order.getOrderProductNum() > 1000){//2:如果大于,还需要继续拆分//2.1再新建一份订单,跟传入的订单除了数量不一样外,其他都相同OrderApi newOrder = order.cloneOrder();//然后进行赋值,产品数量为1000newOrder.setOrderProductNum(1000);//2.2原来的订单保留,把数量设置成减少1000order.setOrderProductNum(order.getOrderProductNum()-1000);//然后是业务功能处理,省略了,打印输出,看一下System.out.println("拆分生成订单=="+newOrder);} //3:不超过,那就直接业务功能处理,省略了,打印输出,看一下System.out.println("订单=="+order);}
}
//订单接口
/*** 订单的接口,声明了可以克隆自身的方法*/
public interface OrderApi {/*** 获取订单产品数量* @return 订单中产品数量*/public int getOrderProductNum();/*** 设置订单产品数量* @param num 订单产品数量*/public void setOrderProductNum(int num);/*** 克隆方法* @return 订单原型的实例*/public OrderApi cloneOrder();
}
//个人订单类
/*** 个人订单对象*/
public class PersonalOrder implements OrderApi{/*** 订购人员姓名*/private String customerName;/*** 产品编号*/private String productId;/*** 订单产品数量*/private int orderProductNum = 0;public int getOrderProductNum() {return this.orderProductNum;} public void setOrderProductNum(int num) {this.orderProductNum = num;} 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 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 implements OrderApi{/*** 企业名称*/private String enterpriseName;/*** 产品编号*/private String productId; /*** 订单产品数量*/private int orderProductNum = 0;public int getOrderProductNum() {return this.orderProductNum;} public void setOrderProductNum(int num) {this.orderProductNum = num;} 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 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;}}
结构型
结构型模式是描述如何组合类和对象以获得更大的结构。
外观模式
描述
定义:为子系统中的一组接口提供一个一致的界面,Façade模式定义了一个高层接口,使得其子系统更加容易使用。本质是封装交互,简化调用。
结构
角色 | 说明 |
---|---|
Facede | 定义子系统的多个模块对外的高层接口 |
模块 | 接受Façade的委派,真正实现功能,各个模块间可能有交互 |
外观模式的实现
- 作为辅助工具类实现。
- 作为interface实现,可以有选择性的暴露接口方法,尽量减少模块对子系统外提供的接口方法。
组合简单工厂模式使用
优缺点
优点
- 松散耦合
- 简单易用
- 更好划分访问层次
缺点
- 过多或不太合理的Façade会使系统过于复杂
使用场景
- 为一个复杂的子系统提供一个简单接口时,可以使用外观对象实现大部分客户需要的功能,简化客户的使用。
- 为使客户程序和抽象类的实现部分松散耦合,可以使用外观对象将这个子系统与客户程序分离,提高子系统的独立性和可移植性。
- 在构建多层结构的系统时,可以使用外观对象作为每层的入口,以简化层间调用,同时松散层次之间的依赖关系。
示例
略。
适配器模式
描述
定义:将一个类的接口转换成客户期望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。本质是转换匹配,复用功能。
结构
角色 | 说明 |
---|---|
Client | 客户端,调用自身需要的领域接口Target |
Target | 定义客户端需要的跟特定领域相关的接口 |
Adaptee | 已存在的接口,但与客户端要求的特定领域接口不一致,需要被适配 |
Adapter | 适配器,将Adaptee适配成为Client需要的Target |
智能适配器:可以在适配器加入新功能的实现。
缺省适配:为一个接口提供缺省实现。
优缺点
优点
- 更好的复用性
- 更好的可扩展性
缺点
- 过多的使用,会使系统混乱,不容易进行整体把握
使用场景
- 已存在的类不符合客户端调用需求,可以使用适配器对象转换成需要的接口
- 需创建一个可复用的类,这个类可能和不兼容的类一起工作,可以使用适配器模式以期需要什么就适配什么
示例
//记录日志(单向适配)
//示例:已存在文件记录日志接口,新加数据库记录日志接口(单向适配,客户端调用数//据库记录日志接口实现文件记录日志功能)
//Client角色
public class Client {public static void main(String[] args) {//准备日志内容,也就是测试的数据LogModel lm1 = new LogModel();lm1.setLogId("001");lm1.setOperateUser("admin");lm1.setOperateTime("2010-03-02 10:08:18");lm1.setLogContent("这是一个测试");List<LogModel> list = new ArrayList<LogModel>();list.add(lm1);//创建操作日志文件的对象LogFileOperateApi logFileApi = new LogFileOperate("");//创建新版的操作日志的接口对象LogDbOperateApi api = new Adapter(logFileApi); //保存日志文件api.createLog(lm1);//读取日志文件的内容List<LogModel> allLog = api.getAllLog();System.out.println("allLog="+allLog);}
}
//Target角色,数据库记录日志接口
/*** 定义操作日志的应用接口,为了示例的简单,* 只是简单的定义了增删改查的方法*/
public interface LogDbOperateApi {/*** 新增日志* @param lm 需要新增的日志对象*/public void createLog(LogModel lm);/*** 修改日志* @param lm 需要修改的日志对象*/public void updateLog(LogModel lm);/*** 删除日志* @param lm 需要删除的日志对象*/public void removeLog(LogModel lm);/*** 获取所有的日志* @return 所有的日志对象*/public List<LogModel> getAllLog();
}
//Adaptee角色,文件记录日志接口及其实现类
//接口
/*** 日志文件操作接口*/
public interface LogFileOperateApi {/*** 读取日志文件,从文件里面获取存储的日志列表对象* @return 存储的日志列表对象*/public List<LogModel> readLogFile();/*** 写日志文件,把日志列表写出到日志文件中去* @param list 要写到日志文件的日志列表*/public void writeLogFile(List<LogModel> list);
}
//实现类
/*** 实现对日志文件的操作*/
public class LogFileOperate implements LogFileOperateApi{/*** 日志文件的路径和文件名称,默认是当前classpath下的AdapterLog.log*/private String logFilePathName = "AdapterLog.log"; /*** 构造方法,传入文件的路径和名称* @param logFilePathName 文件的路径和名称*/public LogFileOperate(String logFilePathName) {//先判断是否传入了文件的路径和名称,如果是,//就重新设置操作的日志文件的路径和名称if(logFilePathName!=null && logFilePathName.trim().length()>0){this.logFilePathName = logFilePathName;}}public List<LogModel> readLogFile() {List<LogModel> list = null;ObjectInputStream oin = null;try {File f = new File(logFilePathName);if(f.exists()){oin = new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));list = (List<LogModel>)oin.readObject();}} catch (Exception e) {e.printStackTrace();}finally{try {if(oin!=null){oin.close();}} catch (IOException e) {e.printStackTrace();}}return list;}public void writeLogFile(List<LogModel> list){File f = new File(logFilePathName);ObjectOutputStream oout = null;try {oout = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));oout.writeObject(list); } catch (IOException e) {e.printStackTrace();}finally{try {oout.close();} catch (IOException e) {e.printStackTrace();}}}
}
//Adapter角色,
/*** 适配器对象,把记录日志到文件的功能适配成第二版需要的增删改查的功能*/
public class Adapter implements LogDbOperateApi{/*** 持有需要被适配的接口对象*/private LogFileOperateApi adaptee;/*** 构造方法,传入需要被适配的对象* @param adaptee 需要被适配的对象*/public Adapter(LogFileOperateApi adaptee) {this.adaptee = adaptee;}public void createLog(LogModel lm) {//1:先读取文件的内容List<LogModel> list = adaptee.readLogFile();//2:加入新的日志对象list.add(lm);//3:重新写入文件adaptee.writeLogFile(list);}public List<LogModel> getAllLog() {return adaptee.readLogFile();}public void removeLog(LogModel lm) {//1:先读取文件的内容List<LogModel> list = adaptee.readLogFile();//2:删除相应的日志对象list.remove(lm);//3:重新写入文件adaptee.writeLogFile(list);}public void updateLog(LogModel lm) {//1:先读取文件的内容List<LogModel> list = adaptee.readLogFile();//2:修改相应的日志对象for(int i=0;i<list.size();i++){if(list.get(i).getLogId().equals(lm.getLogId())){list.set(i, lm);break;}}//3:重新写入文件adaptee.writeLogFile(list);}
}
//日志数据模型类
/*** 日志数据对象*/
public class LogModel implements Serializable{/*** 日志编号*/private String logId;/*** 操作人员*/private String operateUser;/*** 操作时间,以yyyy-MM-dd HH:mm:ss的格式记录*/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 String toString(){return "logId="+logId+",operateUser="+operateUser+",operateTime"+operateTime+",logContent="+logContent;}
}
//记录日志(双向适配)
//示例:文件记录日志接口和数据库记录日志接口双向适配
//Client
public class Client {public static void main(String[] args) {//准备日志内容,也就是测试的数据LogModel lm1 = new LogModel();lm1.setLogId("001");lm1.setOperateUser("admin");lm1.setOperateTime("2010-03-02 10:08:18");lm1.setLogContent("这是一个测试");List<LogModel> list = new ArrayList<LogModel>();list.add(lm1);//创建操作日志文件的对象LogFileOperateApi fileLogApi = new LogFileOperate("");LogDbOperateApi dbLogApi = new LogDbOperate();//创建经过双向适配后的操作日志的接口对象LogFileOperateApi fileLogApi2 = new TwoDirectAdapter(fileLogApi,dbLogApi); LogDbOperateApi dbLogApi2 = new TwoDirectAdapter(fileLogApi,dbLogApi); //先测试从文件操作适配到第二版,虽然调用的是第二版的接口,其实是文件操作在实现dbLogApi2.createLog(lm1);List<LogModel> allLog = dbLogApi2.getAllLog();System.out.println("allLog="+allLog);//再测试从数据库存储适配成第一版的接口,也就是调用第一版的接口,其实是数据实现fileLogApi2.writeLogFile(list);fileLogApi2.readLogFile();}
}
//双向适配器
/*** 双向适配器对象*/
public class TwoDirectAdapter implements LogDbOperateApi,LogFileOperateApi{/*** 持有需要被适配的文件存储日志的接口对象*/private LogFileOperateApi fileLog;/*** 持有需要被适配的DB存储日志的接口对象*/private LogDbOperateApi dbLog;/*** 构造方法,传入需要被适配的对象* @param fileLog 需要被适配的文件存储日志的接口对象* @param dbLog 需要被适配的DB存储日志的接口对象*/public TwoDirectAdapter(LogFileOperateApi fileLog,LogDbOperateApi dbLog) {this.fileLog = fileLog;this.dbLog = dbLog;}
/*-----以下是把文件操作的方式适配成为DB实现方式的接口-----*/ public void createLog(LogModel lm) {//1:先读取文件的内容List<LogModel> list = fileLog.readLogFile();//2:加入新的日志对象list.add(lm);//3:重新写入文件fileLog.writeLogFile(list);}public List<LogModel> getAllLog() {return fileLog.readLogFile();}public void removeLog(LogModel lm) {//1:先读取文件的内容List<LogModel> list = fileLog.readLogFile();//2:删除相应的日志对象list.remove(lm);//3:重新写入文件fileLog.writeLogFile(list);}public void updateLog(LogModel lm) {//1:先读取文件的内容List<LogModel> list = fileLog.readLogFile();//2:修改相应的日志对象for(int i=0;i<list.size();i++){if(list.get(i).getLogId().equals(lm.getLogId())){list.set(i, lm);break;}}//3:重新写入文件fileLog.writeLogFile(list);}
/*-----以下是把DB操作的方式适配成为文件实现方式的接口-----*/public List<LogModel> readLogFile() {return dbLog.getAllLog();}public void writeLogFile(List<LogModel> list) {//1:最简单的实现思路,先删除数据库中的数据//2:然后循环把现在的数据加入到数据库中for(LogModel lm : list){dbLog.createLog(lm);} }
}
//数据库记录日志接口
/*** 定义操作日志的应用接口,为了示例的简单,* 只是简单的定义了增删改查的方法*/
public interface LogDbOperateApi {/*** 新增日志* @param lm 需要新增的日志对象*/public void createLog(LogModel lm);/*** 修改日志* @param lm 需要修改的日志对象*/public void updateLog(LogModel lm);/*** 删除日志* @param lm 需要删除的日志对象*/public void removeLog(LogModel lm);/*** 获取所有的日志* @return 所有的日志对象*/public List<LogModel> getAllLog();
}
//数据库记录日志实现类
/*** DB存储日志的实现,为了简单,这里就不去真的实现和数据库交互了,示意一下*/
public class LogDbOperate implements LogDbOperateApi{public void createLog(LogModel lm) {System.out.println("now in LogDbOperate createLog,lm="+lm);}public List<LogModel> getAllLog() {System.out.println("now in LogDbOperate getAllLog");return null;}public void removeLog(LogModel lm) {System.out.println("now in LogDbOperate removeLog,lm="+lm);}public void updateLog(LogModel lm) {System.out.println("now in LogDbOperate updateLog,lm="+lm);}}
//文件记录日志接口
/*** 日志文件操作接口*/
public interface LogFileOperateApi {/*** 读取日志文件,从文件里面获取存储的日志列表对象* @return 存储的日志列表对象*/public List<LogModel> readLogFile();/*** 写日志文件,把日志列表写出到日志文件中去* @param list 要写到日志文件的日志列表*/public void writeLogFile(List<LogModel> list);
}
//文件记录日志实现类
/*** 实现对日志文件的操作*/
public class LogFileOperate implements LogFileOperateApi{/*** 日志文件的路径和文件名称,默认是当前classpath下的AdapterLog.log*/private String logFilePathName = "AdapterLog.log"; /*** 构造方法,传入文件的路径和名称* @param logFilePathName 文件的路径和名称*/public LogFileOperate(String logFilePathName) {//先判断是否传入了文件的路径和名称,如果是,//就重新设置操作的日志文件的路径和名称if(logFilePathName!=null && logFilePathName.trim().length()>0){this.logFilePathName = logFilePathName;}}public List<LogModel> readLogFile() {List<LogModel> list = null;ObjectInputStream oin = null;try {File f = new File(logFilePathName);if(f.exists()){oin = new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));list = (List<LogModel>)oin.readObject();}} catch (Exception e) {e.printStackTrace();}finally{try {if(oin!=null){oin.close();}} catch (IOException e) {e.printStackTrace();}}return list;}public void writeLogFile(List<LogModel> list){File f = new File(logFilePathName);ObjectOutputStream oout = null;try {oout = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));oout.writeObject(list); } catch (IOException e) {e.printStackTrace();}finally{try {oout.close();} catch (IOException e) {e.printStackTrace();}}}
}
//日志数据模型类
/*** 日志数据对象*/
public class LogModel implements Serializable{/*** 日志编号*/private String logId;/*** 操作人员*/private String operateUser;/*** 操作时间,以yyyy-MM-dd HH:mm:ss的格式记录*/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 String toString(){return "logId="+logId+",operateUser="+operateUser+",operateTime"+operateTime+",logContent="+logContent;}
}
代理模式
描述
定义:为其它对象提供一种代理以控制对这个对象的访问。本质是控制对象访问。
结构
角色 | 说明 |
---|---|
Proxy | 代理对象,具有如下功能 |
- 实现与具体目标对象一样的接口
- 保存一个指向具体目标对象的引用,可在需要时调用
- 可以控制对具体目标对象的访问,并可能负责创建和删除它
|Subject |目标接口,定义代理和具体目标对象的接口
|RealSubject |具体目标对象,真正实现目标接口要求的功能
代理的分类
- 虚代理:根据需要创建开销较大的对象,该对象只有在需要时才会被真正创建
- 远程代理:用于在不同的地址空间上代表同一个对象,如:RMI
- 保护代理:控制对原始对象的访问
- 缓存代理
- 同步代理:解决竞态对象的冲突问题
优缺点
优点
- 隐藏代理细节
使用场景
- 需为一个对象在不同的地址空间提供局部代表时,使用远程代理
- 按需创建开销很大的对象时,使用虚代理
- 需控制对原始对象访问时,使用保护代理
- 需在访问对象时执行附加操作时,使用智能指引代理
示例
//JDK动态代理
//示例:JDK动态代理实现访问权限控制
//Client类
/*** 动态代理示例* @author liaocan**/
public class Client {public static void main(String[] args) {//张三先登录系统创建了一个订单Order order = new Order("设计模式",100,"张三");//创建一个动态代理DynamicProxy dynamicProxy = new DynamicProxy(); //然后把订单和动态代理关联起来OrderApi orderApi = dynamicProxy.getProxyInterface(order);//以下就需要使用被代理过的接口来操作了//李四想要来修改,那就会报错orderApi.setOrderNum(123, "李四");//输出orderSystem.out.println("李四修改后订单记录没有变化:"+orderApi);//张三修改就不会有问题orderApi.setOrderNum(123, "张三");//再次输出orderSystem.out.println("张三修改后,订单记录:"+orderApi);}
}
//订单接口
/*** 订单对象的接口定义*/
public interface OrderApi {/*** 获取订单订购的产品名称* @return 订单订购的产品名称*/public String getProductName();/*** 设置订单订购的产品名称* @param productName 订单订购的产品名称* @param user 操作人员*/public void setProductName(String productName,String user);/*** 获取订单订购的数量* @return 订单订购的数量*/public int getOrderNum();/*** 设置订单订购的数量* @param orderNum 订单订购的数量* @param user 操作人员*/public void setOrderNum(int orderNum,String user);/*** 获取创建订单的人员* @return 创建订单的人员*/public String getOrderUser();/*** 设置创建订单的人员* @param orderUser 创建订单的人员* @param user 操作人员*/public void setOrderUser(String orderUser,String user);
}
//订单类
/*** 订单对象*/
public class Order implements OrderApi{/*** 订单订购的产品名称*/private String productName;/*** 订单订购的数量*/private int orderNum;/*** 创建订单的人员*/private String orderUser;/*** 构造方法,传入构建需要的数据* @param productName 订单订购的产品名称* @param orderNum 订单订购的数量* @param 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 String toString(){return "productName="+this.getProductName()+",orderNum="+this.getOrderNum()+",orderUser="+this.getOrderUser();}
}
//动态代理类
/*** 使用Java中的动态代理*/
public class DynamicProxy implements InvocationHandler{/*** 被代理的对象*/private OrderApi order = null;/*** 获取绑定好代理和具体目标对象后的目标对象的接口* @param order 具体的订单对象,相当于具体目标对象* @return 绑定好代理和具体目标对象后的目标对象的接口*/public OrderApi getProxyInterface(OrderApi order){//设置被代理的对象,好方便invoke里面的操作this.order = order;//把真正的订单对象和动态代理关联起来OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(order.getClass().getClassLoader(),order.getClass().getInterfaces(), this);return orderApi;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {//如果是调用setter方法就需要检查权限if(method.getName().startsWith("set")){//如果不是创建人,那就不能修改if(order.getOrderUser()!=null && order.getOrderUser().equals(args[1])){//可以操作return method.invoke(order, args);}else{System.out.println("对不起,"+args[1]+",您无权修改本订单中的数据");}}else{//不是调用的setter方法就继续运行return method.invoke(order, args);}return null;}
}
//CGLIB动态代理
//示例:CGLIB动态代理实现AOP
//Client类
public class Client {public static void main(String[] args) {OrderManager order = (OrderManager) new CGLibProxy().createProxyObject(new OrderManager("aoho"));order.save(UUID.randomUUID(), "save");order.update(UUID.randomUUID(), "update");order.getByName("save");}}
//被代理的类
public class OrderManager {private String user = null;public OrderManager() {}public OrderManager(String user) {this.setUser(user);}public String getUser() {return user;}public void setUser(String user) {this.user = user;}public void save(UUID orderId, String name) {System.out.println("call save()方法,save:" + name);}public void update(UUID orderId, String name) {System.out.println("call update()方法");}public String getByName(String name) {System.out.println("call getByName()方法");return "aoho";}
}
//代理类
public class CGLibProxy implements MethodInterceptor {// CGLib需要代理的目标对象private Object targetObject;public Object createProxyObject(Object obj) {this.targetObject = obj;Enhancer enhancer = new Enhancer();enhancer.setSuperclass(obj.getClass());//回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法 enhancer.setCallback(this);// 返回代理对象return enhancer.create();}public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {Object obj = null;//切面逻辑(advise),此处是在目标类代码执行之前System.out.println("---before intercept----");obj = method.invoke(targetObject, args);System.out.println("---after intercept----");return obj;}
}
组合模式
描述
定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使用户对单个对象和组合对象的使用具有一致性。本质是统一组合对象和叶子对象。
结构
角色 | 说明 |
---|---|
Component | 抽象的组件对象,为组合中的对象声明接口,使客户端通过其访问和管理整个对象结构。也可以在其中为定义的功能提供缺省的实现 |
Leaf | 叶子节点对象,定义和实现叶子对象的行为 |
Composite | 组合对象,可以存储子组件;定义包含子组件的行为,并实现在组件接口中定义的与子组件有关的操作 |
Client | 客户端 |
环状引用:在对象结构中,某个对象包含的子对象,或是子对象的子对象。。。如此经过N层后,出现所包含的子对象中有这个对象本身,从而构成环状引用。而避免环状引用的方式,则是记录引用的路径,添加子节点时检测。
智能方案:将子节点也看成一个组合对象,只不过其子节点属性为空。而根节点的父节点引用,也为空。
优缺点
优点
- 统一了组合对象和叶子对象的访问
- 易于扩展
缺点
- 难以限制组合中的组件类型
使用场景
- 需表示对象的“部分-整体”层次结构
示例
//商品类别树
//示例:商品类别树的管理//Client类
/*** 添加对父对象的引用* @author liaocan**/
public class Client {public 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("");System.out.println("---------------------------->");//然后删除一个节点root.removeChild(c1);//重新输出整棵树root.printStruct("");}
}
//组件抽象类
/*** 抽象的组件对象*/
public abstract class Component {/*** 记录父组件对象*/private Component parent = null;/*** 获取一个组件的父组件对象* @return 一个组件的父组件对象*/public Component getParent() {return parent;}/*** 设置一个组件的父组件对象* @param parent 一个组件的父组件对象*/public void setParent(Component parent) {this.parent = parent;}/*** 返回某个组件的子组件对象* @return 某个组件的子组件对象*/public List<Component> getChildren() {throw new UnsupportedOperationException("对象不支持这个功能");}
/*-------------------以下是原有的定义----------------------*/ /*** 输出组件自身的名称*/public abstract void printStruct(String preStr);/*** 向组合对象中加入组件对象 * @param child 被加入组合对象中的组件对象*/public void addChild(Component child) {// 缺省的实现,抛出例外,因为叶子对象没有这个功能,或者子组件没有实现这个功能throw new UnsupportedOperationException("对象不支持这个功能");}/*** 从组合对象中移出某个组件对象* @param child 被移出的组件对象*/public void removeChild(Component child) {// 缺省的实现,抛出例外,因为叶子对象没有这个功能,或者子组件没有实现这个功能throw new UnsupportedOperationException("对象不支持这个功能");}/*** 返回某个索引对应的组件对象* @param index 需要获取的组件对象的索引,索引从0开始* @return 索引对应的组件对象*/public Component getChildren(int index) {throw new UnsupportedOperationException("对象不支持这个功能");}
}
//组合对象
/*** 组合对象,可以包含其它组合对象或者叶子对象*/
public class Composite extends Component{public void addChild(Component child) {//延迟初始化if (childComponents == null) {childComponents = new ArrayList<Component>();}childComponents.add(child);//添加对父组件的引用child.setParent(this);}public void removeChild(Component child) {if (childComponents != null) {//查找到要删除的组件在集合中的索引位置int idx = childComponents.indexOf(child);if (idx != -1) {//先把被删除的商品类别对象的父商品类别,设置成为被删除的商品类别的子类别的父商品类别for(Component c : child.getChildren()){//删除的组件对象是本实例的一个子组件对象c.setParent(this);//把被删除的商品类别对象的子组件对象添加到当前实例中childComponents.add(c);}//真的删除childComponents.remove(idx);}} }public List<Component> getChildren() {return childComponents;}/*-------------------以下是原有的实现,没有变化----------------------*/ /*** 用来存储组合对象中包含的子组件对象*/private List<Component> childComponents = null;/*** 组合对象的名字*/private String name = "";/*** 构造方法,传入组合对象的名字* @param name 组合对象的名字*/public Composite(String name){this.name = name;}/*** 输出组合对象自身的结构* @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进*/public void printStruct(String preStr){//先把自己输出去System.out.println(preStr+"+"+this.name);//如果还包含有子组件,那么就输出这些子组件对象if(this.childComponents!=null){//然后添加一个空格,表示向后缩进一个空格preStr+=" "; //输出当前对象的子对象了for(Component c : childComponents){//递归输出每个子对象c.printStruct(preStr);}}}
}
//叶子对象
/*** 叶子对象*/
public class Leaf extends Component{/*** 叶子对象的名字*/private String name = "";/*** 构造方法,传入叶子对象的名字* @param name 叶子对象的名字*/public Leaf(String name){this.name = name;}/*** 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字* @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进*/public void printStruct(String preStr){System.out.println(preStr+"-"+name);}
}
装饰模式
描述
定义:动态地给对象添加额外的职责。本质是增强功能,动态组合。
结构
角色 | 说明 |
---|---|
Component | 组件对象接口 |
ConcreteComponent | 组件对象的实现,是被装饰器装饰的原始对象 |
Decorator | 装饰器的抽象父类,需定义一个与组件接口一致的接口,并持有一个被装饰的对象 |
ConcreteDecorator | 装饰器对象的实现 |
优缺点
优点
- 比继承更灵活
- 更容易复用功能
- 简化高层定义
缺点
- 可能会产生很多细粒度的对象
使用场景
- 需动态、透明地给对象添加职责
- 不适合使用子类扩展时
示例
//示例:奖金计算//Client类
/*** 使用装饰模式的客户端*/
public class Client {public static void main(String[] args) {//先创建计算基本奖金的类,这也是被装饰的对象Component c1 = new ConcreteComponent();//然后对计算的基本奖金进行装饰,这里要组合各个装饰//说明,各个装饰者之间最好是不要有先后顺序的限制,也就是先装饰谁和后装饰谁都应该是一样的//先组合普通业务人员的奖金计算Decorator d1 = new MonthPrizeDecorator(c1);Decorator d2 = new SumPrizeDecorator(d1); //注意:这里只需要使用最后组合好的对象调用业务方法即可,会依次调用回去//日期对象都没有用上,所以传null就可以了double zs = d2.calcPrize("张三",null,null); System.out.println("==========张三应得奖金:"+zs);double ls = d2.calcPrize("李四",null,null);System.out.println("==========李四应得奖金:"+ls);//如果是业务经理,还需要一个计算团队的奖金计算Decorator d3 = new GroupPrizeDecorator(d2);double ww = d3.calcPrize("王五",null,null);System.out.println("==========王经理应得奖金:"+ww);}
}
//计算接口
/*** 计算奖金的组件接口*/
public abstract class Component {/*** 计算某人在某段时间内的奖金,有些参数在演示中并不会使用,* 但是在实际业务实现上是会用的,为了表示这是个具体的业务方法,* 因此这些参数被保留了* @param user 被计算奖金的人员* @param begin 计算奖金的开始时间* @param end 计算奖金的结束时间* @return 某人在某段时间内的奖金*/public abstract double calcPrize(String user,Date begin,Date end);
}
//计算实现
/*** 基本的实现计算奖金的类,也是被装饰器装饰的对象*/
public class ConcreteComponent extends Component{public double calcPrize(String user, Date begin, Date end) {//只是一个默认的实现,默认没有奖金return 0;}
}
//装饰器接口
/*** 装饰器的接口,需要跟被装饰的对象实现同样的接口*/
public abstract class Decorator extends Component{/*** 持有被装饰的组件对象*/protected Component c;/*** 通过构造方法传入被装饰的对象* @param c被装饰的对象*/public Decorator(Component c){this.c = c;}public double calcPrize(String user, Date begin, Date end) {//转调组件对象的方法return c.calcPrize(user, begin, end);}
}
//计算当月业务奖金
/*** 装饰器对象,计算当月业务奖金*/
public class MonthPrizeDecorator extends Decorator{public MonthPrizeDecorator(Component c){super(c);}public double calcPrize(String user, Date begin, Date end) {//1:先获取前面运算出来的奖金double money = super.calcPrize(user, begin, end);//2:然后计算当月业务奖金,按照人员和时间去获取当月的业务额,然后再乘以3%double prize = TempDB.mapMonthSaleMoney.get(user) * 0.03;System.out.println(user+"当月业务奖金"+prize);return money + prize;}}
//计算累计奖金
/*** 装饰器对象,计算累计奖金*/
public class SumPrizeDecorator extends Decorator{public SumPrizeDecorator(Component c){super(c);}public double calcPrize(String user, Date begin, Date end) {//1:先获取前面运算出来的奖金double money = super.calcPrize(user, begin, end);//2:然后计算累计奖金,其实这里应该按照人员去获取累计的业务额,然后再乘以0.1%//简单演示一下,假定大家的累计业务额都是1000000元double prize = 1000000 * 0.001;System.out.println(user+"累计奖金"+prize);return money + prize;}}
//计算当月团队业务奖金
/*** 装饰器对象,计算当月团队业务奖金*/
public class GroupPrizeDecorator extends Decorator{public GroupPrizeDecorator(Component c){super(c);}public double calcPrize(String user, Date begin, Date end) {//1:先获取前面运算出来的奖金double money = super.calcPrize(user, begin, end);//2:然后计算当月团队业务奖金,先计算出团队总的业务额,然后再乘以1%//假设都是一个团队的double group = 0.0;for(double d : TempDB.mapMonthSaleMoney.values()){group += d;}double prize = group * 0.01;System.out.println(user+"当月团队业务奖金"+prize);return money + prize;}}
//模拟数据库
/*** 在内存中模拟数据库,准备点测试数据,好计算奖金*/
public class TempDB {private TempDB(){}/*** 记录每个人的月度销售额,只用了人员,月份没有用*/public static Map<String,Double> mapMonthSaleMoney = new HashMap<String,Double>();static{//填充测试数据mapMonthSaleMoney.put("张三",10000.0);mapMonthSaleMoney.put("李四",20000.0);mapMonthSaleMoney.put("王五",30000.0);}
}
桥接模式
描述
定义:将抽象部分与它的实现部分分离,使得它们都可以独立地变化。本质是分离抽象和实现。
结构
角色 | 说明 |
---|---|
Abstraction | 抽象部分接口,需维护一个实现部分的对象引用,在抽象对象的方法需调用实现部分的对象完成功能 |
RefinedAbstraction | 扩展抽象部分接口,定义与实际业务相关的方法 |
Implementor | 实现部分接口 |
ConcreteImplementor | 真正实现Implementor接口的对象 |
桥接和继承:桥接变化维度为两维,继承变化维度为一维。
桥接
- 客户端负责创建Implementor对象,并在创建抽象部分对象时将它设置到抽象部分对象
- 在抽象部分对象构建时,由其自身创建相应的Implementor对象
- 在Abstraction中选择并创建一个缺省的Implementor对象,而子类可以根据所需覆盖这个实现
- 使用抽象工厂或简单工厂选择并创建Implementor对象,抽象部分通过调用工厂的方法获取Implementor对象
- 通过IoC/DI容器创建Implementor对象并注入到Abstraction
优缺点
优点
- 分离抽象和实现部分
- 更好的扩展性
- 可动态切换实现
- 可减少继承的子类
使用场景
- 抽象和实现部分需要解绑时
- 抽象和实现部分都需单独扩展
示例
//示例:实现发送加急消息
//Client类
/*** 使用桥接模式实现用各种方式发送各种消息* @author liaocan**/
public class Client {public 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 {/*** 发送消息* @param message 要发送的消息内容* @param toUser 把消息发送的目的人员*/public void send(String message,String toUser);
}
//发送站内短消息
/*** 以站内短消息的方式发送消息*/
public class MessageSMS implements MessageImplementor{public void send(String message, String toUser) {System.out.println("使用站内短消息的方式,发送消息'"+message+"'给"+toUser);}
}
//发送Email
/*** 以Email的方式发送消息*/
public class MessageEmail implements MessageImplementor{public void send(String message, String toUser) {System.out.println("使用Email的方式,发送消息'"+message+"'给"+toUser);}}
//发送手机短信
/*** 以手机短消息的方式发送消息*/
public class MessageMobile implements MessageImplementor{public void send(String message, String toUser) {System.out.println("使用手机短消息的方式,发送消息'"+message+"'给"+toUser);}
}
//发送消息抽象部分接口
/*** 抽象的消息对象*/
public abstract class AbstractMessage {/*** 持有一个实现部分的对象*/protected MessageImplementor impl;/*** 构造方法,传入实现部分的对象 * @param impl 实现部分的对象*/public AbstractMessage(MessageImplementor impl){this.impl = impl;}/*** 发送消息,转调实现部分的方法* @param message 要发送的消息内容* @param toUser 把消息发送的目的人员*/public void sendMessage(String message,String toUser){this.impl.send(message, toUser);}}
//普通消息
/*** 普通消息*/
public class CommonMessage extends AbstractMessage{public CommonMessage(MessageImplementor impl) {super(impl);}public void sendMessage(String message, String toUser) {//对于普通消息,什么都不干,直接调用父类的方法,把消息发送出去就可以了super.sendMessage(message, toUser);}
}
//加急消息
/*** 加急消息*/
public class UrgencyMessage extends AbstractMessage{public UrgencyMessage(MessageImplementor impl) {super(impl);}public void sendMessage(String message, String toUser) {message = "加急:"+message;super.sendMessage(message, toUser);}/*** 监控某消息的处理过程* @param messageId 被监控的消息的编号* @return 包含监控到的数据对象,这里示意一下,所以用了Object*/public Object watch(String messageId) {//获取相应的数据,组织成监控的数据对象,然后返回 return null;}
}
//特急消息
/*** 特急消息*/
public class SpecialUrgencyMessage extends AbstractMessage{public SpecialUrgencyMessage(MessageImplementor impl) {super(impl);}public void hurry(String messageId) {//执行催促的业务,发出催促的信息}public void sendMessage(String message, String toUser) {message = "特急:"+message;super.sendMessage(message, toUser);//还需要增加一条待催促的信息}
}
行为型
行为型模式是描述算法和对象间职责的分配。
中介者模式
描述
定义:用一个中介对象封装一系列的对象交互,使得各对象不需显式地相互引用,从而使得其松散耦合,且可以独立的改变它们之间的交互。本质是封装交互。
结构
角色 | 说明 |
---|---|
Mediator | 中介者接口,定义各个同事之间交互需要的方法;可以是公共的通讯方法,也可以是小范围的交互方法 |
ConcreteMediator | 中介者类,需维护各个同事对象,并负责具体的协调各同事对象的交互关系 |
Colleague | 同事类,通常实现为抽象类,主要负责约束同事对象的类型,并实现具体同事类之间的公共功能 |
ConcreteColleague | 具体的同事类,实现自身业务;在需要与其它同事通讯时,则与特有的中介者通信,中介者会负责与其它的同事交互 |
对标准中介者模式的改进
- 通常会去掉同事对象的父类,这样使得任意的需要相互交互的对象都可以成为同事
- 通常不定义Mediator接口,将具体的中介者对象实现成为单例
- 同事对象不再持有中介者,而是需要时直接获取其对象并调用;中介者也不再持有同事对象,而是在具体处理方法创建、或者获取、或者从参数传入需要的同事对象
优缺点
优点
- 松散耦合
- 集中控制交互
- 多对多变成一对多
缺点
- 过度集中化
使用场景
- 简化一组对象之间的通信
示例
//电脑播放电影
//示例:使用电影播放电影//Client类
/*** 使用电脑看电影的例子* @author liaocan**/
public class Client {public static void main(String[] args) {//1:创建中介者——主板对象MotherBoard mediator = new MotherBoard();//2:创建同事类CDDriver cd = new CDDriver(mediator);CPU cpu = new CPU(mediator);VideoCard vc = new VideoCard(mediator);SoundCard sc = new SoundCard(mediator);//3:让中介者知道所有的同事mediator.setCdDriver(cd);mediator.setCpu(cpu);mediator.setVideoCard(vc);mediator.setSoundCard(sc);//4:开始看电影,把光盘放入光驱,光驱开始读盘cd.readCD();}
}
//中介者接口
/*** 中介者对象的接口*/
public interface Mediator {/*** 同事对象在自身改变的时候来通知中介者的方法,* 让中介者去负责相应的与其他同事对象的交互* @param colleague 同事对象自身,好让中介者对象通过对象实例* 去获取同事对象的状态*/public void changed(Colleague colleague);
}
//电脑主板类
/*** 主板类,实现中介者接口*/
public class MotherBoard implements Mediator{/*** 需要知道要交互的同事类——光驱类*/private CDDriver cdDriver = null;/*** 需要知道要交互的同事类——CPU类*/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.opeCDDriverReadData((CDDriver)colleague);}else if(colleague == cpu){//表示CPU处理完了this.opeCPU((CPU)colleague);}}/*** 处理光驱读取数据过后与其他对象的交互* @param cd 光驱同事对象*/private void opeCDDriverReadData(CDDriver cd){//1:先获取光驱读取的数据String data = cd.getData();//2:把这些数据传递给CPU进行处理this.cpu.executeData(data);}/*** 处理CPU处理完数据后与其他对象的交互* @param cpu CPU同事类*/private void opeCPU(CPU cpu){//1:先获取CPU处理过后的数据String videoData = cpu.getVideoData();String soundData = cpu.getSoundData();//2:把这些数据传递给显卡和声卡展示出来this.videoCard.showData(videoData);this.soundCard.soundData(soundData);}}
//同事抽象类
/*** 同事类的抽象父类*/
public abstract class Colleague {/*** 持有中介者对象,每一个同事类都知道它的中介者对象*/private Mediator mediator;/*** 构造方法,传入中介者对象* @param mediator 中介者对象*/public Colleague(Mediator mediator) {this.mediator = mediator;}/*** 获取当前同事类对应的中介者对象* @return 对应的中介者对象*/public Mediator getMediator() {return mediator;}
}
//光驱类
/*** 光驱类,一个同事类*/
public class CDDriver extends Colleague{public CDDriver(Mediator mediator) {super(mediator);}/*** 光驱读取出来的数据*/private String data = "";/*** 获取光驱读取出来的数据* @return 光驱读取出来的数据*/public String getData(){return this.data;}/*** 读取光盘*/public void readCD(){//逗号前是视频显示的数据,逗号后是声音this.data = "设计模式,值得好好研究";//通知主板,自己的状态发生了改变this.getMediator().changed(this);}
}
//CPU类
/*** CPU类,一个同事类*/
public class CPU extends Colleague{public CPU(Mediator mediator) {super(mediator);}/*** 分解出来的视频数据*/private String videoData = "";/*** 分解出来的声音数据*/private String soundData = "";/*** 获取分解出来的视频数据* @return 分解出来的视频数据*/public String getVideoData() {return videoData;}/*** 获取分解出来的声音数据* @return 分解出来的声音数据*/public String getSoundData() {return soundData;}/*** 处理数据,把数据分成音频和视频的数据* @param data 被处理的数据*/public void executeData(String data){//把数据分解开,前面的是视频数据,后面的是音频数据String [] ss = data.split(",");this.videoData = ss[0];this.soundData = ss[1];//通知主板,CPU的工作完成this.getMediator().changed(this);}}
//显卡类
/*** 显卡类,一个同事类*/
public class VideoCard extends Colleague{public VideoCard(Mediator mediator) {super(mediator);}/*** 显示视频数据* @param data 被显示的数据*/public void showData(String data){System.out.println("您正观看的是:"+data);}}
//声卡类
/*** 声卡类,一个同事类*/
public class SoundCard extends Colleague{public SoundCard(Mediator mediator) {super(mediator);}/*** 按照声频数据发出声音* @param data 发出声音的数据*/public void soundData(String data){System.out.println("画外音:"+data);}}
观察者模式
描述
定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。本质是触发联动。
结构
角色 | 说明 |
---|---|
Subject | 目标对象,具有如下功能 |
- 一个目标可以被多个观察者观察
- 目标提供对观察者注册和退订的维护
- 目标的状态发生变化时,负责通知所有注册的观察者
|Observer |观察者接口,提供目标通知时对应的更新方法,可以在其方法回调目标对象,获取目标对象的数据
|ConcreteSubject |实现目标的对象,维护目标状态并执行相应的通知
|ConcreteObserver |实现观察者的对象,接收目标的通知,并进行相应的后续处理
推模型:目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,相当于广播通信。
拉模型:目标对象在通知观察者时,只传递少量信息,若观察者需要更具体的信息,由其主动到目标对象中获取。
优缺点
优点
- 观察者和目标之间的抽象耦合
- 动态联动
- 支持广播通信
缺点
- 可能会消耗多余的资源,如:广播通信并不是所有观察者都需要每次的广播信息
使用场景
- 一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化
- 需要触发联动效果,但不知有多少被触发的对象
示例
//订阅报纸
//示例:Java中的观察者模式实现订阅报纸
//Client类
/*** 使用 java 提供的 观察 支持* @author liaocan**/
public class Client {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.addObserver(reader1);subject.addObserver(reader2);subject.addObserver(reader3);//要出报纸啦subject.setContent("本期内容是观察者模式");}
}
//报纸类
/*** 报纸对象,具体的目标实现*/
public class NewsPaper extends java.util.Observable{/*** 报纸的具体内容*/private String content;/*** 获取报纸的具体内容* @return 报纸的具体内容*/public String getContent() {return content;}/*** 示意,设置报纸的具体内容,相当于要出版报纸了* @param content 报纸的具体内容*/public void setContent(String content) {this.content = content;//内容有了,说明又出报纸了,那就通知所有的读者//注意在用Java中的Observer模式的时候,这句话不可少this.setChanged();//然后主动通知,这里用的是推的方式this.notifyObservers(this.content);//如果用拉的方式,这么调用//this.notifyObservers();}
}
//读者类
/*** 真正的读者,为了简单就描述一下姓名*/
public class Reader implements java.util.Observer{/*** 读者的姓名*/private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public void update(Observable o, Object obj) {//这是采用推的方式System.out.println(name+"收到报纸了,阅读先。目标推过来的内容是==="+obj);//这是获取拉的数据System.out.println(name+"收到报纸了,阅读先。主动到目标对象去拉的内容是==="+((NewsPaper)o).getContent());}}
//水质检测
//示例:水质检测通知不同岗位的人
//Client类
/*** 检测水质案例* @author liaocan**/
public class Client {public static void main(String[] args) {//创建水质主题对象WaterQuality subject = new WaterQuality();//创建几个观察者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);//填写水质报告System.out.println("当水质为正常的时候------------------〉");subject.setPolluteLevel(0);System.out.println("当水质为轻度污染的时候---------------〉");subject.setPolluteLevel(1);System.out.println("当水质为中度污染的时候---------------〉");subject.setPolluteLevel(2);}
}
//目标抽象类
/*** 定义水质监测的目标对象*/
public abstract class WaterQualitySubject {/*** 用来保存注册的观察者对象*/protected List<WatcherObserver> observers = new ArrayList<WatcherObserver>();/*** 注册观察者对象* @param observer 观察者对象*/public void attach(WatcherObserver observer) {observers.add(observer);}/*** 删除观察者对象* @param observer 观察者对象*/public void detach(WatcherObserver observer) {observers.remove(observer);}/*** 通知相应的观察者对象*/public abstract void notifyWatchers();/*** 获取水质污染的级别* @return 水质污染的级别*/public abstract int getPolluteLevel();
}
//目标类
/*** 具体的水质监测对象*/
public class WaterQuality extends WaterQualitySubject{/*** 污染的级别,0表示正常,1表示轻度污染,2表示中度污染,3表示高度污染*/private int polluteLevel = 0;/*** 获取水质污染的级别* @return 水质污染的级别*/public int getPolluteLevel() {return polluteLevel;}/*** 当监测水质情况后,设置水质污染的级别* @param polluteLevel 水质污染的级别*/public void setPolluteLevel(int polluteLevel) {this.polluteLevel = polluteLevel;//通知相应的观察者this.notifyWatchers();}/*** 通知相应的观察者对象*/public void notifyWatchers() {//循环所有注册的观察者for(WatcherObserver watcher : observers){//开始根据污染级别判断是否需要通知,由这里总控if(this.polluteLevel >= 0){//通知监测员做记录if("监测人员".equals(watcher.getJob())){watcher.update(this);}}if(this.polluteLevel >= 1){//通知预警人员if("预警人员".equals(watcher.getJob())){watcher.update(this);}}if(this.polluteLevel >= 2){//通知监测部门领导if("监测部门领导".equals(watcher.getJob())){watcher.update(this);}}}}
}
//观察者接口
/*** 水质观察者接口定义*/
public interface WatcherObserver {/*** 被通知的方法* @param subject 传入被观察的目标对象*/public void update(WaterQualitySubject subject);/*** 设置观察人员的职务* @param job 观察人员的职务*/public void setJob(String job);/*** 获取观察人员的职务* @return 观察人员的职务*/public String getJob();
}
//观察者类
/*** 具体的观察者实现*/
public class Watcher implements WatcherObserver{/*** 职务*/private String job;public void update(WaterQualitySubject subject) {//这里采用的是拉的方式System.out.println(job+"获取到通知,当前污染级别为:"+subject.getPolluteLevel());}public String getJob() {return this.job;}public void setJob(String job) {this.job = job;}
}
命令模式
描述
定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。本质是封装请求。
结构
角色 | 说明 |
---|---|
Command | 命令接口,声明执行的方法 |
ConcreteCommand | 命令实现类,调用持有的接收者完成命令要执行的操作 |
Receiver | 接收者,真正执行命令的对象 |
Invoker | 要求命令对象执行请求,可以持有一个或多个命令对象 |
Client | 组装命令对象和接收者 |
参数化配置:可以用不同的命令对象,参数化配置客户的请求。
可撤销操作的实现方式
- 补偿式
- 存储恢复式
宏命令:包含多个命令的命令,是一个命令的组合。
优缺点
优点
- 更松散的耦合
- 可实现动态控制
- 便于复合命令
- 优秀的扩展性
使用场景
- 需抽象出执行的动作并参数化其对象时
- 需在不同时刻指定、排列和执行请求时
- 需支持撤销、恢复和重做操作时
- 需支持系统崩溃时,能将对系统的操作重新执行一遍时
- 需事务的系统,命令模式提供了对事务进行建模的方法
示例
//电脑开机
//示例:命令模式实现电脑开机//Client类
/*** 用命令模式实现开机过程* @author liaocan**/
public class Client {public static void main(String[] args) {//1:把命令和真正的实现组合起来,相当于在组装机器,//把机箱上按钮的连接线插接到主板上。MainBoardApi mainBoard = new GigaMainBoard();OpenCommand openCommand = new OpenCommand(mainBoard);//2:为机箱上的按钮设置对应的命令,让按钮知道该干什么Box box = new Box();box.setOpenCommand(openCommand);//3:然后模拟按下机箱上的按钮box.openButtonPressed();}
}
//电脑主板接口
/*** 主板的接口*/
public interface MainBoardApi {/*** 主板具有能开机的功能*/public void open();
}
//技嘉主板类
/*** 技嘉主板类,开机命令的真正实现者,在Command模式中充当Receiver*/
public class GigaMainBoard implements MainBoardApi{/*** 真正的开机命令的实现*/public void open(){System.out.println("技嘉主板现在正在开机,请等候");System.out.println("接通电源......");System.out.println("设备检查......");System.out.println("装载系统......");System.out.println("机器正常运转起来......");System.out.println("机器已经正常打开,请操作");}
}
//微星主板类
/*** 微星主板类,开机命令的真正实现者,在Command模式中充当Receiver*/
public class MsiMainBoard implements MainBoardApi{/*** 真正的开机命令的实现*/public void open(){System.out.println("微星主板现在正在开机,请等候");System.out.println("接通电源......");System.out.println("设备检查......");System.out.println("装载系统......");System.out.println("机器正常运转起来......");System.out.println("机器已经正常打开,请操作");}
}
//命令接口
/*** 命令接口,声明执行的操作*/
public interface Command {/*** 执行命令对应的操作*/public void execute();
}
//开机命令类
/*** 开机命令的实现,实现Command接口,* 持有开机命令的真正实现,通过调用接收者的方法来实现命令*/
public class OpenCommand implements Command{/*** 持有真正实现命令的接收者——主板对象*/private MainBoardApi mainBoard = null;/*** 构造方法,传入主板对象* @param mainBoard 主板对象*/public OpenCommand(MainBoardApi mainBoard) {this.mainBoard = mainBoard;}public void execute() {//对于命令对象,根本不知道如何开机,会转调主板对象//让主板去完成开机的功能this.mainBoard.open();}
}
//机箱类
/*** 机箱对象,本身有按钮,持有按钮对应的命令对象*/
public class Box {/*** 开机命令对象*/private Command openCommand;/*** 设置开机命令对象* @param command 开机命令对象*/public void setOpenCommand(Command command){this.openCommand = command;}/*** 提供给客户使用,接受并相应用户请求,相当于按钮被按下触发的方法*/public void openButtonPressed(){//按下按钮,执行命令openCommand.execute();}
}//计算器
//示例:命令模式实现计算器加减法运算并提供撤销和恢复操作
//Client类
/*** 命令模式实现计算器功能,支持撤销命令功能(反操作)* @author liaocan**/
public class Client {public static void main(String[] args) {//1:组装命令和接收者//创建接收者OperationApi operation = new Operation();//创建命令对象,并组装命令和接收者AddCommand addCmd = new AddCommand(operation);SubstractCommand substractCmd = new SubstractCommand(operation);//2:把命令设置到持有者,就是计算器里面Calculator calculator = new Calculator();calculator.setAddCmd(addCmd);calculator.setSubstractCmd(substractCmd);//3:模拟按下按钮,测试一下calculator.addPressed(5);System.out.println("一次加法运算后的结果为:"+operation.getResult());calculator.substractPressed(3);System.out.println("一次减法运算后的结果为:"+operation.getResult());//测试撤消calculator.undoPressed();System.out.println("撤销一次后的结果为:"+operation.getResult());calculator.undoPressed();System.out.println("再撤销一次后的结果为:"+operation.getResult());//测试恢复calculator.redoPressed();System.out.println("恢复操作一次后的结果为:"+operation.getResult());calculator.redoPressed();System.out.println("再恢复操作一次后的结果为:"+operation.getResult());/*一次加法运算后的结果为:5一次减法运算后的结果为:2撤销一次后的结果为:5再撤销一次后的结果为:0恢复操作一次后的结果为:5再恢复操作一次后的结果为:2*/}
}
//操作接口
/*** 操作运算的接口*/
public interface OperationApi {/*** 获取计算完成后的结果* @return 计算完成后的结果*/public int getResult();/*** 设置计算开始的初始值* @param result 计算开始的初始值*/public void setResult(int result);/*** 执行加法* @param num 需要加的数*/public void add(int num);/*** 执行减法* @param num 需要减的数*/public void substract(int num);
}
//操作类
/*** 运算类,真正实现加减法运算*/
public class Operation implements 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 {/*** 执行命令对应的操作*/public void execute(int opeNum);/*** 执行撤销命令对应的操作*/public void undo();/*** 执行恢复命令对应的操作*/public void redo();
}
//加法命令
/*** 具体的加法命令实现对象*/
public class AddCommand implements Command{/*** 持有具体执行计算的对象*/private OperationApi operation = null;/*** 操作的数据,也就是要加上的数据*/private int opeNum;/*** 构造方法,传入具体执行计算的对象* @param operation 具体执行计算的对象* @param opeNum 要加上的数据*/public AddCommand(OperationApi operation){this.operation = operation;}public void execute(int opeNum) {this.opeNum = opeNum;//转调接收者去真正执行功能,这个命令是做加法this.operation.add(opeNum);}public void undo() {//转调接收者去真正执行功能//命令本身是做加法,那么撤销的时候就是做减法了this.operation.substract(opeNum);}public void redo() {execute(opeNum);}
}
//减法命令
/*** 具体的减法命令实现对象*/
public class SubstractCommand implements Command{/*** 持有具体执行计算的对象*/private OperationApi operation = null;/*** 操作的数据,也就是要减去的数据*/private int opeNum;/*** 构造方法,传入具体执行计算的对象* @param operation 具体执行计算的对象* @param opeNum 要减去的数据*/public SubstractCommand(OperationApi operation){this.operation = operation;} public void execute(int opeNum) {this.opeNum = opeNum;//转调接收者去真正执行功能,这个命令是做减法this.operation.substract(opeNum);}public void undo() {//转调接收者去真正执行功能//命令本身是做减法,那么撤销的时候就是做加法了this.operation.add(opeNum);}public void redo() {execute(opeNum);}
}
//计算器类
/*** 计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮*/
public class Calculator {/*** 命令的操作的历史记录,在撤销时候用*/private List<Command> undoCmds = new ArrayList<Command>();/*** 命令被撤销的历史记录,在恢复时候用*/private List<Command> redoCmds = new ArrayList<Command>();private Command addCmd = null;private Command substractCmd = null;public void setAddCmd(Command addCmd) {this.addCmd = addCmd;}public void setSubstractCmd(Command substractCmd) {this.substractCmd = substractCmd;} public void addPressed(int opeNum){this.addCmd.execute(opeNum);//把操作记录到历史记录里面undoCmds.add(this.addCmd);}public void substractPressed(int opeNum){this.substractCmd.execute(opeNum);//把操作记录到历史记录里面undoCmds.add(this.substractCmd);}public void undoPressed(){if(this.undoCmds.size()>0){//取出最后一个命令来撤销Command cmd = this.undoCmds.get(this.undoCmds.size()-1);cmd.undo();//如果还有恢复的功能,那就把这个命令记录到恢复的历史记录里面this.redoCmds.add(cmd );//然后把最后一个命令删除掉,this.undoCmds.remove(cmd);}else{System.out.println("很抱歉,没有可撤销的命令");}}public void redoPressed(){if(this.redoCmds.size()>0){//取出最后一个命令来重做Command cmd = this.redoCmds.get(this.redoCmds.size()-1);cmd.redo(); //把这个命令记录到可撤销的历史记录里面this.undoCmds.add(cmd);//然后把最后一个命令删除掉this.redoCmds.remove(cmd);}else{System.out.println("很抱歉,没有可恢复的命令");}}
}
//点菜(宏命令)
//示例:宏命令实现点菜过程
//Client类
/*** 点菜过程(宏命令)* @author liaocan**/
public class Client {public static void main(String[] args) {//只是负责向服务员点菜就好了//创建服务员Waiter waiter = new Waiter();//创建命令对象,就是要点的菜Command chop = new ChopCommand();Command duck = new DuckCommand();Command pork = new PorkCommand();//点菜,就是把这些菜让服务员记录下来waiter.orderDish(chop);waiter.orderDish(duck);waiter.orderDish(pork);//点菜完毕waiter.orderOver();}
}
//服务员
/*** 服务员,负责组合菜单,负责组装每个菜和具体的实现者,* 还负责执行调用,相当于标准Command模式的Client+Invoker*/
public class Waiter {/*** 持有一个宏命令对象——菜单*/private MenuCommand menuCommand = new MenuCommand();/*** 客户点菜* @param cmd 客户点的菜,每道菜是一个命令对象*/public void orderDish(Command cmd){//客户传过来的命令对象是没有和接收者组装的//在这里组装吧CookApi hotCook = new HotCook();CookApi coolCook = new CoolCook();//判读到底是组合凉菜师傅还是热菜师傅//简单点根据命令的原始对象的类型来判断if(cmd instanceof DuckCommand){((DuckCommand)cmd).setCookApi(hotCook);}else if(cmd instanceof ChopCommand){((ChopCommand)cmd).setCookApi(hotCook);}else if(cmd instanceof PorkCommand){//这是个凉菜,所以要组合凉菜的师傅((PorkCommand)cmd).setCookApi(coolCook);}//添加到菜单中menuCommand.addCommand(cmd);}/*** 客户点菜完毕,表示要执行命令了,这里就是执行菜单这个组合命令*/public void orderOver(){this.menuCommand.execute();}
}
//命令接口
/*** 命令接口,声明执行的操作*/
public interface Command {/*** 执行命令对应的操作*/public void execute();
}
//菜品命令
/*** 命令对象,绿豆排骨煲*/
public class ChopCommand implements Command{/*** 持有具体做菜的厨师的对象*/private CookApi cookApi = null;/*** 设置具体做菜的厨师的对象* @param cookApi 具体做菜的厨师的对象*/public void setCookApi(CookApi cookApi) {this.cookApi = cookApi;}public void execute() {this.cookApi.cook("绿豆排骨煲");}
}
//菜品命令
/*** 命令对象,北京烤鸭*/
public class DuckCommand implements Command{private CookApi cookApi = null;public void setCookApi(CookApi cookApi) {this.cookApi = cookApi;}public void execute() {this.cookApi.cook("北京烤鸭");}
}
//菜品命令
/*** 命令对象,蒜泥白肉*/
public class PorkCommand implements Command {private CookApi cookApi = null;public void setCookApi(CookApi cookApi) {this.cookApi = cookApi;}public void execute() {this.cookApi.cook("蒜泥白肉");}
}
//宏命令
/*** 菜单对象,是个宏命令对象*/
public class MenuCommand implements Command {/*** 用来记录组合本菜单的多道菜品,也就是多个命令对象*/private Collection<Command> col = new ArrayList<Command>();/*** 点菜,把菜品加入到菜单中* @param cmd 客户点的菜*/public void addCommand(Command cmd){col.add(cmd);}public void execute() {//执行菜单其实就是循环执行菜单里面的每个菜for(Command cmd : col){cmd.execute();}}
}
//厨师接口
/*** 厨师的接口*/
public interface CookApi {/*** 示意,做菜的方法* @param name 菜名*/public void cook(String name);
}
//凉菜厨师
/*** 厨师对象,做凉菜*/
public class CoolCook implements CookApi {public void cook(String name) {System.out.println("凉菜"+name+"已经做好,本厨师正在装盘。" );}
}
//热菜厨师
/*** 厨师对象,做热菜*/
public class HotCook implements CookApi{public void cook(String name) {System.out.println("本厨师正在做:"+name);}
}
//点菜(队列)
//示例:队列加宏命令实现点菜过程
//Client类
/*** 点菜队列命令的实现* @author liaocan**/
public class Client {public static void main(String[] args) {//先要启动后台,让整个程序运行起来CookManager.runCookManager();//为了简单,直接用循环模拟多个桌号点菜for(int i = 0;i<5;i++){//创建服务员Waiter waiter = new Waiter();//创建命令对象,就是要点的菜Command chop = new ChopCommand(i);Command duck = new DuckCommand(i);//点菜,就是把这些菜让服务员记录下来waiter.orderDish(chop);waiter.orderDish(duck);//点菜完毕waiter.orderOver();} }
}
//后厨管理类
/*** 后厨的管理类,通过此类让后厨的厨师进行运行状态*/
public class CookManager {/*** 用来控制是否需要创建厨师,如果已经创建过了就不要再执行了*/private static boolean runFlag = false;/*** 运行厨师管理,创建厨师对象并启动他们相应的现程,* 无论运行多少次,创建厨师对象和启动线程的工作就只做一次*/public static void runCookManager(){if(!runFlag){runFlag = true;//创建三位厨师HotCook cook1 = new HotCook("张三");HotCook cook2 = new HotCook("李四");HotCook cook3 = new HotCook("王五");//启动他们的线程Thread t1 = new Thread(cook1);t1.start();Thread t2 = new Thread(cook2);t2.start();Thread t3 = new Thread(cook3);t3.start();}}
}
//厨师接口
/*** 厨师的接口*/
public interface CookApi {/*** 示意,做菜的方法* @param tableNum 点菜的桌号* @param name 菜名*/public void cook(int tableNum,String name);
}
//热菜厨师
/*** 厨师对象,做热菜的厨师*/
public class HotCook implements CookApi,Runnable{/*** 厨师姓名*/private String name;/*** 构造方法,传入厨师姓名* @param name 厨师姓名*/public HotCook(String name){this.name = name;}public void cook(int tableNum,String name) {//每次做菜的时间是不一定的,用个随机数来模拟一下int cookTime = (int)(20 * Math.random());System.out.println(this.name+"厨师正在为"+tableNum+"号桌做:"+name);try {//让线程休息这么长时间,表示正在做菜Thread.sleep(cookTime);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(this.name+"厨师为"+tableNum+"号桌做好了:"+name+",共计耗时="+cookTime+"秒");}public void run() {while(true){//到命令队列里面获取命令对象Command cmd = CommandQueue.getOneCommand();if(cmd != null){//说明取到命令对象了,这个命令对象还没有设置接收者//因为前面都还不知道到底哪一个厨师来真正执行这个命令//现在知道了,就是当前厨师实例,设置到命令对象里面cmd.setCookApi(this);//然后真正执行这个命令cmd.execute();}//休息1秒try {Thread.sleep(1000L);} catch (InterruptedException e) {e.printStackTrace();}}}
}
//服务员
/*** 服务员,负责组合菜单,还负责执行调用*/
public class Waiter {/*** 持有一个宏命令对象——菜单*/private MenuCommand menuCommand = new MenuCommand();/*** 客户点菜* @param cmd 客户点的菜,每道菜是一个命令对象*/public void orderDish(Command cmd){//添加到菜单中menuCommand.addCommand(cmd);}/*** 客户点菜完毕,表示要执行命令了,这里就是执行菜单这个组合命令*/public void orderOver(){this.menuCommand.execute();}
}
//命令接口
/*** 命令接口,声明执行的操作*/
public interface Command {/*** 执行命令对应的操作*/public void execute();/*** 设置命令的接收者* @param cookApi 命令的接收者 */public void setCookApi(CookApi cookApi);/*** 返回发起请求的桌号,就是点菜的桌号* @return 发起请求的桌号*/public int getTableNum();
}
//菜单命令
/*** 菜单对象,是个宏命令对象*/
public class MenuCommand implements Command {/*** 用来记录组合本菜单的多道菜品,也就是多个命令对象*/private Collection<Command> col = new ArrayList<Command>();/*** 点菜,把菜品加入到菜单中* @param cmd 客户点的菜*/public void addCommand(Command cmd){col.add(cmd);}public void setCookApi(CookApi cookApi){//什么都不用做}public int getTableNum(){//什么都不用做return 0;}/*** 获取菜单中的多个命令对象* @return 菜单中的多个命令对象*/public Collection<Command> getCommands(){return this.col;} public void execute() {//执行菜单就是把菜单传递给后厨CommandQueue.addMenu(this);}
}
//菜品命令
/*** 命令对象,绿豆排骨煲*/
public class ChopCommand implements Command{/*** 持有具体做菜的厨师的对象*/private CookApi cookApi = null;/*** 设置具体做菜的厨师的对象* @param cookApi 具体做菜的厨师的对象*/public void setCookApi(CookApi cookApi) {this.cookApi = cookApi;}/*** 点菜的桌号*/private int tableNum;/*** 构造方法,传入点菜的桌号* @param tableNum 点菜的桌号*/public ChopCommand(int tableNum){this.tableNum = tableNum;}public int getTableNum(){return this.tableNum;}public void execute() {this.cookApi.cook(tableNum,"绿豆排骨煲");}
}
//菜品命令
/*** 命令对象,北京烤鸭*/
public class DuckCommand implements Command{private CookApi cookApi = null;public void setCookApi(CookApi cookApi) {this.cookApi = cookApi;}private int tableNum;public DuckCommand(int tableNum){this.tableNum = tableNum;}public int getTableNum(){return this.tableNum;}public void execute() {this.cookApi.cook(tableNum,"北京烤鸭");}
}
//命令队列
/*** 命令队列类*/
public class CommandQueue {/*** 用来存储命令对象的队列* 请注意:这里没有使用java.util.Queue,是因为常用的实现Queue接口的LinkedList类要求存放的对象是可排序的,* 排序是使用的Comparator,这跟要演示的功能没有多大关系,反而会增加复杂性。* 另外一个需要的功能是把命令对象按照先后顺序排好就可以了,只要是有序的就可以了。* 因此为了演示的简洁性,就直接使用List了。*/private static List<Command> cmds = new ArrayList<Command>();/*** 服务员传过来一个新的菜单* 需要同步:是因为同时会有很多的服务员传入菜单,而同时又有很多厨师在从队列里取值* @param menu 传入的菜单*/public synchronized static void addMenu(MenuCommand menu){//一个菜单对象包含很多命令对象for(Command cmd : menu.getCommands()){cmds.add(cmd);}}/*** 厨师从命令队列里面获取命令对象进行处理,也是需要同步的*/public synchronized static Command getOneCommand(){Command cmd = null;if(cmds.size() > 0 ){//取出队列的第一个,因为是约定的按照加入的先后来处理cmd = cmds.get(0);//同时从队列里面取掉这个命令对象cmds.remove(0);}return cmd;}
}
迭代器模式
描述
定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。本质是控制访问聚合对象中的元素。
结构
角色 | 说明 |
---|---|
Iterator | 迭代器接口,定义访问和遍历元素的接口 |
ConcreteIterator | 具体的迭代器实现对象,实现对聚合对象的遍历,并跟踪遍历时的当前位置 |
Aggregate | 聚合对象,定义创建相应迭代器对象的接口 |
ConcreteAggregate | 具体聚合对象,实现创建相应的迭代器对象 |
内部迭代器:由迭代器自身控制迭代下一个元素的步骤,客户端无法干预。因此,若需在迭代过程中完成工作,客户端则需将操作传给迭代器,类似于Java的回调机制。
外部迭代器:有客户端控制下一个元素的步骤,如:客户端必须显式调用next方法迭代下一个元素。
优缺点
优点
- 更好的封装性
- 简化聚合接口
- 简化调用
使用场景
- 若需提供访问一个聚合对象的内容,又不暴露它的内部表示时
- 需有多种遍历方式可以访问聚合对象时
- 需为遍历不同的聚合对象提供一个统一的接口
示例
//工资表数据整合
//示例:将母公司和子公司的工资整合起来,统一查看//Client类
/*** 迭代模式的使用:工资表数据整合,一个是用 list ,一个是用 array ,需要以统一的方式访问* @author liaocan**/
public class Client {public static void main(String[] args) {//访问集团的工资列表PayManager payManager= new PayManager();//先计算再获取payManager.calcPay();System.out.println("集团工资列表:");test(payManager.createIterator());//访问新收购公司的工资列表SalaryManager salaryManager = new SalaryManager();//先计算再获取salaryManager.calcSalary();System.out.println("新收购的公司工资列表:");test(salaryManager.createIterator());}/*** 测试通过访问聚合对象的迭代器,是否能正常访问聚合对象* @param it 聚合对象的迭代器*/private static void test(Iterator it){//循环输出聚合对象中的值//首先设置迭代器到第一个元素it.first();while(!it.isDone()){//取出当前的元素来Object obj = it.currentItem();System.out.println("the obj=="+obj);//如果还没有迭代到最后,那么就向下迭代一个it.next();}}
}
//聚合接口
/*** 聚合对象的接口,定义创建相应迭代器对象的接口*/
public abstract class Aggregate {/*** 工厂方法,创建相应迭代器对象的接口* @return 相应迭代器对象的接口*/public abstract Iterator createIterator();
}
//母公司工资类
/*** 客户方已有的工资管理对象*/
public class PayManager extends Aggregate{/*** 聚合对象,这里是Java的集合对象*/private List list = new ArrayList();/*** 获取工资列表* @return 工资列表*/public List 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 Iterator createIterator(){return new CollectionIteratorImpl(this);}public Object get(int index){Object retObj = null;if(index < this.list.size()){retObj = this.list.get(index);}return retObj;}public int size(){return this.list.size();}
}
//子公司工资类
/*** 被客户方收购的那个公司的工资管理类*/
public class SalaryManager extends Aggregate{/*** 用数组管理*/private PayModel[] pms = null;/*** 获取工资列表* @return 工资列表*/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;}public Iterator createIterator(){return new ArrayIteratorImpl(this);}public Object get(int index){Object retObj = null;if(index < pms.length){retObj = pms[index];}return retObj;}public int size(){return this.pms.length;}
}
//迭代器接口
/*** 迭代器接口,定义访问和遍历元素的操作*/
public interface Iterator {/*** 移动到聚合对象的第一个位置*/public void first();/*** 移动到聚合对象的下一个位置*/public void next();/*** 判断是否已经移动聚合对象的最后一个位置* @return true表示已经移动聚合对象的最后一个位置,* false表示还没有移动到聚合对象的最后一个位置*/public boolean isDone();/*** 获取迭代的当前元素* @return 迭代的当前元素*/public Object currentItem();
}
//访问数组迭代器
/*** 用来实现访问数组的迭代接口*/
public class ArrayIteratorImpl implements Iterator{/*** 用来存放被迭代的聚合对象*/private SalaryManager aggregate = null;/*** 用来记录当前迭代到的位置索引* -1表示刚开始的时候,迭代器指向聚合对象第一个对象之前*/private int index = -1;public ArrayIteratorImpl(SalaryManager aggregate){this.aggregate = aggregate;}public void first(){index = 0;}public void next(){if(index < this.aggregate.size()){index = index + 1;}}public boolean isDone(){if(index == this.aggregate.size()){return true;}return false;}public Object currentItem(){return this.aggregate.get(index);}
}
//访问集合迭代器
/*** 用来实现访问Collection集合的迭代接口,为了外部统一访问方式*/
public class CollectionIteratorImpl implements Iterator{/*** 用来存放被迭代的聚合对象*/private PayManager aggregate = null;/*** 用来记录当前迭代到的位置索引* -1表示刚开始的时候,迭代器指向聚合对象第一个对象之前*/private int index = -1;public CollectionIteratorImpl(PayManager aggregate){this.aggregate = aggregate;}public void first(){index = 0;}public void next(){if(index < this.aggregate.size()){index = index + 1;}}public boolean isDone(){if(index == this.aggregate.size()){return true;}return false;}public Object currentItem(){return this.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 String toString(){return "userName="+userName+",pay="+pay;}
}
模板方法模式
描述
定义:定义一个操作中的算法骨架,将部分步骤延迟到子类中实现;使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。本质是固定算法骨架。
结构
角色 | 说明 |
---|---|
AbstractClass | 抽象类,定义算法骨架和原语操作并提供算法中部分步骤的通用实现 |
ConcreteClass | 实现类,实现算法骨架中的某些步骤,完成跟特定子类相关的功能 |
原语操作:在模板中定义的抽象操作,且是必需的,需由子类实现。
钩子操作:在模板中定义并提供默认实现的操作。
优缺点
优点
- 代码复用
缺点
- 算法骨架不易升级
使用场景
- 需固定算法骨架,实现一个算法不变的部分,并将可变的行为由其子类实现
- 各个子类中具有公共行为,应该抽取出来,集中到公共类中实现,从而实现代码复用
- 需控制子类扩展的情况,模板方法模式会在特定时刻调用子类的方法,这样只允许在扩展这些方法
示例
// 回调技术
//示例:字符串转大小写(JDK1.8回调方式)
public class CallbackDemo {public static void adapter(Function<String, String> function) {String message = "Hello World";System.out.println(function.apply(message));}public static void main(String[] args) {Function<String, String> function1 = (str) -> {return str.toUpperCase();};Function<String, String> function2 = (str) -> {return str.toLowerCase();};adapter(function1);adapter(function2);}}
//多种类型用户登陆
//继承实现
//示例:模板方法模式使用继承实现多种类型用户登陆逻辑//Client类
/*** 使用模板模式实现 普通用户与员工 登录功能* @author liaocan**/
public class Client {public static void main(String[] args) {//准备登录人的信息LoginModel lm = new LoginModel();lm.setLoginId("admin");lm.setPwd("workerpwd");//准备用来进行判断的对象LoginTemplate lt = new WorkerLogin();LoginTemplate lt2 = new NormalLogin();//进行登录测试boolean flag = lt.login(lm);System.out.println("可以登录工作平台="+flag);boolean flag2 = lt2.login(lm);System.out.println("可以进行普通人员登录="+flag2);//准备登录人的信息NormalLoginModel nlm = new NormalLoginModel();nlm.setLoginId("testUser");nlm.setPwd("testpwd");nlm.setQuestion("testQuestion");nlm.setAnswer("testAnswer");//准备用来进行判断的对象LoginTemplate lt3 = new NormalLogin2();//进行登录测试boolean flag3 = lt3.login(nlm);System.out.println("可以进行普通人员加强版登录="+flag3);}
}
//登陆模型
/*** 封装进行登录控制所需要的数据*/
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 {/*** 判断登录数据是否正确,也就是是否能登录成功* @param lm 封装登录数据的Model* @return true表示登录成功,false表示登录失败*/public final boolean login(LoginModel lm){//1:根据登录人员的编号去获取相应的数据LoginModel dbLm = this.findLoginUser(lm.getLoginId());if(dbLm!=null){//2:对密码进行加密String encryptPwd = this.encryptPwd(lm.getPwd());//把加密后的密码设置回到登录数据模型里面lm.setPwd(encryptPwd);//3:判断是否匹配return this.match(lm, dbLm);}return false;}/*** 根据登录编号来查找和获取存储中相应的数据* @param loginId 登录编号* @return 登录编号在存储中相对应的数据*/public abstract LoginModel findLoginUser(String loginId);/*** 对密码数据进行加密* @param pwd 密码数据* @return 加密后的密码数据*/public String encryptPwd(String pwd){return pwd;}/*** 判断用户填写的登录数据和存储中对应的数据是否匹配得上* @param lm 用户填写的登录数据* @param dbLm 在存储中对应的数据* @return true表示匹配成功,false表示匹配失败*/public boolean match(LoginModel lm,LoginModel dbLm){if(lm.getLoginId().equals(dbLm.getLoginId()) && lm.getPwd().equals(dbLm.getPwd())){return true;}return false;}
}
//普通登陆实现类
/*** 普通用户登录控制的逻辑处理*/
public class NormalLogin extends LoginTemplate{public LoginModel findLoginUser(String loginId) {// 这里省略具体的处理,仅做示意,返回一个有默认数据的对象LoginModel lm = new LoginModel();lm.setLoginId(loginId);lm.setPwd("testpwd");return lm;}
}
//工作人员登陆实现类
/*** 工作人员登录控制的逻辑处理*/
public class WorkerLogin extends LoginTemplate{public LoginModel findLoginUser(String loginId) {// 这里省略具体的处理,仅做示意,返回一个有默认数据的对象LoginModel lm = new LoginModel();lm.setLoginId(loginId);lm.setPwd("workerpwd");return lm;}public String encryptPwd(String pwd){//覆盖父类的方法,提供真正的加密实现//这里对密码进行加密,比如使用:MD5、3DES等等,省略了System.out.println("使用MD5进行密码加密");return pwd;}
}
//普通登陆加强实现类
/*** 普通用户登录控制加强版的逻辑处理*/
public class NormalLogin2 extends LoginTemplate{public LoginModel findLoginUser(String loginId) {// 这里省略具体的处理,仅做示意,返回一个有默认数据的对象//注意一点:这里使用的是自己需要的数据模型了NormalLoginModel nlm = new NormalLoginModel();nlm.setLoginId(loginId);nlm.setPwd("testpwd");nlm.setQuestion("testQuestion");nlm.setAnswer("testAnswer");return nlm;}public boolean match(LoginModel lm,LoginModel dbLm){//这个方法需要覆盖,因为现在进行登录控制的时候,//需要检测4个值是否正确,而不仅仅是缺省的2个//先调用父类实现好的,检测编号和密码是否正确boolean f1 = super.match(lm, dbLm);if(f1){//如果编号和密码正确,继续检查问题和答案是否正确//先把数据转换成自己需要的数据NormalLoginModel nlm = (NormalLoginModel)lm;NormalLoginModel dbNlm = (NormalLoginModel)dbLm;//检查问题和答案是否正确if(dbNlm.getQuestion().equals(nlm.getQuestion())&& dbNlm.getAnswer().equals(nlm.getAnswer())){return true;}}return false;}}
//普通登陆加强模型
/*** 封装进行登录控制所需要的数据,在公共数据的基础上,* 添加具体模块需要的数据*/
public class NormalLoginModel extends LoginModel{/*** 密码验证问题*/private String question;/*** 密码验证答案*/private String answer;public String getQuestion() {return question;}public void setQuestion(String question) {this.question = question;}public String getAnswer() {return answer;}public void setAnswer(String answer) {this.answer = answer;}}
//回调实现
//示例:模板方法模式使用回调技术实现多种类型用户登陆逻辑
//Client类
/*** 使用 java 的回调机制实现登录功能* @author liaocan**/
public class Client {public static void main(String[] args) {//准备登录人的信息LoginModel lm = new LoginModel();lm.setLoginId("admin");lm.setPwd("workerpwd");//准备用来进行判断的对象LoginTemplate lt = new LoginTemplate();//进行登录测试,先测试普通人员登录boolean flag = lt.login(lm,new LoginCallback(){public String encryptPwd(String pwd, LoginTemplate template) {//自己不需要,直接转调模板中的默认实现return template.encryptPwd(pwd);}public LoginModel findLoginUser(String loginId) {// 这里省略具体的处理,仅做示意,返回一个有默认数据的对象LoginModel lm = new LoginModel();lm.setLoginId(loginId);lm.setPwd("testpwd");return lm;}public boolean match(LoginModel lm, LoginModel dbLm,LoginTemplate template) {//自己不需要覆盖,直接转调模板中的默认实现return template.match(lm, dbLm);}});System.out.println("可以进行普通人员登录="+flag);//测试工作人员登录boolean flag2 = lt.login(lm,new LoginCallback(){public String encryptPwd(String pwd, LoginTemplate template) {//覆盖父类的方法,提供真正的加密实现//这里对密码进行加密,比如使用:MD5、3DES等等,省略了System.out.println("使用MD5进行密码加密");return pwd;}public LoginModel findLoginUser(String loginId) {// 这里省略具体的处理,仅做示意,返回一个有默认数据的对象LoginModel lm = new LoginModel();lm.setLoginId(loginId);lm.setPwd("workerpwd");return lm;}public boolean match(LoginModel lm, LoginModel dbLm,LoginTemplate template) {//自己不需要覆盖,直接转调模板中的默认实现return template.match(lm, dbLm);}}); System.out.println("可以登录工作平台="+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 interface LoginCallback {/*** 根据登录编号来查找和获取存储中相应的数据* @param loginId 登录编号* @return 登录编号在存储中相对应的数据*/public LoginModel findLoginUser(String loginId);/*** 对密码数据进行加密* @param pwd 密码数据* @param template LoginTemplate对象,通过它来调用在* LoginTemplate中定义的公共方法或缺省实现* @return 加密后的密码数据*/public String encryptPwd(String pwd,LoginTemplate template);/*** 判断用户填写的登录数据和存储中对应的数据是否匹配得上* @param lm 用户填写的登录数据* @param dbLm 在存储中对应的数据* @param template LoginTemplate对象,通过它来调用在* LoginTemplate中定义的公共方法或缺省实现* @return true表示匹配成功,false表示匹配失败*/public boolean match(LoginModel lm,LoginModel dbLm,LoginTemplate template);
}
//登陆实现类
/*** 登录控制的模板*/
public class LoginTemplate {/*** 判断登录数据是否正确,也就是是否能登录成功* @param lm 封装登录数据的Model* @param callback LoginCallback对象* @return true表示登录成功,false表示登录失败*/public final boolean login(LoginModel lm,LoginCallback callback){//1:根据登录人员的编号去获取相应的数据LoginModel dbLm = callback.findLoginUser(lm.getLoginId());if(dbLm!=null){//2:对密码进行加密String encryptPwd = callback.encryptPwd(lm.getPwd(),this);//把加密后的密码设置回到登录数据模型里面lm.setPwd(encryptPwd);//3:判断是否匹配return callback.match(lm, dbLm,this);}return false;}/*** 对密码数据进行加密* @param pwd 密码数据* @return 加密后的密码数据*/public String encryptPwd(String pwd){return pwd;}/*** 判断用户填写的登录数据和存储中对应的数据是否匹配得上* @param lm 用户填写的登录数据* @param dbLm 在存储中对应的数据* @return true表示匹配成功,false表示匹配失败*/public boolean match(LoginModel lm,LoginModel dbLm){if(lm.getLoginId().equals(dbLm.getLoginId()) && lm.getPwd().equals(dbLm.getPwd())){return true;}return false;}
}
策略模式
描述
定义:定义一系列封装好的算法并使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。本质是分离算法,选择实现。
结构
角色 | 说明 |
---|---|
Strategy | 策略接口,约束一系列具体的策略算法。Context使用其调用具体的策略实现定义的算法 |
ConcreteStrategy | 具体的策略实现 |
Context | 上下文,负责和具体的策略类交互 |
实现容错恢复:策略模式可以实现容错恢复功能。如:正常情况需将操作日志记录到数据库,若出现异常情况,可以暂时记录到文件,待数据库正常后,再从文件读取日志记录到数据库。保证记录日志功能正常使用。
对于一系列算法的实现上存在公共功能的情况,策略模式有3中实现方式
- 在上下文实现公共功能,让具体的策略算法回调这些方法
- 将策略的接口改造成抽象类,并在其中实现具体算法的公共功能
- 为策略算法定义一个抽象的父类,使其父类实现策略接口和公共功能
优缺点
优点
- 定义一系列算法
- 避免多重条件语句
- 更好的扩展性
缺点
- 客户必须了解各种策略的差异
- 增加了类
- 只适合扁平的算法结构
使用场景
- 出现有相关的类,仅仅是行为有差别的情况
- 出现同一个算法,有多种不同的实现情况
示例
//报价管理(JDK1.5回调技术)
//示例:使用JDK1.5回调技术实现对不同客户的报价管理//Client类
/*** 使用策略模式实现报价逻辑* @author liaocan**/
public class Client {public static void main(String[] args) {//1:选择并创建需要使用的策略对象Strategy strategy = new LargeCustomerStrategy();//2:创建上下文Price ctx = new Price(strategy);//3:计算报价double quote = ctx.quote(1000);System.out.println("向客户报价:"+quote);}
}
//策略接口
/*** 策略,定义计算报价算法的接口*/
public interface Strategy {/*** 计算应报的价格* @param goodsPrice 商品销售原价* @return 计算出来的,应该给客户报的价格*/public double calcPrice(double goodsPrice);
}
//战略合作客户策略实现类
/*** 具体算法实现,为战略合作客户客户计算应报的价格*/
public class CooperateCustomerStrategy implements Strategy{public double calcPrice(double goodsPrice) {System.out.println("对于战略合作客户,统一8折");return goodsPrice*0.8;}
}
//大客户策略实现类
/*** 具体算法实现,为大客户计算应报的价格*/
public class LargeCustomerStrategy implements Strategy{public double calcPrice(double goodsPrice) {System.out.println("对于大客户,统一折扣10%");return goodsPrice*(1-0.1);}
}
//新客户或普通客户策略实现类
/*** 具体算法实现,为新客户或者是普通客户计算应报的价格*/
public class NormalCustomerStrategy implements Strategy{public double calcPrice(double goodsPrice) {System.out.println("对于新客户或者是普通客户,没有折扣");return goodsPrice;}
}
//老客户策略实现类
/*** 具体算法实现,为老客户计算应报的价格*/
public class OldCustomerStrategy implements Strategy{public double calcPrice(double goodsPrice) {System.out.println("对于老客户,统一折扣5%");return goodsPrice*(1-0.05);}
}
//价格管理类
/*** 价格管理,主要完成计算向客户所报价格的功能*/
public class Price {/*** 持有一个具体的策略对象*/private Strategy strategy = null;/*** 构造方法,传入一个具体的策略对象* @param aStrategy 具体的策略对象*/public Price(Strategy aStrategy){this.strategy = aStrategy;} /*** 报价,计算对客户的报价* @param goodsPrice 商品销售原价* @return 计算出来的,应该给客户报的价格*/public double quote(double goodsPrice){return this.strategy.calcPrice(goodsPrice);}
}
//价格管理(JDK1.8回调技术)
//示例:使用JDK1.8回调技术实现对不同客户的报价管理
//Client类
/*** 使用策略模式实现报价逻辑* @author liaocan**/
public class Client {public static void main(String[] args) {//1:创建上下文Price ctx = new Price();//2:计算报价double quote = ctx.quote(1000, StrategyUtil.cooperateCustomerStrategy);System.out.println("向客户报价:"+quote);}}
//价格管理类
/*** 价格管理,主要完成计算向客户所报价格的功能*/
public class Price {/*** 报价,计算对客户的报价* @param goodsPrice 商品销售原价* @param calcPrice 计算函数* @return 计算出来的,应该给客户报的价格*/public double quote(double goodsPrice, Function<Double, Double> calcPrice){return calcPrice.apply(goodsPrice);}
}
//策略工具类
/*** 策略工具类* @author liaocan**/
public class StrategyUtil {private StrategyUtil() {}/*** 具体算法实现,为大客户计算应报的价格*/public static Function<Double, Double> largeCustomerStrategy = (goodsPrice) -> {System.out.println("对于大客户,统一折扣10%");return goodsPrice*(1-0.1);};/*** 具体算法实现,为战略合作客户客户计算应报的价格*/public static Function<Double, Double> cooperateCustomerStrategy = (goodsPrice) -> {System.out.println("对于战略合作客户,统一8折");return goodsPrice*0.8;};/*** 具体算法实现,为新客户或者是普通客户计算应报的价格*/public static Function<Double, Double> normalCustomerStrategy = (goodsPrice) -> {System.out.println("对于新客户或者是普通客户,没有折扣");return goodsPrice;};/*** 具体算法实现,为老客户计算应报的价格*/public static Function<Double, Double> oldCustomerStrategy = (goodsPrice) -> {System.out.println("对于老客户,统一折扣5%");return goodsPrice*(1-0.05);};}
//工资支付
//示例:实现多种工资支付方式的自由切换
//Client类
/*** 使用策略模式实现 复杂的工资支付功能* @author liaocan**/
public class Client {public static void main(String[] args) {//创建相应的支付策略PaymentStrategy strategyRMB = new RMBCash();PaymentStrategy strategyDollar = new DollarCash();//准备小李的支付工资上下文PaymentContext ctx1 = new PaymentContext("小李",5000,strategyRMB);//向小李支付工资ctx1.payNow();//切换一个人,给petter支付工资PaymentContext ctx2 = new PaymentContext("Petter",8000,strategyDollar);ctx2.payNow();//测试新添加的支付方式PaymentStrategy strategyCard = new Card();PaymentContext ctx3 = new PaymentContext2("小王",9000,"010998877656",strategyCard);ctx3.payNow();//测试新添加的支付方式PaymentStrategy strategyCard2 = new Card2("010998877656");PaymentContext ctx4 = new PaymentContext("小张",9000,strategyCard2);ctx4.payNow();}
}
//支付工资策略接口
/*** 支付工资的策略的接口,公司有多种支付工资的算法* 比如:现金、银行卡、现金加股票、现金加期权、美元支付等等*/
public interface PaymentStrategy {/*** 公司给某人真正支付工资* @param ctx 支付工资的上下文,里面包含算法需要的数据*/public void pay(PaymentContext ctx);
}
//人民币现金支付策略实现类
/*** 人民币现金支付*/
public class RMBCash implements PaymentStrategy{public void pay(PaymentContext ctx) {System.out.println("现在给"+ctx.getUserName()+"人民币现金支付"+ctx.getMoney()+"元");}}
//美元现金支付策略实现类
/*** 美元现金支付*/
public class DollarCash implements PaymentStrategy{public void pay(PaymentContext ctx) {System.out.println("现在给"+ctx.getUserName()+"美元现金支付"+ctx.getMoney()+"元");}
}
//支付到银行卡策略实现类
/*** 支付到银行卡*/
public class Card implements PaymentStrategy{public void pay(PaymentContext ctx) {//这个新的算法自己知道要使用扩展的支付上下文,所以强制造型一下PaymentContext2 ctx2 = (PaymentContext2)ctx;System.out.println("现在给"+ctx2.getUserName()+"的"+ctx2.getAccount()+"帐号支付了"+ctx2.getMoney()+"元");//连接银行,进行转帐,就不去管了}
}
//支付到银行卡加强策略实现类
/*** 支付到银行卡*/
public class Card2 implements PaymentStrategy{/*** 帐号信息*/private String account = "";/*** 构造方法,传入帐号信息* @param account 帐号信息*/public Card2(String account){this.account = account;}public void pay(PaymentContext ctx) {System.out.println("现在给"+ctx.getUserName()+"的"+this.account+"帐号支付了"+ctx.getMoney()+"元");//连接银行,进行转帐,就不去管了}
}
//支付工资的上下文实现类
/*** 支付工资的上下文,每个人的工资不同,支付方式也不同*/
public class PaymentContext {/*** 应被支付工资的人员,简单点,用姓名来代替*/private String userName = null;/*** 应被支付的工资的金额*/private double money = 0.0;/*** 支付工资的方式策略的接口*/private PaymentStrategy strategy = null;/*** 构造方法,传入被支付工资的人员,应支付的金额和具体的支付策略* @param userName 被支付工资的人员* @param money 应支付的金额* @param strategy 具体的支付策略*/public PaymentContext(String userName,double money,PaymentStrategy strategy){this.userName = userName;this.money = money;this.strategy = strategy;}/*** 立即支付工资*/public void payNow(){//使用客户希望的支付策略来支付工资this.strategy.pay(this);}public String getUserName() {return userName;}public double getMoney() {return money;}
}
//支付工资的上下文加强实现类
/*** 扩展的支付上下文对象*/
public class PaymentContext2 extends PaymentContext {/*** 银行帐号*/private String account = null;/*** 构造方法,传入被支付工资的人员,应支付的金额和具体的支付策略* @param userName 被支付工资的人员* @param money 应支付的金额* @param account 支付到的银行帐号* @param strategy 具体的支付策略*/public PaymentContext2(String userName,double money,String account,PaymentStrategy strategy){super(userName,money,strategy);this.account = account;}public String getAccount() {return account;}
}
状态模式
描述
定义:允许一个对象在其内部状态改变时改变它的行为。本质是根据状态分离和选择行为。
结构
角色 | 说明 |
---|---|
Context | 环境,也称为上下文;定义客户感兴趣的接口,并维护一个具体处理当前状态的实例对象 |
State | 状态接口,封装与上下文的一个特定状态所对应的行为 |
ConcreteState | 实现状态处理类,每个类实现一个状态的具体处理 |
行为的平行性:各个状态的行为所处的层次是一样的,相互独立且没有关联,根据不同的状态决定平行的哪一个行为。行为是不同的,对应的实现也是不同的;因此,相互之间不可替换。
创建和销毁状态对象的方式
- 需要时再创建,用完后则销毁
- 提前创建并且始终不销毁,如:使用单例
- 采用延迟加载和缓存合用的方式
状态转换控制
- 上下文中处理:规则比较稳定
- 状态处理类中处理:状态的转换取决于前一个状态处理的结果
转移持久化:转移是描述从A状态到B状态的转换变化。持久化则是记录其描述到数据库。
优缺点
优点
- 简化业务逻辑控制
- 分离状态和行为
- 更好的扩展性
- 显式进行状态转换
缺点
- 引入太多的状态类
使用场景
- 一个对象的行为取决于其状态,且其必须在运行时刻根据状态改变其行为
- 一个操作中含有庞大的多分支语句,且这些分支依赖于该对象的状态
示例
//在线投票//上下文转换状态
//示例:实现在线投票
//Client类
/*** 使用状态模式实现投票功能(违背开闭原则)* @author liaocan**/
public class Client {public static void main(String[] args) {VoteManager vm = new VoteManager();for(int i=0;i<9;i++){vm.vote("u1", "A");}}
}
//投票管理类
/*** 投票管理*/
public class VoteManager {/*** 持有状态处理对象*/private VoteState state = null;/*** 记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项>*/private Map<String,String> mapVote = new HashMap<String,String>();/*** 记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数>*/private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();/*** 获取记录用户投票结果的Map* @return 记录用户投票结果的Map*/public Map<String, String> getMapVote() {return mapVote;}/*** 投票* @param user 投票人,为了简单,就是用户名称* @param voteItem 投票的选项*/public void vote(String user,String voteItem){//1:先为该用户增加投票的次数//先从记录中取出已有的投票次数Integer oldVoteCount = mapVoteCount.get(user);if(oldVoteCount==null){oldVoteCount = 0;}oldVoteCount = oldVoteCount + 1;mapVoteCount.put(user, oldVoteCount);//2:判断该用户投票的类型,就相当于是判断对应的状态//到底是正常投票、重复投票、恶意投票还是上黑名单的状态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 interface VoteState {/*** 处理状态对应的行为* @param user 投票人* @param voteItem 投票项* @param voteManager 投票上下文,用来在实现状态对应的功能处理的时候,* 可以回调上下文的数据*/public void vote(String user,String voteItem,VoteManager voteManager);
}
//正常投票状态类
public class NormalVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//正常投票//记录到投票记录中voteManager.getMapVote().put(user, voteItem);System.out.println("恭喜你投票成功");}
}
//重复投票状态类
public class RepeatVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//重复投票//暂时不做处理System.out.println("请不要重复投票");}
}
//恶意投票状态类
public class SpiteVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//恶意投票//取消用户的投票资格,并取消投票记录String s = voteManager.getMapVote().get(user);if(s!=null){voteManager.getMapVote().remove(user);}System.out.println("你有恶意刷票行为,取消投票资格");}
}
//黑名单状态类
public class BlackVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//黑名单//记入黑名单中,禁止登录系统了System.out.println("进入黑名单,将禁止登录和使用本系统");}
}
//状态处理类转换状态
//示例:实现在线投票
//Client类
public class Client {public static void main(String[] args) {VoteManager vm = new VoteManager();for(int i=0;i<10;i++){vm.vote("u1", "A");}}
}
//投票管理类
/*** 投票管理*/
public class VoteManager {/*** 记录当前每个用户对应的状态处理对象,每个用户当前的状态是不同的* Map<String,VoteState>对应Map<用户名称,当前对应的状态处理对象>*/private Map<String,VoteState> mapState = new HashMap<String,VoteState>();/*** 记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项>*/private Map<String,String> mapVote = new HashMap<String,String>();/*** 记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数>*/private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();/*** 获取记录用户投票结果的Map* @return 记录用户投票结果的Map*/public Map<String, String> getMapVote() {return mapVote;}/*** 获取记录每个用户对应的状态处理对象的Map* @return 记录每个用户对应的状态处理对象的Map*/public Map<String, VoteState> getMapState() {return mapState;}/*** 获取记录每个用户对应的投票次数的Map* @return 记录每个用户对应的投票次数的Map*/public Map<String, Integer> getMapVoteCount() {return mapVoteCount;}/*** 投票* @param user 投票人,为了简单,就是用户名称* @param voteItem 投票的选项*/public void vote(String user,String voteItem){//1:先为该用户增加投票的次数//先从记录中取出已有的投票次数Integer oldVoteCount = mapVoteCount.get(user);if(oldVoteCount==null){oldVoteCount = 0;}oldVoteCount = oldVoteCount + 1;mapVoteCount.put(user, oldVoteCount);//2:获取该用户的投票状态VoteState state = mapState.get(user);//如果没有投票状态,说明还没有投过票,就初始化一个正常投票状态if(state==null){state = new NormalVoteState();}//然后转调状态对象来进行相应的操作state.vote(user, voteItem, this);}
}
//状态接口
/*** 封装一个投票状态相关的行为*/
public interface VoteState {/*** 处理状态对应的行为* @param user 投票人* @param voteItem 投票项* @param voteManager 投票上下文,用来在实现状态对应的功能处理的时候,* 可以回调上下文的数据*/public void vote(String user,String voteItem,VoteManager voteManager);
}
//正常投票状态类
public class NormalVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//正常投票//记录到投票记录中voteManager.getMapVote().put(user, voteItem);System.out.println("恭喜你投票成功");//正常投票完成,维护下一个状态,同一个人再投票就重复了voteManager.getMapState().put(user, new RepeatVoteState());}
}
//重复投票状态类
public class RepeatVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//重复投票//暂时不做处理System.out.println("请不要重复投票");//重复投票完成,维护下一个状态,重复投票到5次,就算恶意投票了//注意这里是判断大于等于4,因为这里设置的是下一个状态//下一个操作次数就是5了,就应该算是恶意投票了if(voteManager.getMapVoteCount().get(user) >= 4){voteManager.getMapState().put(user, new SpiteVoteState());}}
}
//恶意投票状态类
public class SpiteVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//恶意投票//取消用户的投票资格,并取消投票记录String s = voteManager.getMapVote().get(user);if(s!=null){voteManager.getMapVote().remove(user);}System.out.println("你有恶意刷票行为,取消投票资格");//恶意投票完成,维护下一个状态,投票到8次,就进黑名单了//注意这里是判断大于等于7,因为这里设置的是下一个状态//下一个操作次数就是8了,就应该算是进黑名单了if(voteManager.getMapVoteCount().get(user) >= 7){voteManager.getMapState().put(user, new BlackVoteState());}}
}
//黑名单状态类
public class BlackVoteState implements VoteState{public void vote(String user, String voteItem, VoteManager voteManager) {//黑名单//记入黑名单中,禁止登录系统了System.out.println("进入黑名单,将禁止登录和使用本系统");}
}
备忘录模式
描述
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存。这样以后就可将该对象恢复到原先保存的状态。本质是保存和恢复内部状态。
结构
角色 | 说明 |
---|---|
Memento | 备忘录,存储原发器对象的内部状态,但具体存储的数据是由原发器对象决定的。另外,备忘录应该只能由原发器对象访问它内部的数据 |
Originator | 原发器,使用备忘录保存某个时刻其自身的状态,也可以使用备忘录恢复其内部状态 |
Caretaker | 备忘录管理者,主责保存备忘录对象,但不能对备忘录对象的内容进行操作或检查 |
窄接口:管理者只能看到备忘录的窄接口;窄接口没有任务的方法,只是一个类型标识,使得管理者只能将备忘录传递给其它对象。
宽接口:原发器能够看到一个宽接口,允许它访问所需的所有数据,以返回到先前的状态。
优缺点
优点
- 更好的封装性
- 简化原发器
- 保证安全性(窄接口和宽接口)
缺点
- 可能会导致高的开销(若需缓存备忘录对象)
使用场景
- 须保存一个对象在某一个时刻的全部或部分状态
- 需保存一个对象的内部状态并且保证其安全性
示例
//仿真系统
//示例:实现仿真系统//Client类
/*** 使用备忘录模式和原型模式实现仿真系统* @author liaocan**/
public class Client {public static void main(String[] args) {// 创建模拟运行流程的对象FlowAMockPrototype mock = new FlowAMockPrototype("TestFlow"); // //原型模式//运行流程的第一个阶段mock.runPhaseOne();//创建一个管理者FlowAMementoFileCareTaker careTaker = new FlowAMementoFileCareTaker(); // 离线存储//创建此时对象的备忘录对象,并保存到管理者对象那里,后面要用FlowAMockMemento memento = mock.createMemento();careTaker.saveMemento(memento);//按照方案一来运行流程后半部分mock.schema1();//从管理者获取备忘录对象,然后设置回去,//让模拟运行流程的对象自己恢复自己的内部状态mock.setMemento(careTaker.retriveMemento());//按照方案二来运行流程后半部分mock.schema2();}
}
//共同流程分支实现类
/*** 模拟运行流程A,只是一个示意,代指某个具体流程*/
public class FlowAMockPrototype implements Cloneable, Serializable {/*** 流程名称,不需要外部存储的状态数据*/private String flowName;/*** 示意,代指某个中间结果,需要外部存储的状态数据*/private int tempResult;/*** 示意,代指某个中间结果,需要外部存储的状态数据*/private String tempState;/*** 构造方法,传入流程名称* @param flowName 流程名称*/public FlowAMockPrototype(String flowName){this.flowName = flowName;}/*** 示意,运行流程的第一个阶段*/public void runPhaseOne(){//在这个阶段,可能产生了中间结果,示意一下tempResult = 3;tempState = "PhaseOne";}/*** 示意,按照方案一来运行流程后半部分*/public void schema1(){//示意,需要使用第一个阶段产生的数据this.tempState += ",Schema1";System.out.println(this.tempState + " : now run "+tempResult);this.tempResult += 11;}/*** 示意,按照方案二来运行流程后半部分*/public void schema2(){//示意,需要使用第一个阶段产生的数据this.tempState += ",Schema2";System.out.println(this.tempState + " : now run "+tempResult);this.tempResult += 22;} /*** 创建保存原发器对象的状态的备忘录对象* @return 创建好的备忘录对象*/public FlowAMockMemento createMemento() {try {return new MementoImplPrototype((FlowAMockPrototype) this.clone());} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;}/*** 重新设置原发器对象的状态,让其回到备忘录对象记录的状态* @param memento 记录有原发器状态的备忘录对象*/public void setMemento(FlowAMockMemento memento) {MementoImplPrototype mementoImpl = (MementoImplPrototype)memento;this.tempResult = mementoImpl.getFlowAMock().tempResult;this.tempState = mementoImpl.getFlowAMock().tempState;}/*** 真正的备忘录对象,实现备忘录窄接口* 实现成私有的内部类,不让外部访问*/private static class MementoImplPrototype implements FlowAMockMemento{private FlowAMockPrototype flowAMock = null;public MementoImplPrototype(FlowAMockPrototype f){this.flowAMock = f;}public FlowAMockPrototype getFlowAMock() {return flowAMock;}}
}
//备忘录对象
/*** 负责在文件中保存模拟运行流程A的对象的备忘录对象*/
public class FlowAMementoFileCareTaker {/*** 保存备忘录对象* @param memento 被保存的备忘录对象*/public void saveMemento(FlowAMockMemento memento){//写到文件中ObjectOutputStream out = null;try{out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("FlowAMemento")));out.writeObject(memento);}catch(Exception err){err.printStackTrace();}finally{try {out.close();} catch (IOException e) {e.printStackTrace();}}}/*** 获取被保存的备忘录对象* @return 被保存的备忘录对象*/public FlowAMockMemento retriveMemento(){FlowAMockMemento memento = null;//从文件中获取备忘录数据ObjectInputStream in = null;try{in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("FlowAMemento")));memento = (FlowAMockMemento)in.readObject();}catch(Exception err){err.printStackTrace();}finally{try {in.close();} catch (IOException e) {e.printStackTrace();}}return memento;}
}
//备忘录接口
/*** 模拟运行流程A的对象的备忘录接口,是个窄接口*/
public interface FlowAMockMemento extends Serializable{//空的
}
//原发器对象
/*** 模拟运行流程A,只是一个示意,代指某个具体流程*/
public class FlowAMock implements Serializable {/*** 流程名称,不需要外部存储的状态数据*/private String flowName;/*** 示意,代指某个中间结果,需要外部存储的状态数据*/private int tempResult;/*** 示意,代指某个中间结果,需要外部存储的状态数据*/private String tempState;/*** 构造方法,传入流程名称* @param flowName 流程名称*/public FlowAMock(String flowName){this.flowName = flowName;}/*** 示意,运行流程的第一个阶段*/public void runPhaseOne(){//在这个阶段,可能产生了中间结果,示意一下tempResult = 3;tempState = "PhaseOne";}/*** 示意,按照方案一来运行流程后半部分*/public void schema1(){//示意,需要使用第一个阶段产生的数据this.tempState += ",Schema1";System.out.println(this.tempState + " : now run "+tempResult);this.tempResult += 11;}/*** 示意,按照方案二来运行流程后半部分*/public void schema2(){//示意,需要使用第一个阶段产生的数据this.tempState += ",Schema2";System.out.println(this.tempState + " : now run "+tempResult);this.tempResult += 22;} /*** 创建保存原发器对象的状态的备忘录对象* @return 创建好的备忘录对象*/public FlowAMockMemento createMemento() {return new MementoImpl(this.tempResult,this.tempState);}/*** 重新设置原发器对象的状态,让其回到备忘录对象记录的状态* @param memento 记录有原发器状态的备忘录对象*/public void setMemento(FlowAMockMemento memento) {MementoImpl mementoImpl = (MementoImpl)memento;this.tempResult = mementoImpl.getTempResult();this.tempState = mementoImpl.getTempState();}/*** 真正的备忘录对象,实现备忘录窄接口* 实现成私有的内部类,不让外部访问*/private static class MementoImpl implements 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;}}
}
//计算器
//示例:命令模式和备忘录模式实现具备撤销和恢复功能的计算器
//Client类
/*** 使用备忘录模式实现计算器的撤销功能(存储恢复式)* @author liaocan**/
public class Client {public static void main(String[] args) {//1:组装命令和接收者//创建接收者OperationApi operation = new Operation();//创建命令AddCommand addCmd = new AddCommand(5);SubstractCommand substractCmd = new SubstractCommand(3);//组装命令和接收者addCmd.setOperation(operation);substractCmd.setOperation(operation);//2:把命令设置到持有者,就是计算器里面Calculator calculator = new Calculator();calculator.setAddCmd(addCmd);calculator.setSubstractCmd(substractCmd);//3:模拟按下按钮,测试一下calculator.addPressed();System.out.println("一次加法运算后的结果为:"+operation.getResult());calculator.substractPressed();System.out.println("一次减法运算后的结果为:"+operation.getResult());//测试撤消calculator.undoPressed();System.out.println("撤销一次后的结果为:"+operation.getResult());calculator.undoPressed();System.out.println("再撤销一次后的结果为:"+operation.getResult());//测试恢复calculator.redoPressed();System.out.println("恢复操作一次后的结果为:"+operation.getResult());calculator.redoPressed();System.out.println("再恢复操作一次后的结果为:"+operation.getResult());}
}
//操作运算接口
/*** 操作运算的接口*/
public interface OperationApi {/*** 获取计算完成后的结果* @return 计算完成后的结果*/public int getResult();/*** 执行加法* @param num 需要加的数*/public void add(int num);/*** 执行减法* @param num 需要减的数*/public void substract(int num);/*** 创建保存原发器对象的状态的备忘录对象* @return 创建好的备忘录对象*/public Memento createMemento();/*** 重新设置原发器对象的状态,让其回到备忘录对象记录的状态* @param memento 记录有原发器状态的备忘录对象*/public void setMemento(Memento memento);
}
//操作运算实现类
/*** 运算类,真正实现加减法运算*/
public class Operation implements OperationApi{/*** 记录运算的结果*/private int result;public int getResult() {return result;}public void add(int num){result += num;}public void substract(int num){result -= num;}public Memento createMemento() {MementoImpl m = new MementoImpl(result);return m;}public void setMemento(Memento memento) {MementoImpl m = (MementoImpl)memento;this.result = m.getResult();}/*** 备忘录对象*/private static class MementoImpl implements Memento{private int result = 0;public MementoImpl(int result){this.result = result;}public int getResult() {return result;}}
}
//命令接口
/*** 定义一个命令的接口*/
public interface Command {/*** 执行命令*/public void execute();/*** 撤销命令,恢复到备忘录对象记录的状态* @param m 备忘录对象*/public void undo(Memento m);/*** 重做命令,恢复到备忘录对象记录的状态* @param m 备忘录对象*/public void redo(Memento m);/*** 创建保存原发器对象的状态的备忘录对象* @return 创建好的备忘录对象*/public Memento createMemento();
}
//命令抽象类
/*** 命令对象的公共对象,实现各个命令对象的公共方法*/
public abstract class AbstractCommand implements Command{/*** 具体的功能实现,这里不管*/public abstract void execute();/*** 持有真正的命令实现者对象*/protected OperationApi operation = null;public void setOperation(OperationApi operation) {this.operation = operation;}public Memento createMemento() {return this.operation.createMemento();}public void redo(Memento m) {this.operation.setMemento(m);}public void undo(Memento m) {this.operation.setMemento(m);}
}
//加法命令实现类
public class AddCommand extends AbstractCommand{private int opeNum;public AddCommand(int opeNum){this.opeNum = opeNum;} public void execute() {this.operation.add(opeNum);}
}
//减法命令实现类
public class SubstractCommand extends AbstractCommand{private int opeNum;public SubstractCommand(int opeNum){this.opeNum = opeNum;}public void execute() {this.operation.substract(opeNum);}}
//计算器类
/*** 计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮*/
public class Calculator {/*** 命令的操作的历史记录,在撤销时候用*/private List<Command> undoCmds = new ArrayList<Command>();/*** 命令被撤销的历史记录,在恢复时候用*/private List<Command> redoCmds = new ArrayList<Command>();/*** 命令操作对应的备忘录对象的历史记录,在撤销时候用* 由于对于每个命令对象,撤销和重做的状态是不一样的,* 撤销是回到命令操作前的状态,而重做是回到命令操作后的状态,* 因此对每一个命令,使用一个备忘录对象的数组来记录对应的状态*/private List<Memento[]> undoMementos = new ArrayList<Memento[]>();/*** 被撤销命令对应的备忘录对象的历史记录,在恢复时候用,* 数组有两个元素,第一个是命令执行前的状态,第二个是命令执行后的状态*/private List<Memento[]> redoMementos = new ArrayList<Memento[]>();private Command addCmd = null;private Command substractCmd = null;public void setAddCmd(Command addCmd) {this.addCmd = addCmd;}public void setSubstractCmd(Command substractCmd) {this.substractCmd = substractCmd;} public void addPressed(){//获取对应的备忘录对象,并保存在相应的历史记录里面Memento m1 = this.addCmd.createMemento();//执行命令this.addCmd.execute();//把操作记录到历史记录里面undoCmds.add(this.addCmd);//获取执行命令后的备忘录对象Memento m2 = this.addCmd.createMemento();//设置到撤销的历史记录里面this.undoMementos.add(new Memento[]{m1,m2});}public void substractPressed(){//获取对应的备忘录对象,并保存在相应的历史记录里面 Memento m1 = this.substractCmd.createMemento();//执行命令this.substractCmd.execute();//把操作记录到历史记录里面undoCmds.add(this.substractCmd);//获取执行命令后的备忘录对象Memento m2 = this.substractCmd.createMemento();//设置到撤销的历史记录里面this.undoMementos.add(new Memento[]{m1,m2});}public void undoPressed(){if(undoCmds.size()>0){//取出最后一个命令来撤销Command cmd = undoCmds.get(undoCmds.size()-1);//获取对应的备忘录对象Memento[] ms = undoMementos.get(undoCmds.size()-1);//撤销cmd.undo(ms[0]);//如果还有恢复的功能,那就把这个命令记录到恢复的历史记录里面redoCmds.add(cmd);//把相应的备忘录对象也添加过去redoMementos.add(ms);//然后把最后一个命令删除掉,undoCmds.remove(cmd);//把相应的备忘录对象也删除掉undoMementos.remove(ms);}else{System.out.println("很抱歉,没有可撤销的命令");}}public void redoPressed(){if(redoCmds.size()>0){//取出最后一个命令来重做Command cmd = redoCmds.get(redoCmds.size()-1);//获取对应的备忘录对象Memento[] ms = redoMementos.get(redoCmds.size()-1);//重做cmd.redo(ms[1]);//把这个命令记录到可撤销的历史记录里面undoCmds.add(cmd);//把相应的备忘录对象也添加过去undoMementos.add(ms);//然后把最后一个命令删除掉redoCmds.remove(cmd);//把相应的备忘录对象也删除掉redoMementos.remove(ms);}else{System.out.println("很抱歉,没有可恢复的命令");}}
}
//备忘录接口
public interface Memento {//空的
}
解释器模式
描述
定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示解释语言中的句子。本质是分离实现,解释执行。
结构
角色 | 说明 |
---|---|
AbstractExpression | 解释器接口,约定解释器的解释操作 |
TerminalExpression | 终结符解释器,实现语法规则中和终结符相关的操作 |
NonterminalExpression | 非终结符解释器 |
Context | 上下文,包含各个解释器需要的数据或是公共功能 |
Client | 客户端 |
解析器:将表达式解析转换为解释器需要的抽象语法树。
优缺点
优点
- 易于实现语法
- 易于扩展语法
缺点
- 不适合复杂的语法
使用场景
- 有语言需要解释执行
示例
//示例:模拟XPath解析XML(解析器+解释器)
//Client类
/*** 解释器模式实现用表达式取xml元素值(增加了解析器)* @author liaocan**/
public class Client {public static void main(String[] args) throws Exception {//准备上下文Context c = new Context("InterpreterTest.xml");//通过解析器获取抽象语法树ReadXmlExpression re = Parser.parse("root/a/b/d$.id$");//请求解析,获取返回值String ss[] = re.interpret(c);for (String s : ss) {System.out.println("d的属性id值是=" + s);}//如果要使用同一个上下文,连续进行解析,需要重新初始化上下文对象c.reInit();ReadXmlExpression re2 = Parser.parse("root/a/b/d$");//请求解析,获取返回值String ss2[] = re2.interpret(c);for (String s : ss2) {System.out.println("d的值是=" + s);}}
}
//上下文
/*** 上下文,用来包含解释器需要的一些全局信息*/
public class Context {/*** Dom解析Xml的Document对象*/private Document document = null;/*** 上一次被处理的多个元素*/private List<Element> preEles = new ArrayList<Element>();/*** 构造方法* @param filePathName 需要读取的xml的路径和名字* @throws Exception*/public Context(String filePathName) throws Exception{//通过辅助的Xml工具类来获取被解析的xml对应的Document对象this.document = XmlUtil.getRoot(filePathName);}/*** 重新初始化上下文*/public void reInit(){preEles = new ArrayList<Element>();}/*** 各个Expression公共使用的方法,* 根据父元素和当前元素的名称来获取当前的多个元素的集合* @param pEle 父元素 * @param eleName 当前元素的名称* @return 当前的多个元素的集合*/public List<Element> getNowEles(Element pEle,String eleName){List<Element> elements = new ArrayList<Element>();NodeList tempNodeList = pEle.getChildNodes();for(int i=0;i<tempNodeList.getLength();i++){if(tempNodeList.item(i) instanceof Element){Element nowEle = (Element)tempNodeList.item(i);if(nowEle.getTagName().equals(eleName)){elements.add(nowEle);}}}return elements;}public Document getDocument() {return document;}public List<Element> getPreEles() {return preEles;}public void setPreEles(List<Element> nowEles) {this.preEles = nowEles;}
}
//解析器
/*** 根据语法来解析表达式,转换成为相应的抽象语法树*/
public class Parser {/*** 私有化构造器,避免外部无谓的创建对象实例*/private Parser(){//}//定义几个常量,内部使用private final static String BACKLASH = "/";private final static String DOT = ".";private final static String DOLLAR = "$";/*** 按照分解的先后记录需要解析的元素的名称*/private static List<String> listEle = null;/*** 传入一个字符串表达式,通过解析,组合成为一个抽象的语法树* @param expr 描述要取值的字符串表达式* @return 对应的抽象语法树*/public static ReadXmlExpression parse(String expr){//先初始化记录需解析的元素的名称的集会listEle = new ArrayList<String>();//第一步:分解表达式,得到需要解析的元素名称和该元素对应的解析模型Map<String,ParserModel> mapPath = parseMapPath(expr);//第二步:根据节点的属性转换成为相应的解释器对象List<ReadXmlExpression> list = mapPath2Interpreter(mapPath);//第三步:组合抽象语法树,一定要按照先后顺序来组合,//否则对象的包含关系就乱了ReadXmlExpression returnRe = buildTree(list);return returnRe; }/*----------------------开始实现第一步-----------------------*/ /*** 按照从左到右顺序来分解表达式,得到需要解析的元素名称,* 还有该元素对应的解析模型* @param expr 需要分解的表达式* @return 得到需要解析的元素名称,还有该元素对应的解析模型*/private static Map<String,ParserModel> parseMapPath(String expr){//先按照/分割字符串StringTokenizer tokenizer = new StringTokenizer(expr, BACKLASH);//初始化一个map用来存放分解出来的值Map<String,ParserModel> mapPath = new HashMap<String,ParserModel>();while (tokenizer.hasMoreTokens()) {String onePath = tokenizer.nextToken();if (tokenizer.hasMoreTokens()) {//还有下一个值,说明这不是最后一个元素//按照现在的语法,属性必然在最后,因此也不是属性setParsePath(false,onePath,false,mapPath);} else {//说明到最后了int dotIndex = onePath.indexOf(DOT);if (dotIndex > 0) {//说明是要获取属性的值,那就按照"."来分割,前面的就是元素名字,后面的是属性的名字String eleName = onePath.substring(0, dotIndex);String propName = onePath.substring(dotIndex + 1);//设置属性前面的那个元素,自然不是最后一个,也不是属性setParsePath(false,eleName,false,mapPath);//设置属性,按照现在的语法定义,属性只能是最后一个setParsePath(true,propName,true,mapPath);} else {//说明是取元素的值,而且是最后一个元素的值setParsePath(true,onePath,false,mapPath);}break;}}return mapPath;}/*** 按照分解出来的位置和名称来设置需要解析的元素名称,* 还有该元素对应的解析模型* @param end 是否是最后一个* @param ele 元素名称* @param propertyValue 是否是取属性* @param mapPath 设置需要解析的元素名称,还有该元素对应的解析模型的Map对象*/private static void setParsePath(boolean end,String ele,boolean propertyValue,Map<String,ParserModel> mapPath){ParserModel pm = new ParserModel();pm.setEnd(end);//如果带有$符号就说明不是一个值pm.setSingleVlaue(!(ele.indexOf(DOLLAR)>0));pm.setPropertyValue(propertyValue); //去掉$ele = ele.replace(DOLLAR, "");mapPath.put(ele,pm);listEle.add(ele);}
/*----------------------第一步实现结束-----------------------*//*----------------------开始实现第二步-----------------------*/ /*** 把分解出来的元素名称,根据对应的解析模型转换成为相应的解释器对象* @param mapPath 分解出来的需要解析的元素名称,还有该元素对应的解析模型* @return 把每个元素转换成为相应的解释器对象后的集合*/private static List<ReadXmlExpression> mapPath2Interpreter(Map<String,ParserModel> mapPath){List<ReadXmlExpression> list = new ArrayList<ReadXmlExpression>();//一定要按照分解的先后顺序来转换成解释器对象for(String key : listEle){ParserModel pm = mapPath.get(key);ReadXmlExpression obj = null;if(!pm.isEnd()){if(pm.isSingleVlaue()){//不是最后一个,是一个值,转化为obj = new ElementExpression(key); }else{//不是最后一个,是多个值,转化为obj = new ElementsExpression(key);}}else{if(pm.isPropertyValue()){if(pm.isSingleVlaue()){//是最后一个,是一个值,取属性的值,转化为obj = new PropertyTerminalExpression(key);}else{//是最后一个,是多个值,取属性的值,转化为obj = new PropertysTerminalExpression(key);}}else{if(pm.isSingleVlaue()){//是最后一个,是一个值,取元素的值,转化为obj = new ElementTerminalExpression(key);}else{//是最后一个,是多个值,取元素的值,转化为obj = new ElementsTerminalExpression(key);}}}//把转换后的对象添加到集合中list.add(obj);}return list;}
/*----------------------第二步实现结束-----------------------*/ /*----------------------开始实现第三步-----------------------*/ private static ReadXmlExpression buildTree(List<ReadXmlExpression> list){//第一个对象,也是返回去的对象,就是抽象语法树的根ReadXmlExpression returnRe = null;//定义上一个对象ReadXmlExpression preRe = null;for(ReadXmlExpression re : list){ if(preRe==null){//说明是第一个元素preRe = re;returnRe = re;}else{//把元素添加到上一个对象下面,同时把本对象设置成为oldRe,作为下一个对象的父结点if(preRe instanceof ElementExpression){ElementExpression ele = (ElementExpression)preRe;ele.addEle(re);preRe = re;}else if(preRe instanceof ElementsExpression){ElementsExpression eles = (ElementsExpression)preRe;eles.addEle(re);preRe = re;}}}return returnRe;}
/*----------------------第三步实现结束-----------------------*/
}
//解释器接口
/*** 用于处理自定义Xml取值表达式的接口*/
public abstract class ReadXmlExpression {/*** 解释表达式* @param c 上下文* @return 解析过后的值,为了通用,可能是单个值,也可能是多个值,* 因此就返回一个数组*/public abstract String[] interpret(Context c);
}
//单元素解释器
/*** 单个元素作为非终结符的解释器*/
public class ElementExpression extends ReadXmlExpression{/*** 用来记录组合的ReadXmlExpression元素*/private Collection<ReadXmlExpression> eles = new ArrayList<ReadXmlExpression>();/*** 元素的名称*/private String eleName = "";public ElementExpression(String eleName){this.eleName = eleName;}public boolean addEle(ReadXmlExpression ele){this.eles.add(ele);return true;}public boolean removeEle(ReadXmlExpression ele){this.eles.remove(ele);return true;}public String[] interpret(Context c) {//先取出上下文里的父级元素List<Element> pEles = c.getPreEles();Element ele = null;//把当前获取的元素放到上下文里面List<Element> nowEles = new ArrayList<Element>(); if(pEles.size()==0){//说明现在获取的是根元素ele = c.getDocument().getDocumentElement();pEles.add(ele);c.setPreEles(pEles);}else{for(Element tempEle : pEles){nowEles.addAll(c.getNowEles(tempEle, eleName));if(nowEles.size()>0){//找到一个就停止break;}}List<Element> tempList = new ArrayList<Element>();tempList.add(nowEles.get(0));c.setPreEles(tempList);}//循环调用子元素的interpret方法String [] ss = null;for(ReadXmlExpression tempEle : eles){ss = tempEle.interpret(c);}return ss;}
}
//多元素解释器
/*** 多个元素做为非终结符的解释处理对象*/
public class ElementsExpression extends ReadXmlExpression{/*** 用来记录组合的ReadXmlExpression元素*/private Collection<ReadXmlExpression> eles = new ArrayList<ReadXmlExpression>();/*** 元素名字*/private String eleName = "";public ElementsExpression(String eleName){this.eleName = eleName;}public String[] interpret(Context c) {//先取出上下文里的父级元素List<Element> pEles = c.getPreEles();//把当前获取的元素放到上下文里面,这次是获取多个元素List<Element> nowEles = new ArrayList<Element>();for(Element ele : pEles){nowEles.addAll(c.getNowEles(ele, eleName));}c.setPreEles(nowEles);//循环调用子元素的interpret方法String [] ss = null;for(ReadXmlExpression ele : eles){ss = ele.interpret(c);}return ss;}public boolean addEle(ReadXmlExpression ele){this.eles.add(ele);return true;}public boolean removeEle(ReadXmlExpression ele){this.eles.remove(ele);return true;}
}
//单元素终结符解释器
/*** 元素作为终结符对应的解释器*/
public class ElementTerminalExpression extends ReadXmlExpression{/*** 元素的名字*/private String eleName = "";public ElementTerminalExpression(String name){this.eleName = name;}public String[] interpret(Context c) {//先取出上下文里的当前元素作为父级元素List<Element> pEles = c.getPreEles();//查找到当前元素名称所对应的xml元素Element ele = null;if(pEles.size() == 0){//说明现在获取的是根元素ele = c.getDocument().getDocumentElement();}else{//获取当前的元素ele = c.getNowEles(pEles.get(0), eleName).get(0);}//然后需要去获取这个元素的值String[] ss = new String[1];ss[0] = ele.getFirstChild().getNodeValue();return ss;}
}
//多元素终结符解释器
/*** 以多个元素作为终结符的解释处理对象*/
public class ElementsTerminalExpression extends ReadXmlExpression{/*** 元素的名称*/private String eleName = "";public ElementsTerminalExpression(String name){this.eleName = name;}public String[] interpret(Context c) {//先取出上下文里的父级元素List<Element> pEles = c.getPreEles();//获取当前的多个元素List<Element> nowEles = new ArrayList<Element>();for(Element ele : pEles){nowEles.addAll(c.getNowEles(ele, eleName));}//然后需要去获取这些元素的值String[] ss = new String[nowEles.size()];for(int i=0;i<ss.length;i++){ss[i] = nowEles.get(i).getFirstChild().getNodeValue();}return ss;}}
//单属性终结符解释器
/*** 属性作为终结符对应的解释器*/
public class PropertyTerminalExpression extends ReadXmlExpression{/*** 属性的名字*/private String propName;public PropertyTerminalExpression(String propName){this.propName = propName;}public String[] interpret(Context c) {//直接获取最后的元素的属性的值String[] ss = new String[1];ss[0] = c.getPreEles().get(0).getAttribute(this.propName);return ss;}
}
//多属性终结符解释器
/*** 以多个元素的属性做为终结符的解释处理对象*/
public class PropertysTerminalExpression extends ReadXmlExpression{/*** 属性名字*/private String propName;public PropertysTerminalExpression(String propName){this.propName = propName;}public String[] interpret(Context c) {//获取最后的多个元素List<Element> eles = c.getPreEles();String[] ss = new String[eles.size()];//循环多个元素,获取每个的属性的值for(int i=0;i<ss.length;i++){ss[i] = eles.get(i).getAttribute(this.propName);}return ss;}
}
//解析模型类
/*** 用来封装每一个解析出来的元素对应的属性*/
public class ParserModel {/*** 是否单个值*/private boolean singleVlaue;/*** 是否属性,不是属性就是元素*/private boolean propertyValue;/*** 是否终结符*/private boolean end;public boolean isEnd() {return end;}public void setEnd(boolean end) {this.end = end;}public boolean isSingleVlaue() {return singleVlaue;}public void setSingleVlaue(boolean oneVlaue) {this.singleVlaue = oneVlaue;}public boolean isPropertyValue() {return propertyValue;}public void setPropertyValue(boolean propertyValue) {this.propertyValue = propertyValue;}
}
//操作XML工具类
public class XmlUtil {public static Document getRoot(String filePathName) throws Exception{Document doc = null;//建立一个解析器工厂DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();//获得一个DocumentBuilder对象,这个对象代表了具体的DOM解析器DocumentBuilder builder=factory.newDocumentBuilder();//得到一个表示XML文档的Document对象doc=builder.parse(filePathName);//去掉XML文档中作为格式化内容的空白而映射在DOM树中的不必要的Text Node对象doc.normalize();return doc;}
}
InterpreterTest.xml
<?xml version="1.0" encoding="UTF-8"?>
<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="41">d4</d></b><e><d id="42">d5</d><d id="43">d6</d></e></a>
</root>
责任链模式
描述
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着其传递该请求,直到有一个对象处理它为止。本质是分离职责,动态组合。
结构
角色 | 说明 |
---|---|
Handler | 职责接口,定义处理请求的方法 |
ConcreteHandler | 职责实现 |
Client | 客户端 |
链的构建
- 客户端实现,在提交请求前组合链,即是在使用时动态组合链,称为外部链
- Handler实现链的组合,称为内部链
- 由各个职责对象自行决定后续的处理对象,其方式要求每个职责对象除了进行业务处理外,还必须了解整个业务流程
优缺点
优点
- 请求者和接收者松散耦合
- 动态组合职责
缺点
- 产生很多细粒度对象
- 不一定能被处理
使用场景
- 若有多个对象可以处理同一个请求,但具体由哪个对象处理该请求,是运行时决定的
- 动态指定处理一个请求的对象集合
示例
//示例:申请聚餐费用//Client类
/*** 使用责任链设计模式实现聚餐和差旅费用申请流程(改进版)* @author liaocan**/
public class Client {public static void main(String[] args) {//先要组装职责链 Handler h1 = new GeneralManager2();Handler h2 = new DepManager2();Handler h3 = new ProjectManager2();h3.setSuccessor(h2);h2.setSuccessor(h1);//开始测试申请聚餐费用FeeRequestModel frm = new FeeRequestModel();frm.setFee(300);frm.setUser("小李");//调用处理String ret1 = (String)h3.handleRequest(frm);System.out.println("ret1="+ret1);//重新设置申请金额,再调用处理frm.setFee(800); h3.handleRequest(frm);String ret2 = (String)h3.handleRequest(frm);System.out.println("ret2="+ret2);//重新设置申请金额,再调用处理frm.setFee(1600); h3.handleRequest(frm);String ret3 = (String)h3.handleRequest(frm);System.out.println("ret3="+ret3);//开始测试申请预支差旅费用PreFeeRequestModel pfrm = new PreFeeRequestModel();pfrm.setFee(3000);pfrm.setUser("小张");//调用处理h3.handleRequest(pfrm);//重新设置申请金额,再调用处理pfrm.setFee(6000);h3.handleRequest(pfrm);//重新设置申请金额,再调用处理pfrm.setFee(36000);h3.handleRequest(pfrm);}
}
//职责接口
/*** 定义职责对象的接口*/
public abstract class Handler {/*** 持有下一个处理请求的对象*/protected Handler successor = null;/*** 设置下一个处理请求的对象* @param successor 下一个处理请求的对象*/public void setSuccessor(Handler successor){this.successor = successor;}/*** 通用的请求处理方法* @param rm 通用的请求对象* @return 处理后需要返回的对象*/public Object handleRequest(RequestModel rm){if(successor != null){//这个是默认的实现,如果子类不愿意处理这个请求,那就传递到下一个职责对象去处理return this.successor.handleRequest(rm);}else{System.out.println("没有后续处理或者暂时不支持这样的功能处理");return false;}}
}
//项目经理处理聚餐费用
/*** 实现项目经理处理聚餐费用申请的对象 */
public class ProjectManager extends Handler{public Object handleRequest(RequestModel rm){if(FeeRequestModel.FEE_TYPE.equals(rm.getType())){//表示聚餐费用申请return handleFeeRequest(rm);}else{//其他的项目经理暂时不想处理return super.handleRequest(rm);}}private Object handleFeeRequest(RequestModel rm) {//先把通用的对象造型回来FeeRequestModel frm = (FeeRequestModel)rm;String str = "";//项目经理的权限比较小,只能在500以内if(frm.getFee() < 500){//为了测试,简单点,只同意小李的if("小李".equals(frm.getUser())){str = "项目经理同意"+frm.getUser()+"聚餐费用"+frm.getFee()+"元的请求";}else{//其他人一律不同意str = "项目经理不同意"+frm.getUser()+"聚餐费用"+frm.getFee()+"元的请求";}return str;}else{//超过500,继续传递给级别更高的人处理if(this.successor!=null){return successor.handleRequest(rm);}}return str;}
}
//项目经理处理预支差旅费用
/*** 实现为项目经理增加预支差旅费用申请处理的功能的子对象,* 现在的项目经理既可以处理聚餐费用申请,又可以处理预支差旅费用申请*/
public class ProjectManager2 extends ProjectManager{public Object handleRequest(RequestModel rm){if(PreFeeRequestModel.FEE_TYPE.equals(rm.getType())){//表示预支差旅费用申请return myHandler(rm);}else{//其他的让父类去处理return super.handleRequest(rm);}}private Object myHandler(RequestModel rm) {//先把通用的对象造型回来PreFeeRequestModel frm = (PreFeeRequestModel)rm;//项目经理的权限比较小,只能在5000以内if(frm.getFee() < 5000){//工作需要嘛,统统同意System.out.println("项目经理同意"+frm.getUser()+"预支差旅费用"+frm.getFee()+"元的请求");return true;}else{//超过5000,继续传递给级别更高的人处理if(this.successor!=null){return this.successor.handleRequest(rm);}}return false;}
}
//部门经理处理聚餐费用
/*** 实现部门经理处理聚餐费用申请的对象 */
public class DepManager extends Handler{public Object handleRequest(RequestModel rm){ if(FeeRequestModel.FEE_TYPE.equals(rm.getType())){return handleFeeRequest(rm);}else{return super.handleRequest(rm);}}private Object handleFeeRequest(RequestModel rm) {//先把通用的对象造型回来FeeRequestModel frm = (FeeRequestModel)rm;String str = "";//部门经理的权限只能在1000以内if(frm.getFee() < 1000){//为了测试,简单点,只同意小李申请的if("小李".equals(frm.getUser())){str = "部门经理同意"+frm.getUser()+"聚餐费用"+frm.getFee()+"元的请求";}else{//其他人一律不同意str = "部门经理不同意"+frm.getUser()+"聚餐费用"+frm.getFee()+"元的请求";}return str;}else{//超过1000,继续传递给级别更高的人处理if(this.successor!=null){return this.successor.handleRequest(rm);}}return str;}
}
//部门经理处理预支差旅费用
/*** 实现部门经理处理预支差旅费用申请的对象 */
public class DepManager2 extends DepManager{public Object handleRequest(RequestModel request){if(PreFeeRequestModel.FEE_TYPE.equals(request.getType())){//表示预支差旅费用申请return myHandler(request);}else{//其他的让父类去处理return super.handleRequest(request);}}private Object myHandler(RequestModel request) {//先把通用的对象造型回来PreFeeRequestModel fr = (PreFeeRequestModel)request;//部门经理的权限比较小,只能在20000以内if(fr.getFee() < 20000){//工作需要嘛,统统同意System.out.println("部门经理同意"+fr.getUser()+"预支差旅费用"+fr.getFee()+"元的请求");return true;}else{//超过20000,继续传递给级别更高的人处理if(this.successor != null){return this.successor.handleRequest(request);}}return false;}
}
//总经理处理预支费用
/*** 实现总经理处理聚餐费用申请的对象 */
public class GeneralManager extends Handler{public Object handleRequest(RequestModel rm){if(FeeRequestModel.FEE_TYPE.equals(rm.getType())){return handleFeeRequest(rm);}else{return super.handleRequest(rm);}}private Object handleFeeRequest(RequestModel rm) {//先把通用的对象造型回来FeeRequestModel frm = (FeeRequestModel)rm;String str = "";//总经理的权限很大,只要请求到了这里,他都可以处理if(frm.getFee() >= 1000){//为了测试,简单点,只同意小李的if("小李".equals(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 class GeneralManager2 extends GeneralManager{public Object handleRequest(RequestModel rm){if(PreFeeRequestModel.FEE_TYPE.equals(rm.getType())){//表示预支差旅费用申请return myHandler(rm);}else{//其他的让父类去处理return super.handleRequest(rm);}}private Object myHandler(RequestModel rm) {//先把通用的对象造型回来PreFeeRequestModel frm = (PreFeeRequestModel)rm;if(frm.getFee() >= 5000){//工作需要嘛,统统同意System.out.println("总经理同意"+frm.getUser()+"预支差旅费用"+frm.getFee()+"元的请求");return true;}else{//如果还有后继的处理对象,继续传递if(this.successor!=null){return this.successor.handleRequest(rm);}}return false;}
}
//通用请求对象
/*** 通用的请求对象*/
public class RequestModel {/*** 表示具体的业务类型*/private String type;/*** 通过构造方法把具体的业务类型传递进来* @param type 具体的业务类型*/public RequestModel(String type){this.type = type;}public String getType() {return type;}
}
//聚餐费用请求对象
/*** 封装跟聚餐费用申请业务相关的请求数据*/
public class FeeRequestModel extends RequestModel{/*** 约定具体的业务类型*/public final static String FEE_TYPE = "fee";public FeeRequestModel() {super(FEE_TYPE);}/*** 申请人*/private String user;/*** 申请金额*/private double fee;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 extends RequestModel{/*** 约定具体的业务类型*/public final static String FEE_TYPE = "preFee";public PreFeeRequestModel() {super(FEE_TYPE);}/*** 申请人*/private String user;/*** 申请金额*/private double fee;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;}
}
访问者模式
描述
定义:表示一个作用于某对象结构中的各元素的操作。使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。本质是预留通路,回调实现。
结构
角色 | 说明 |
---|---|
Visitor | 访问者接口,为访问者对象声明visit方法,用于代表为对象结构添加的功能 |
ConcreteVisitor | 访问者实现对象 |
Element | 抽象的元素对象,对象结构的顶层接口,定义接受访问的操作 |
ConcreteElement | 元素实现对象 |
ObjectStructure | 对象结构,包含多个被访问的对象 |
优缺点
优点
- 更好的扩展性
- 更好的复用性
- 分离无关行为
缺点
- 对象结构变化困难
- 破坏封装
使用场景
- 对一个对象结构实施依赖于对象结构中的具体类的操作
- 将一个对象结构中复杂的行为转移到访问者中
示例
//示例:商品类别树的管理//Client类
public class Client {public 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);//调用根元素的方法来接受请求功能Visitor psVisitor = new PrintStructVisitor(); root.accept(psVisitor);}
}
//抽象组件对象
/*** 抽象的组件对象,相当于访问者模式中的元素对象*/
public abstract class Component {/*** 接受访问者的访问* @param visitor 访问者对象*/public abstract void accept(Visitor visitor);/*** 向组合对象中加入组件对象 * @param child 被加入组合对象中的组件对象*/public void addChild(Component child) {// 缺省的实现,抛出例外,因为叶子对象没有这个功能,或者子组件没有实现这个功能throw new UnsupportedOperationException("对象不支持这个功能");}/*** 从组合对象中移出某个组件对象* @param child 被移出的组件对象*/public void removeChild(Component child) {// 缺省的实现,抛出例外,因为叶子对象没有这个功能,或者子组件没有实现这个功能throw new UnsupportedOperationException("对象不支持这个功能");}/*** 返回某个索引对应的组件对象,如果没有对应的子对象,就返回null* @param index 需要获取的组件对象的索引,索引从0开始* @return 索引对应的组件对象*/public Component getChildren(int index) {throw new UnsupportedOperationException("对象不支持这个功能");}
}
//组合对象
/*** 组合对象,可以包含其它组合对象或者叶子对象,* 相当于访问者模式的具体Element实现对象*/
public class Composite extends Component{public void accept(Visitor visitor) {//回调访问者对象的相应方法visitor.visitComposite(this);}/*** 用来存储组合对象中包含的子组件对象*/private List<Component> childComponents = new ArrayList<Component>();public List<Component> getChildComponents() {return childComponents;}/*** 组合对象的名字*/private String name = "";/*** 构造方法,传入组合对象的名字* @param name 组合对象的名字*/public Composite(String name){this.name = name;}public void addChild(Component child) {//延迟初始化if (childComponents == null) {childComponents = new ArrayList<Component>();}childComponents.add(child);}public String getName() {return name;}
}
//叶子对象
/*** 叶子对象,相当于访问者模式的具体Element实现对象*/
public class Leaf extends Component{public void accept(Visitor visitor) {//回调访问者对象的相应方法visitor.visitLeaf(this);}/*** 叶子对象的名字*/private String name = "";/*** 构造方法,传入叶子对象的名字* @param name 叶子对象的名字*/public Leaf(String name){this.name = name;}public String getName() {return name;}
}
//访问者接口
/*** 访问组合对象结构的访问者接口*/
public interface Visitor {/*** 访问组合对象,相当于给组合对象添加访问者的功能* @param composite 组合对象*/public void visitComposite(Composite composite);/*** 访问叶子对象,相当于给叶子对象添加访问者的功能* @param leaf 叶子对象*/public void visitLeaf(Leaf leaf);
}
//具体访问者
/*** 具体的访问者,实现:输出组合对象自身的结构
*/
public class PrintStructVisitor implements Visitor {/*** 用来累计记录对象需要向后退的格*/private String preStr = "";public void visitComposite(Composite composite) {//先把自己输出去System.out.println(preStr+"+"+composite.getName());//如果还包含有子组件,那么就输出这些子组件对象if(composite.getChildComponents()!=null){//然后添加一个空格,表示向后缩进一个空格preStr+=" "; //输出当前对象的子对象了for(Component c : composite.getChildComponents()){//递归输出每个子对象c.accept(this);}//把循环子对象所多加入的一个退格给去掉preStr = preStr.substring(0,preStr.length()-1);}}public void visitLeaf(Leaf leaf) {//访问到叶子对象的数据 System.out.println(preStr+"-"+leaf.getName());}
}
细数23种设计模式以及Java代码实现相关推荐
- 23种设计模式(java代码实现案例)
设计模式 创造型.结构型.行为型 创建型: 1.(类)工厂方法(Factory Method) 意图 定义一个用于创建对象的接口,让子类决定实例化哪一个类.使一个类的实例化延迟到了子类 适用性 ...
- 23 种设计模式详解 代码实现全解析
设计模式 Design Pattern 是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结,使用设计模式是为了可重用代码.让代码更容易被他人理解并且保证代码可靠性. 在<设计模式 ...
- 23种设计模式详解(代码讲解、持续更新)
目录 设计模式分类 设计模式的六大原则 创建型模式 1.工厂方法模式(Factory Method) 2.建造者模式(Builder Pattern(常用.常见)) 行为型模式 模板模式(Templa ...
- 23种设计模式及简单代码
1. 概述 随着做项目增多不可避免地接触到了设计模式,现在各大文档中呈现的设计模式总共有23种,实际上使用中的肯定比23种多,为了让自己深刻理解设计模式,本博决定自己手写这些设计模式,便于在项目中灵活 ...
- 23种设计模式之Java实现
目录 **单例模式之饿汉式** **单例模式之懒汉式** **单例模式之静态内部类实现** **单例模式之枚举实现** **简单工厂模式(静态工厂模式)** **工厂方法模式** **抽象工厂模式** ...
- 23种设计模式的JAVA实现——访问者模式(行为型模式)
访问者模式 访问者模式:在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法.通过这种方式,元素的执行算法可以随着访问者改变而改变.这种类型的设计模式属于 ...
- 23种设计模式介绍以及在Java中的实现
本文章出自:blog.csdn.net/anxpp/artic- 若要查看原文请点击 文章中的示例源码在github上:github.com/anxpp/JavaD- 由于CSDN上的下拉翻页比较麻烦 ...
- JAVA开发的23种设计模式之 — 装饰器模式 代理器模式
装饰器模式 概述 : 动态的给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更加灵活 维基百科解释 : 通过使用修饰模式,可以在运行时扩充一个类的功能.原理是 :增加一个修饰类包裹 ...
- 23种设计模式-完结!
23种设计模式(Java版本) 总述 在大四的时候开始了作为一个码农的实习阶段,在进入公司之前,我只知道一些简单的工厂模式--比如简单工厂(对应还有"复杂"工厂).单例模式.代 ...
最新文章
- matlab定子磁链观测器,一种基于二阶广义积分器的永磁同步电机定子磁链观测方法...
- 【Android 进程保活】提升进程优先级 ( 使用前台 Service 提高应用进程优先级 | 启动相同 id 的第二个前台 Service 关闭通知 )
- PHP基础1--环境搭建
- 腾讯视频客户端导出MP4格式
- Hadoop分布式文件系统HDFS
- 机试题:地图定位、拍照并显示、录制视频并播放
- HTML+CSS+JS实现网页随机点名
- 博客营销之博客平台的选择和优化
- 创建一个war类型的maven项目
- 一个用于分布式DNN训练加速的通用通信调度器
- for循环 php 增加数组维数_php实现给二维数组中所有一维数组添加值的方法
- 速达软件启示录——记中国一代ERP性价比之王的没落
- Oracle VM VirtualBox Ubuntu1804虚拟机磁盘扩容
- 免费好用的 Apple 工具(Windows 适用)
- 17秋 软件工程 团队作业 最终总结博客
- android png 图标制作,ico图标怎么制作?png图片文件转换成ico图标文件的教程
- 语言模型——n元语法模型
- ibm服务器维修论坛,IBM通病
- 女大学生 你的青春值多少钱
- mysql服务器相关命令