windows server 2008 网站配置,郑州网站建设学校,网站建设步骤及推广方法,如何做淘宝优惠券网站设计模式的分类 总体来说设计模式分为三大类#xff1a; 创建型模式#xff0c;共五种#xff1a;单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。 结构型模式#xff0c;共七种#xff1a;适配器模式、装饰器模式、代理模式、外观模式、桥接模式、 组合模…设计模式的分类 总体来说设计模式分为三大类 创建型模式共五种单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。 结构型模式共七种适配器模式、装饰器模式、代理模式、外观模式、桥接模式、 组合模式、享元模式。 行为型模式共十一种策略模式、模板方法模式、观察者模式、迭代子模式、 责任链模式、 命令模式、备忘录模式、状态模式、访问者模式、 中介者模式、解释器模式。 A、创建模式5种
单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
1. 单例模式
1.1 定义 定义确保一个类最多只有一个实例并提供一个全局访问点 单例模式可以分为两种预加载和懒加载
1.2 预加载
顾名思义就是预先加载。再进一步解释就是还没有使用该单例对象但是该单例对象就已经被加载到内存了。
/**预加载
*/
public class PreloadSingleton {public static PreloadSingleton instance new PreloadSingleton();//其他的类无法实例化单例类的对象private PreloadSingleton() {};public static PreloadSingleton getInstance() {return instance;}
}很明显没有使用该单例对象该对象就被加载到了内存会造成内存的浪费。
1.3 懒加载
为了避免内存的浪费我们可以采用懒加载即用到该单例对象的时候再创建。
public class Singleton {private static Singleton instance null;private Singleton() {}public static Singleton getInstance() {if (instance null) {instance new Singleton();}return instance;}}1.4 单例模式和线程安全
1预加载只有一条语句return instance,这显然可以保证线程安全。但是我们知道预加载会造成内存的浪费。
2懒加载不浪费内存但是无法保证线程的安全。首先if判断以及其内存执行代码是非原子性的。其次new Singleton()无法保证执行的顺序性。
不满足原子性或者顺序性线程肯定是不安全的这是基本的常识不再赘述。我主要讲一下为什么new Singleton()无法保证顺序性。我们知道创建一个对象分三步:
memoryallocate();//1:初始化内存空间ctorInstance(memory);//2:初始化对象instancememory();//3:设置instance指向刚分配的内存地址jvm为了提高程序执行性能会对没有依赖关系的代码进行重排序上面2和3行代码可能被重新排序。我们用两个线程来说明线程是不安全的。线程A和线程B都创建对象。其中A2和A3的重排序将导致线程B在B1处判断出instance不为空线程B接下来将访问instance引用的对象。此时线程B将会访问到一个还未初始化的对象线程不安全。
1.4 保证懒加载的线程安全
我们首先想到的就是使用synchronized关键字。synchronized加载getInstace()函数上确实保证了线程的安全。但是如果要经常的调用getInstance()方法不管有没有初始化实例都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间如果对象已经实例化了我们没有必要再使用synchronized加锁直接返回对象。
public class Singleton {private static Singleton instance null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance null) {instance new Singleton();}return instance;}
}我们把sychronized加在if(instancenull)判断语句里面保证instance未实例化的时候才加锁
public class Singleton {private static Singleton instance null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance null) {synchronized (Singleton.class) {if (instance null) {instance new Singleton();}}}return instance;}
}我们经过1.4的讨论知道new一个对象的代码是无法保证顺序性的因此我们需要使用另一个关键字volatile保证对象实例化过程的顺序性。
public class Singleton {private static volatile Singleton instance null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance null) {synchronized (instance) {if (instance null) {instance new Singleton();}}}return instance;}
}到此我们就保证了懒加载的线程安全。
2 工厂模式
2.1 简单工厂模式
2.1.1 定义 定义定义了一个创建对象的类由这个类来封装实例化对象的行为。 2.1.2 代码示例
举例我们举一个pizza工厂的例子
pizza工厂一共生产三种类型的pizzachesse,pepper,greak。通过工厂类SimplePizzaFactory实例化这三种类型的对象。类图如下 工厂类的代码
public class SimplePizzaFactory {public Pizza CreatePizza(String ordertype) {Pizza pizza null;if (ordertype.equals(cheese)) {pizza new CheesePizza();} else if (ordertype.equals(greek)) {pizza new GreekPizza();} else if (ordertype.equals(pepper)) {pizza new PepperPizza();}return pizza;}
}简单工厂存在的问题与解决方法 简单工厂模式有一个问题就是类的创建依赖工厂类也就是说如果想要拓展程序必须对工厂类进行修改这违背了开闭原则所以从设计角度考虑有一定的问题如何解决我们可以定义一个创建对象的抽象方法并创建多个不同的工厂类实现该抽象方法这样一旦需要增加新的功能直接增加新的工厂类就可以了不需要修改之前的代码。这种方法也就是我们接下来要说的工厂方法模式。
2.2 工厂方法模式
2.2.1 定义 定义定义了一个创建对象的抽象方法由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。 2.2.2 代码示例
举例我们依然举pizza工厂的例子不过这个例子中pizza产地有两个伦敦和纽约。添加了一个新的产地如果用简单工厂模式的的话我们要去修改工厂代码并且会增加一堆的if else语句。而工厂方法模式克服了简单工厂要修改代码的缺点它会直接创建两个工厂纽约工厂和伦敦工厂。类图如下 OrderPizza中有个抽象的方法
public class OrderPizza{abstract Pizza createPizza();
}
两个工厂类继承OrderPizza并实现抽象方法
public class LDOrderPizza extends OrderPizza {Pizza createPizza(String ordertype) {Pizza pizza null;if (ordertype.equals(cheese)) {pizza new LDCheesePizza();} else if (ordertype.equals(pepper)) {pizza new LDPepperPizza();}return pizza;}
}public class NYOrderPizza extends OrderPizza {Pizza createPizza(String ordertype) {Pizza pizza null;if (ordertype.equals(cheese)) {pizza new NYCheesePizza();} else if (ordertype.equals(pepper)) {pizza new NYPepperPizza();}return pizza;}
}通过不同的工厂会得到不同的实例化的对象PizzaStroe的代码如下
public class PizzaStroe {public static void main(String[] args) {OrderPizza mOrderPizza;mOrderPizza new NYOrderPizza();}
}解决了简单工厂模式的问题增加一个新的pizza产地北京只要增加一个BJOrderPizza类
public class BJOrderPizza extends OrderPizza {Pizza createPizza(String ordertype) {Pizza pizza null;if (ordertype.equals(cheese)) {pizza new BJCheesePizza();} else if (ordertype.equals(pepper)) {pizza new BJPepperPizza();}return pizza;}
}其实这个模式的好处就是如果你现在想增加一个功能只需做一个实现类就OK了无需去改动现成的代码。这样做拓展性较好
工厂方法存在的问题与解决方法客户端需要创建类的具体的实例。简单来说就是用户要订纽约工厂的披萨他必须去纽约工厂想订伦敦工厂的披萨必须去伦敦工厂。 当伦敦工厂和纽约工厂发生变化了用户也要跟着变化这无疑就增加了用户的操作复杂性。为了解决这一问题我们可以把工厂类抽象为接口用户只需要去找默认的工厂提出自己的需求传入参数便能得到自己想要产品而不用根据产品去寻找不同的工厂方便用户操作。这也就是我们接下来要说的抽象工厂模式。
2.3 抽象工厂模式
2.3.1 定义 定义定义了一个接口用于创建相关或有依赖关系的对象族而无需明确指定具体类。 2.3.2 代码示例
举例我们依然举pizza工厂的例子pizza工厂有两个纽约工厂和伦敦工厂。类图如下 工厂的接口
public interface AbsFactory {Pizza CreatePizza(String ordertype) ;
}工厂的实现
public class LDFactory implements AbsFactory {Overridepublic Pizza CreatePizza(String ordertype) {Pizza pizza null;if (cheese.equals(ordertype)) {pizza new LDCheesePizza();} else if (pepper.equals(ordertype)) {pizza new LDPepperPizza();}return pizza;}
}PizzaStroe的代码如下
public class PizzaStroe {public static void main(String[] args) {OrderPizza mOrderPizza;mOrderPizza new OrderPizza(London);}
}解决了工厂方法模式的问题在抽象工厂中PizzaStroe中只需要传入参数就可以实例化对象。
2.4 工厂模式适用的场合
大量的产品需要创建并且这些产品具有共同的接口 。
2.5 三种工厂模式的使用选择 简单工厂 用来生产同一等级结构中的任意产品。不支持拓展增加产品 工厂方法 用来生产同一等级结构中的固定产品。支持拓展增加产品 抽象工厂 用来生产不同产品族的全部产品。支持拓展增加产品支持增加产品族 简单工厂的适用场合只有伦敦工厂只有这一个等级并且这个工厂只生产三种类型的pizzachesse,pepper,greak固定产品。 工厂方法的适用场合现在不光有伦敦工厂还增设了纽约工厂仍然是同一等级结构但是支持了产品的拓展这两个工厂依然只生产三种类型的pizzachesse,pepper,greak固定产品。 抽象工厂的适用场合不光增设了纽约工厂仍然是同一等级结构但是支持了产品的拓展这两个工厂还增加了一种新的类型的pizzachinese pizza增加产品族。 所以说抽象工厂就像工厂而工厂方法则像是工厂的一种产品生产线。 因此我们可以用抽象工厂模式创建工厂而用工厂方法模式创建生产线。 举例我们可以使用抽象工厂模式创建伦敦工厂和纽约工厂使用工厂方法实现cheese pizza和greak pizza的生产。类图如下 2.6 总结一下三种模式 简单工厂模式建立一个实例化对象的类在该类中对多个对象实例化。 工厂方法模式定义了一个创建对象的抽象方法由子类决定要实例化的类。这样做的好处是再有新的类型的对象需要实例化只要增加子类即可。 抽象工厂模式定义了一个接口用于创建对象族而无需明确指定具体类。抽象工厂也是把对象的实例化交给了子类即支持拓展。同时提供给客户端接口避免了用户直接操作子类工厂。 3 生成器模式
3.1 定义 定义使用生成器模式封装一个产品的构造过程并允许按步骤构造。 定义解释假设我们有一个对象需要建立这个对象是由多个组件Component组合而成每个组件的建立都比较复杂但运用组件来建立所需的对象非常简单所以我们就可以将构建复杂组件的步骤与运用组件构建对象分离使用builder模式可以建立。 3.2 模式的结构和代码示例
3.2.1 生成器模式结构 生成器模式结构中包括四种角色 1产品(Product)具体生产器要构造的复杂对象 2抽象生成器(Bulider)抽象生成器是一个接口该接口除了为创建一个Product对象的各个组件定义了若干个方法之外还要定义返回Product对象的方法定义构造步骤 3具体生产器(ConcreteBuilder)实现Builder接口的类具体生成器将实现Builder接口所定义的方法生产各个组件 4指挥者(Director)指挥者是一个类该类需要含有Builder接口声明的变量。指挥者的职责是负责向用户提供具体生成器即指挥者将请求具体生成器类来构造用户所需要的Product对象如果所请求的具体生成器成功地构造出Product对象指挥者就可以让该具体生产器返回所构造的Product对象。按照步骤组装部件并返回Product 3.2.2 代码示例
举个例子我们如果构建生成一台电脑那么我们可能需要这么几个步骤
1需要一个主机 2需要一个显示器 3需要一个键盘 4需要一个鼠标 5需要音响等 虽然我们具体在构建一台主机的时候每个对象的实际步骤是不一样的比如有的对象构建了i7cpu的主机有的对象构建了i5cpu的主机有的对象构建了普通键盘有的对象构建了机械键盘等。
但不管怎样你总是需要经过一个步骤就是构建一台主机一台键盘。 对于这个例子我们就可以使用生成器模式来生成一台电脑他需要通过多个步骤来生成。
类图如下 下面我们就根据这个例子来实现一个生成器模式生成一台电脑
首先我们需要一个电脑类
// 电脑类
public class Computer {public String master;public String screen;public String keyboard;public String mouse;public String audio;public void setMaster(String master) {this.master master;}public void setScreen(String screen) {this.screen screen;}public void setKeyboard(String keyboard) {this.keyboard keyboard;}public void setMouse(String mouse) {this.mouse mouse;}public void setAudio(String audio) {this.audio audio;}
}
然后我们建立一个抽象的builder类
/**抽象的builder类
*/
public abstract class ComputerBuilder {protected Computer computer;public Computer getComputer() {return computer;}public void buildComputer() {computer new Computer();System.out.println(生成了一台电脑);}public abstract void buildMaster();public abstract void buildScreen();public abstract void buildKeyboard();public abstract void buildMouse();public abstract void buildAudio();
}然后我们实现两个具体的builder类分别是惠普电脑的builder和戴尔电脑的builder
/**惠普电脑的builder
*/
public class HPComputerBuilder extends ComputerBuilder {Overridepublic void buildMaster() {// TODO Auto-generated method stubcomputer.setMaster(i7,16g,512SSD,1060);System.out.println((i7,16g,512SSD,1060)的惠普主机);}Overridepublic void buildScreen() {// TODO Auto-generated method stubcomputer.setScreen(1080p);System.out.println((1080p)的惠普显示屏);}Overridepublic void buildKeyboard() {// TODO Auto-generated method stubcomputer.setKeyboard(cherry 青轴机械键盘);System.out.println((cherry 青轴机械键盘)的键盘);}Overridepublic void buildMouse() {// TODO Auto-generated method stubcomputer.setMouse(MI 鼠标);System.out.println((MI 鼠标)的鼠标);}Overridepublic void buildAudio() {// TODO Auto-generated method stubcomputer.setAudio(飞利浦 音响);System.out.println((飞利浦 音响)的音响);}
}
/**戴尔电脑的builder
*/
public class DELLComputerBuilder extends ComputerBuilder {Overridepublic void buildMaster() {// TODO Auto-generated method stubcomputer.setMaster(i7,32g,1TSSD,1060);System.out.println((i7,32g,1TSSD,1060)的戴尔主机);}Overridepublic void buildScreen() {// TODO Auto-generated method stubcomputer.setScreen(4k);System.out.println((4k)的dell显示屏);}Overridepublic void buildKeyboard() {// TODO Auto-generated method stubcomputer.setKeyboard(cherry 黑轴机械键盘);System.out.println((cherry 黑轴机械键盘)的键盘);}Overridepublic void buildMouse() {// TODO Auto-generated method stubcomputer.setMouse(MI 鼠标);System.out.println((MI 鼠标)的鼠标);}Overridepublic void buildAudio() {// TODO Auto-generated method stubcomputer.setAudio(飞利浦 音响);System.out.println((飞利浦 音响)的音响);}}
然后我们实现一个director类
/**director类
*/
public class Director {private ComputerBuilder computerBuilder;public void setComputerBuilder(ComputerBuilder computerBuilder) {this.computerBuilder computerBuilder;}public Computer getComputer() {return computerBuilder.getComputer();}public void constructComputer() {computerBuilder.buildComputer();computerBuilder.buildMaster();computerBuilder.buildScreen();computerBuilder.buildKeyboard();computerBuilder.buildMouse();computerBuilder.buildAudio();}}
最后我们测试一下代码
//测试类
public class ComputerCustomer {public static void main(String[] args) {// TODO Auto-generated method stubDirector director new Director();ComputerBuilder hp new HPComputerBuilder();director.setComputerBuilder(hp);director.constructComputer();//get the pcComputer pc director.getComputer();}
}
结果如下 3.3 生成器的优缺点 生成器的优点 1将一个复杂对象的创建过程封装起来 2允许对象通过多个步骤来创建并且可以改变过程。这和只有一个步骤的工厂模式不同 3向客户隐藏产品内部的表现 4产品的实现可以被替换因为客户只看到一个抽象的接口。 生成器的缺点 对不同类型的对象需要实现不同的具体构造器的类这可能会大大增加类的数量 3.4 生成器模式与工厂模式的不同 生成器模式构建对象的时候对象通常构建的过程中需要多个步骤就像我们例子中的先有主机再有显示屏再有鼠标等等生成器模式的作用就是将这些复杂的构建过程封装起来。 工厂模式构建对象的时候通常就只有一个步骤调用一个工厂方法就可以生成一个对象。 4 原型模式
4.1 定义 定义通过复制现有实例来创建新的实例无需知道相应类的信息。 简单地理解其实就是当需要创建一个指定的对象时我们刚好有一个这样的对象但是又不能直接使用我会clone一个一毛一样的新对象来使用基本上这就是原型模式。关键字Clone。 4.2 深拷贝和浅拷贝 浅复制将一个对象复制后基本数据类型的变量都会重新创建而引用类型指向的还是原对象所指向的。 深复制将一个对象复制后不论是基本数据类型还有引用类型都是重新创建的。简单来说就是深复制进行了完全彻底的复制而浅复制不彻底。clone明显是深复制clone出来的对象是是不能去影响原型对象的。 4.3 原型模式的结构和代码示例
4.3.1 原型模式核心组成 Client使用者 Prototype接口抽象类声明具备clone能力例如java中得Cloneable接口 ConcretePrototype具体的原型类 可以看出设计模式还是比较简单的重点在于Prototype接口和Prototype接口的实现类ConcretePrototype。原型模式的具体实现一个原型类只需要实现Cloneable接口覆写clone方法此处clone方法可以改成任意的名称因为Cloneable接口是个空接口你可以任意定义实现类的方法名如cloneA或者cloneB因为此处的重点是super.clone()这句话super.clone()调用的是Object的clone()方法。
public class Prototype implements Cloneable { public Object clone() throws CloneNotSupportedException { Prototype proto (Prototype) super.clone(); return proto; }
} 4.3.2 代码示例
举例 银行发送大量邮件使用clone和不使用clone的时间对比我们模拟创建一个对象需要耗费比较长的时间因此在构造函数中我们让当前线程sleep一会
public Mail(EventTemplate et) {this.tail et.geteventContent();this.subject et.geteventSubject();try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}不使用clone,发送十个邮件
public static void main(String[] args) {int i 0;int MAX_COUNT 10;EventTemplate et new EventTemplate(9月份信用卡账单, 国庆抽奖活动...);long start System.currentTimeMillis();while (i MAX_COUNT) {// 以下是每封邮件不同的地方Mail mail new Mail(et);mail.setContent(getRandString(5) ,先生女士:你的信用卡账单... mail.getTail());mail.setReceiver(getRandString(5) getRandString(8) .com);// 然后发送邮件sendMail(mail);i;}long end System.currentTimeMillis();System.out.println(用时: (end - start));}用时10001
使用clone,发送十个邮件 public static void main(String[] args) {int i 0;int MAX_COUNT 10;EventTemplate et new EventTemplate(9月份信用卡账单, 国庆抽奖活动...);long startSystem.currentTimeMillis();Mail mail new Mail(et); while (i MAX_COUNT) {Mail cloneMail mail.clone();mail.setContent(getRandString(5) ,先生女士:你的信用卡账单... mail.getTail());mail.setReceiver(getRandString(5) getRandString(8) .com);sendMail(cloneMail);i;}long endSystem.currentTimeMillis();System.out.println(用时:(end-start));}4.4 总结 原型模式的本质就是clone可以解决构建复杂对象的资源消耗问题能再某些场景中提升构建对象的效率还有一个重要的用途就是保护性拷贝可以通过返回一个拷贝对象的形式实现只读的限制。 B、结构模式7种
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
5 适配器模式
5.1 定义 定义 适配器模式将某个类的接口转换成客户端期望的另一个接口表示目的是消除由于接口不匹配所造成的类的兼容性问题。 5.2 适配器的分类 主要分为三类类的适配器模式、对象的适配器模式、接口的适配器模式。 5.2.1 类适配器模式 顾名思义通过适配器通过类来实现以类来继承和实现接口的方式来获取被适配类的信息并转换输出重写到适配接口。即Adapter类通过继承src类实现dst类接口完成src-dst的适配。 5.2.1.1 代码示例
我们可以用一个现实生活中非常简单的例子来举例一般家庭电压都是220v而我们的笔记本电脑可能只需要15v的电压而不能直接使用家庭电压这就需要一个适配器连接笔记本和家庭电压通过适配器将家庭电压转换为笔记本可以适配的电压才可以使笔记本正常工作下面是代码的具体实现:
//家庭电压类 被适配的类
public class Home {//家庭电压类输入方法public int input(){return 220;}
}//笔记本接口
public interface Computer {//笔记本接口输出电压方法public int output();
}//适配器类
public class Wrapper extends Home implements Computer {//通过继承家庭电压类和实现笔记本接口重写笔记本类中的输出电压方法使输出的电压适配笔记本的电压Overridepublic int output() {return input() - 205;}
}//测试
public class Client {public static void main(String[] args) {Wrapper wrapper new Wrapper();System.out.println(笔记本的输出电压是 wrapper.output() v);;}
}5.2.1.2 类适配器模式优缺点 类适配器模式优点 由于其继承了src类所以它可以根据需求重写sre类的方法使得Adapter的灵活性增强了。 类适配器模式缺点 1Java是单继承机制所以类适配器需要继承src类因为这要求dst必须是接口有一定局限性 2src类的方法在Adapter中都会暴露出来也增加了使用的成本 5.2.2 对象适配器模式 顾名思义通过实例对象构造器传递来实现适配器而不是再用继承通过持有src类的实例以解决兼容性的问题。即持有src类实现dst类接口完成src-dst的适配。 5.2.2.1 代码示例
//家庭电压类 被适配的类不变
public class Home {//家庭电压类输入方法public int input(){return 220;}
}//笔记本接口不变
public interface Computer {//笔记本接口输出电压方法public int output();
}//适配器类
public class Wrapper implements Computer {//在当前类中创建源对象类的引用public Home home new Home();Overridepublic int output() {return home.input() - 205;}public static void main(String[] args) {Wrapper wrapper new Wrapper();System.out.println(笔记本的输出电压是 wrapper.output() v);;}
}//测试不变
public class Client {public static void main(String[] args) {Wrapper wrapper new Wrapper();System.out.println(笔记本的输出电压是 wrapper.output() v);;}
}5.2.2.2 优缺点 优缺点 把继承解耦解决了类适配器必须继承src的局限性问题也不再要求dst必须是接口。同一个Adapter可以把source类和他的子类都适配到目标接口。需要重新定义source行为时需要重新定义source的子类并将适配器组合适配。 5.2.3 接口适配器模式 接口适配器也称缺省适配器模式适用于一个接口不想使用其所有的方法的情况。当不需要全部实现接口提供的方法时可先设计一个抽象类实现接口并为该接口中每个方法提供一个默认实现空方法那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。 5.2.3.1 代码示例
//家庭电压类 被适配的类不变
public class Home {//家庭电压类输入方法public int input(){return 220;}
}//笔记本接口 适配接口不变
public interface Computer {//笔记本接口输出电压方法public int output();//接口里冗余不重要的方法public void m2(); public String m3();
}//抽象适配器
public abstract class Wrapper extends Home implements Computer {Override //以空方法实现接口所有方法public int output() {}Overridepublic void m2() {}Overridepublic String m3() {}}//测试不变
public class Client {public static void main(String[] args) {Wrapper wrapper new Wrapper() { //匿名内部类的形式Override //按需要重写接口方法public int output() {System.out.println(使用了output的方法);int srcV input();int dstV srcV / 44 ; //转成5vreturn dstV;}};System.out.println(笔记本的输出电压是 wrapper.output() v);;}
}5.2.3.2 优缺点 优缺点 可以灵活方便的选择性重写接口方法。由于是匿名内部类的形式所以不利于代码复用。 5.3 总结 以上三种形式是根据src是以怎样的形式给到Adapter来命名的 类适配器以类给到在Adapter里就是将src当做类继承。 对象适配器以对象给到在Adapter里将src作为一个对象持有。 接口适配器以接口给到在Adapter里将src作为一个接口实现。 Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作在实际开发中实现起来不拘泥于我们讲解的三种经典形式。 6 装饰者模式
6.1 定义 定义 动态的将新功能附加到对象上。在对象功能扩展方面它比继承更有弹性。 6.2 模式结构 装饰者模式通常涉及以下几个角色 Component抽象组件定义一个对象接口可以给这些对象动态地添加职责。 ConcreteComponent具体组件实现 Component 接口的具体对象可以给这些对象添加一些职责。 Decorator装饰者抽象类继承 Component 接口通常持有一个 Component 对象的引用并定义一个与 Component 接口一致的接口。 ConcreteDecorator具体装饰者扩展 Decorator 类的具体装饰者负责向组件添加新的职责。 6.3 代码示例
举例 咖啡馆订单项目我们有一个基本的咖啡对象可以动态地添加不同的配料如牛奶和糖。
1咖啡种类被装饰者SimpleCoffee
2调料装饰者Milk、Sugar
被装饰的对象和装饰者都继承自同一个超类
//组件接口
public interface Coffee {double cost();String getDescription();
}//具体组件 简易咖啡
public class SimpleCoffee implements Coffee {public double cost() {return 5.0;}public String getDescription() {return Simple Coffee;}
}//装饰者抽象类
public abstract class CoffeeDecorator implements Coffee {protected Coffee coffee;public CoffeeDecorator(Coffee coffee) {this.coffee coffee;}public double cost() {return coffee.cost();}public String getDescription() {return coffee.getDescription();}
}//具体装饰者 加了牛奶的咖啡
public class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}public double cost() {return super.cost() 1.5;}public String getDescription() {return super.getDescription() , Milk;}
}//具体装饰者 加了糖的咖啡
public class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}public double cost() {return super.cost() 0.5;}public String getDescription() {return super.getDescription() , Sugar;}
}//使用装饰者模式
public class CoffeeShop {public static void main(String[] args) {Coffee coffee new SimpleCoffee();System.out.println(coffee.getDescription() Cost: $ coffee.cost());coffee new MilkDecorator(coffee);System.out.println(coffee.getDescription() Cost: $ coffee.cost());coffee new SugarDecorator(coffee);System.out.println(coffee.getDescription() Cost: $ coffee.cost());}
}//输出结果
Simple Coffee Cost: $5.0
Simple Coffee, Milk Cost: $6.5
Simple Coffee, Milk, Sugar Cost: $7.06.4 总结 装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。 7 代理模式
7.1 定义 定义代理模式给某一个对象提供一个代理对象并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。 举个例子来说明假如说我现在想买一辆二手车虽然我可以自己去找车源做质量检测等一系列的车辆过户流程但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢于是我就通过中介公司来买车他们来给我找车源帮我办理车辆过户流程我只是负责选择自己喜欢的车然后付钱就可以了。 7.2 代理模式优缺点 代理模式的主要优点有 1代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用 2代理对象可以扩展目标对象的功能 3代理模式能将客户端与目标对象分离在一定程度上降低了系统的耦合度 其主要缺点是 1在客户端和目标对象之间增加一个代理对象会造成请求处理速度变慢 2增加了系统的复杂度 7.3 代理模式分类 静态代理 由程序员创建或第三方工具生成代理类的源代码再进行编译。 其代理类和委托。类在编译期间就确定下来。动态代理 代理类在程序运行时通过反射等机制动态生成无需程序员显式地创建代理类。 1JDK 动态代理 2CGLib 动态代理虚拟代理 当有一个需要创建开销较大的对象时通过虚拟代理来存放实例化需要较长时间的真实对象。 7.3.1 静态代理
7.3.1.1 代码示例
举例保存数据 /*** 代理模式之静态代理实现* 模拟一个保存用户数据的功能** 定义一个接口用于规定实现具体业务保存数据的方法即抽象主题*/
public interface IUserDao {void save();
}/******************************************************** 具体目标类即代理模式中的具体主题********************************************************/
public class UserDaoImpl implements IUserDao{Overridepublic void save() {System.out.println(保存数据);}
}/******************************************************** 静态代理类*******************************************************/
public class ProxyUserDao implements IUserDao{private IUserDao target;public ProxyUserDao(IUserDao target){this.target target;}Overridepublic void save() {System.out.println(开启保存数据~~);target.save();System.out.println(结束保存数据~~);}
}//测试
public class Example01 {public static void main(String[] args) {//目标对象UserDaoImpl userDao new UserDaoImpl();//代理对象ProxyUserDao proxy new ProxyUserDao(userDao);proxy.save();}
}7.3.1.2 优缺点总结 优点可以做到在符合开闭原则的情况下对目标对象进行功能扩展。 缺点 代理对象与目标对象要实现相同的接口我们得为每一个服务都得创建代理类工作量太大不易管理。同时接口一旦发生改变代理类也得相应修改。 7.3.2 JDK动态代理 7.3.2.1 jdk动态代理实现原理 jdk动态代理利用了JDK API且需要实现接口动态地在内存中构建代理对象从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。 静态代理与jdk动态代理的区别 1静态代理在编译时就已经实现了编译完成后代理类是一个实际的class文件 2jdk动态代理是在运行时动态生成的即编译完成后没有实际的class文件而是在运行时动态生成类字节码并加载到JVM中 7.3.2.2 jdk动态代理涉及到的jdk api jdk动态代理主要涉及到的类Proxy的newProxyInstance 方法和接口InvocationHandler的 invoke 方法详情如下 1java.lang.reflect.Proxy 的 newProxyInstance 方法如下 //返回一个指定接口的代理类实例该接口可以将方法调用指派到指定的调用处理程序。
static Object newProxyInstance(ClassLoader loader, //指定当前目标对象使用类加载器Class?[] interfaces, //目标对象实现的接口的类型InvocationHandler h //事件处理器
) 2java.lang.reflect.InvocationHandler的invoke方法如下: // 在代理实例上处理方法调用并返回结果。
Object invoke(Object proxy, Method method, Object[] args) 7.3.2.3 代码示例
/********************************************************jdk动态代理实现* 定义一个动态工厂 ProxyFactory用于生成动态代理对象********************************************************/
public class ProxyFactory {//维护的目标对象private Object target;public ProxyFactory(Object target){this.target target;}//为目标对象生成代理对象public Object getProxyInstance(){return Proxy.newProxyInstance(//第一个参数目标对象的类加载器target.getClass().getClassLoader(),//第二个参数目标对象所实现的接口类型target.getClass().getInterfaces(),//第三个参数事件处理器new InvocationHandler() {/*** 用于执行目标对象方法* 可以在目标对象方法执行前后进行个性化扩展** param proxy 代理对象* param method 对应于在代理对象上调用的接口方法Method实例* param args 代理对象调用接口方法时传递的实际参数* return 返回目标对象方法的返回值,没有返回值就返回null* throws Throwable*/Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(执行目标对象方法执行 开始);//执行目标对象方法method.invoke(target,args);System.out.println(执行目标对象方法执行 结束);return null;}});}
}/******************************************************** 测试********************************************************/
public class Example02 {public static void main(String[] args) {IUserDao userDao new UserDaoImpl();System.out.println(userDao.getClass().toString());ProxyFactory factory new ProxyFactory(userDao);//生成 IUserDao 的代理对象IUserDao proxyObject (IUserDao) factory.getProxyInstance();System.out.println(proxyObject.getClass().toString());//执行代理对象的目标方法proxyObject.save();}
}
7.3.2.4 jdk动态代理中代理类是如何生成的 我们知道Java虚拟机类加载过程主要分为五个阶段加载、验证、准备、解析、初始化。 其中加载阶段需要完成以下3件事情 1通过一个类的全限定名来获取定义此类的二进制字节流 2将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 3在内存中生成一个代表这个类的 java.lang.Class 对象作为方法区这个类的各种数据访问入口由于虚拟机规范对这3点要求并不具体所以实际的实现是非常灵活的关于第1点获取类的二进制字节流class字节码就有很多途径即 1从本地获取 2从网络中获取 3运行时计算生成这种场景使用最多的是动态代理技术在 java.lang.reflect.Proxy类中就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为$Proxy 的代理类的二进制字节流流程如下所示 从这里可以看出jdk动态代理就是采用“运行时计算生成” 的方式来获取代理对象的二进制字节流。所以jdk动态代理本质上就是想办法根据接口或目标对象计算出代理类的字节码然后再加载到JVM中使用 7.3.2.5、jdk代理类的调用过程 通过借用阿里巴巴的一款线上监控诊断产品 Arthas(阿尔萨斯) ,对动态生成的代理类代码进行查看如下所示 上边类 UserDaoImpl 的代理类代码如下 public final class $Proxy0
extends Proxy
implements IUserDao {private static Method m3;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m3 Class.forName(com.mashibing.proxy.example01.IUserDao).getMethod(save, new Class[0]);return;}}public final void save() {try {this.h.invoke(this, m3, null);return;}}//。。。。。。。。。。。。。其他代码省略 。。。。。。。。。。。。。
} 1动态代理类对象 继承了 Proxy 类并且实现了被代理的所有接口以及equals、hashCode、toString等方法 2代理类的构造函数参数是InvocationHandler实例Proxy.newInstance方法就是通过这个构造函数来创建代理实例的 3类和所有方法都被 public final 修饰所以代理类只可被使用不可以再被继承 4每个方法都有一个 Method 对象来描述Method 对象在static静态代码块中创建以 m 数字 的格式命名 5调用方法的时候通过 this.h.invoke(this, m3, null)); **实际上 h.invoke就是在调用ProxyFactory中我们重写的invoke方法 7.3.2.6 总结 jdk动态代理总结虽然相对于静态代理动态代理大大减少了我们的开发任务同时减少了对业务接口的依赖降低了耦合度。但是还是有一点点小小的遗憾之处那就是它始终无法摆脱仅支持interface代理的桎梏我们要使用被代理的对象的接口因为它的设计注定了这个遗憾。 7.3.3 CGLIB动态代理
7.3.3.1 CGLIB动态代理实现 CGLIB(Code Generation Library ) 是一个第三方代码生成类库运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理为JDK的动态代理提供了很好的补充。 1最底层是字节码 2ASM是操作字节码的工具 3cglib基于ASM字节码工具操作字节码即动态生成代理对方法进行增强 4SpringAOP基于cglib进行封装实现cglib方式的动态代理 因为cglib是第三方插件使用cglib 需要引入cglib 的jar包如果你已经有spring-core的 jar包则无需引入因为spring中包含了cglib 如下所示 dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.2.5/version
/dependency 7.3.3.2 CGLIB动态代理代码示例
/******************************************************** 目标类*******************************************************/
public class UserServiceImpl {//查询功能public ListUser findUserList(){return Collections.singletonList(new User(tom,18));}
}/******************************************************** 用于生成代理类* 需要实现接口 MethodInterceptor********************************************************/
public class UserLogProxy implements MethodInterceptor {//目标类private Object target;/**** param target 需要创建代理的目标对象*/public UserLogProxy(Object target) {this.target target;}/*** 生成CGLIB动态代理类的方法* return 返回生成的代理对象*/public Object getLogProxy(){//增强器类,用来创建动态代理类Enhancer enhancer new Enhancer();//设置代理类的父类字节码对象即目标类的Class对象enhancer.setSuperclass(target.getClass());//设置回调: 对于代理类上所有的方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截enhancer.setCallback(this);//创建动态代理对象并返回return enhancer.create();}/*** 实现回调方法* param o 代理对象* param method 目标对象中的方法的Method实例* param objects 实际参数* param methodProxy 代理对象中的方法的method实例* return: java.lang.Object*/Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Calendar calendar Calendar.getInstance();SimpleDateFormat formatter new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);System.out.println(formatter.format(calendar.getTime()) [ method.getName() ] 查询用户信息...]);Object result methodProxy.invokeSuper(o, objects);return result;}
}/******************************************************** 测试********************************************************/
public class Example03 {public static void main(String[] args) {//目标类UserServiceImpl userService new UserServiceImpl();System.out.println(userService.getClass().toString());//创建代理对象UserLogProxy proxy new UserLogProxy(userService);UserServiceImpl userServiceProxy (UserServiceImpl) proxy.getLogProxy();System.out.println(userServiceProxy.getClass().toString());//执行代理对象目标方法ListUser userList userServiceProxy.findUserList();System.out.println(用户信息: userList);}
}NoArgsConstructor
AllArgsConstructor
Data
public class User {private String name;private Integer age;
}
7.3.3.3 CGLIB动态代理执行流程 CGLIB动态代理执行流程如下图所示 7.3.3.4 总结 CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象因为无需频繁创建对象用CGLIB合适反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法对于final修饰的方法无法进行代理。 7.4 代理模式总结 1、三种代理模式实现方式对比 1cglib代理和jdk代理对比 Icglib底层采用ASM字节码生成框架使用字节码技术生成代理类在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是cglib不能对声明为final的类或者方法进行代理因为cglib原理是动态生成被代理类的子类。 II在JDK1.6之后逐步对JDK动态代理优化之后在调用次数较少的情况下JDK代理效率高于cglib代理效率只有当进行大量调用的时候JDK1.6和JDK1.7比cglib代理效率低一点但是到JDK1.8的时候JDK代理效率高于cglib代理。所以如果有接口使用JDK动态代理如果没有接口使用cglib代理 2静态代理和动态代理对比 I动态代理与静态代理相比较最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理InvocationHandler.invoke。这样在接口方法数量比较多的时候我们可以进行灵活处理而不需要像静态代理那样每一个方法进行中转。 II如果接口增加一个方法静态代理模式除了所有实现类需要实现这个方法外所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题 2、代理模式优缺点 1优点 I代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用 II代理对象可以扩展目标对象的功能 III代理模式能将客户端与目标对象分离在一定程度上降低了系统的耦合度 2缺点 I增加了系统的复杂度 8 外观模式
8.1 定义 定义 隐藏了系统的复杂性并向客户端提供了一个可以访问系统的接口。 8.2 模式结构 简单来说该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中设计到3个角色。 1门面角色外观模式的核心。它被客户角色调用它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。客户调用同时自身调用子系统功能 2子系统角色实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互也可以由供外界调用的接口。实现具体功能 3客户角色通过调用Facede来完成要实现的功能调用门面角色。 8.3 代码示例
举例每个Computer都有CPU、Memory、Disk。在Computer开启和关闭的时候相应的部件也会开启和关闭
//子系统类
public class CPU {public void start() {System.out.println(cpu is start...);}public void shutDown() {System.out.println(CPU is shutDown...);}
}public class Disk {public void start() {System.out.println(Disk is start...);}public void shutDown() {System.out.println(Disk is shutDown...);}
}public class Memory {public void start() {System.out.println(Memory is start...);}public void shutDown() {System.out.println(Memory is shutDown...);}
}//门面类Facade
public class Computer {private CPU cpu;private Memory memory;private Disk disk;public Computer() {cpu new CPU();memory new Memory();disk new Disk();}public void start() {System.out.println(Computer start begin);cpu.start();disk.start();memory.start();System.out.println(Computer start end);}public void shutDown() {System.out.println(Computer shutDown begin);cpu.shutDown();disk.shutDown();memory.shutDown();System.out.println(Computer shutDown end...);}
}//客户角色
public class Client {public static void main(String[] args) {Computer computer new Computer();computer.start();System.out.println();computer.shutDown();}}8.4 总结 优点1松散耦合 使得客户端和子系统之间解耦让子系统内部的模块功能更容易扩展和维护 2简单易用 客户端根本不需要知道子系统内部的实现或者根本不需要知道子系统内部的构成它只需要跟Facade类交互即可。 3更好的划分访问层次 有些方法是对系统外的有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中这样就可以实现客户端的使用很好的隐藏了子系统内部的细节。 9 桥接模式
9.1 定义 定义 将抽象部分与它的实现部分分离使它们都可以独立地变化。 桥接模式用一种巧妙的方式处理多层继承存在的问题用抽象关联来取代传统的多层继承将类之间的静态继承关系转变为动态的组合关系使得系统更加灵活并易于扩展有效的控制了系统中类的个数 (避免了继承层次的指数级爆炸)。 9.2 模式结构 桥接模式Bridge包含以下角色 1抽象化Abstraction角色 主要负责定义出该角色的行为 ,并包含一个对实现化对象的引用。 2扩展抽象化RefinedAbstraction角色 是抽象化角色的子类实现父类中的业务方法并通过组合关系调用实现化角色中的业务方法。 3实现化Implementor角色 定义实现化角色的接口包含角色必须的行为和属性并供扩展抽象化角色调用。 4具体实现化Concrete Implementor角色 给出实现化角色接口的具体实现。 9.3 模式原理 桥接模式原理的核心是: 首先有要识别出一个类所具有的的两个独立变化维度将它们设计为两个独立的继承等级结构为两个维度都提供抽象层并建立抽象耦合。 总结一句话就是: 抽象角色引用实现角色。 如我们拿毛笔举例, 型号和颜色是毛笔的两个维度 1型号是其固有的维度所以抽象出一个毛笔类而将各种型号的毛笔作为其子类也就是下图的左侧抽象部分内容。 2颜色是毛笔的另一个维度它与毛笔之间存在一种设置的关系因此可以提供一个抽象的颜色接口将具体颜色作为该接口的子类。 9.4 代码示例
以当下支付场景为例看下不同支付模式中桥接模式的应用如微信和支付宝都可以完成支付操作而支付操作又可以有扫码支付、密码支付、人脸支付等那么关于支付操作其实就有两个维度包括支付渠道和支付方式如下图所示 9.4.1 不使用设计模式来模拟实现不同模式的支付场景
/******************************************************** 模拟网络支付场景* 模拟不同的支付工具对应不同的支付模式,比如微信和支付宝都可以完成支付操作,而支付操作又可以有扫码支付、密码支付、人脸支付等,* 那么关于支付操作其实就有两个维度, 包括:支付渠道和支付方式** 不使用设计模式实现** 不使用设计模式缺点* 维护和扩展都会变得非常复杂需要修改原来代码风险较大********************************************************/
public class PayController {/*** param uId 用户id* param tradeId 交易流水号* param amount 交易金额* param channelType 渠道类型 1 微信, 2 支付宝* param modeType 支付模式 1 密码,2 人脸,3 指纹* return: boolean*/public boolean doPay(String uId, String tradeId, BigDecimal amount, int channelType, int modeType){//微信支付if(1 channelType){System.out.println(微信渠道支付划账开始......);if(1 modeType){System.out.println(密码支付);}if(2 modeType){System.out.println(人脸支付);}if(3 modeType){System.out.println(指纹支付);}}//支付宝支付if(2 channelType){System.out.println(支付宝渠道支付划账开始......);if(1 modeType){System.out.println(密码支付);}if(2 modeType){System.out.println(人脸支付);}if(3 modeType){System.out.println(指纹支付);}}return true;}
}//测试
public class Test {public static void main(String[] args) {PayController payController new PayController();System.out.println(测试: 微信支付、人脸支付方式);payController.doPay(weixin_001,1000112333333,new BigDecimal(100),1,2);System.out.println(\n测试: 支付宝支付、指纹支付方式);payController.doPay(hifubao_002,1000112334567,new BigDecimal(100),2,3);}
}
虽然不使用设计模式也能实现该支付场景需求但以后若增加支付渠道或修改支付方式则成本比较高不利于后边的扩展和维护。
9.4.2 使用桥接模式重构支付场景代码
我们知道桥接模式原理的核心是: 首先有要识别出一个类所具有的的两个独立变化维度将它们设计为两个独立的继承等级结构为两个维度都提供抽象层并建立抽象耦合。针对该支付场景上边已经抽出了2个维度即支付渠道 和 支付方式这里我们可以把支付渠道作为抽象化角色支付方式作为实现化角色支付渠道*支付模式 相对应的支付组合这样就得到下边2个类
1Pay抽象类支付渠道 I支付渠道子类: 微信支付 II支付渠道子类: 支付宝支付 2IPayMode接口支付方式 I支付模式实现: 刷脸支付 II支付模式实现: 指纹支付 III密码支付 类图如下 示例代码如下 实现化角色代码
/*** 支付模式接口* 实现化Implementor角色*/
public interface IPayMode {//安全校验功能: 对各种支付模式进行风控校验boolean security(String uId);
}/******************************************************** 密码支付及风控校验* 具体实现化Concrete Implementor角色********************************************************/
public class PayCypher implements IPayMode{Overridepublic boolean security(String uId) {return false;}
}/******************************************************** 刷脸支付及风控校验* 具体实现化Concrete Implementor角色* *******************************************************/
public class PayFaceMode implements IPayMode{Overridepublic boolean security(String uId) {return true;}
}/******************************************************** 指纹支付及风控校验* 具体实现化Concrete Implementor角色********************************************************/
public class PayFingerprintMode implements IPayMode{Overridepublic boolean security(String uId) {return false;}
}抽象化角色代码
/******************************************************** 支付抽象化类* 抽象化Abstraction角色********************************************************/
public abstract class Pay {protected IPayMode payMode;/*** todo 注意* 抽象类的公共构造函数不能用来new 创建对象抽象类不能实例化* 该构造函数必须由子类调用在子类构造函数中通过super调用** param payMode*/public Pay(IPayMode payMode){this.payMode payMode;}//划账功能public abstract String transfer(String uId, String tradeId, BigDecimal amount);}/******************************************************** 支付渠道-微信* 扩展抽象化RefinedAbstraction角色* *******************************************************/
public class WxPay extends Pay{public WxPay(IPayMode payMode) {super(payMode);}Overridepublic String transfer(String uId, String tradeId, BigDecimal amount) {System.out.println(微信渠道支付划账开始......);//支付方式校验boolean security payMode.security(uId);System.out.println(微信渠道支付风险校验: uId , tradeId , security);if(!security){System.out.println(微信渠道支付划账失败!);return 500;}System.out.println(微信渠道划账成功! 金额: amount);return 200;}
}/******************************************************** 支付渠道--支付宝* 扩展抽象化RefinedAbstraction角色* *******************************************************/
public class ZfbPay extends Pay{public ZfbPay(IPayMode payMode) {super(payMode);}Overridepublic String transfer(String uId, String tradeId, BigDecimal amount) {System.out.println(支付宝渠道支付划账开始......);//支付方式校验boolean security payMode.security(uId);System.out.println(支付宝渠道支付风险校验: uId , tradeId , security);if(!security){System.out.println(支付宝渠道支付划账失败!);return 500;}System.out.println(支付宝渠道划账成功! 金额: amount);return 200;}
}测试
//测试
public class Test {public static void main(String[] args) {System.out.println(测试场景1: 微信支付、人脸方式.);Pay wxpay new WxPay(new PayFaceMode());wxpay.transfer(wx_00100100,10001900,new BigDecimal(100));System.out.println();System.out.println(测试场景2: 支付宝支付、指纹方式);Pay zfbPay new ZfbPay(new PayFingerprintMode());zfbPay.transfer(jlu1234567,567689999999,new BigDecimal(200));}
}
9.5 桥接模式优缺点 1、桥接模式优点 1分离抽象接口及其实现部分桥接模式使用对象间的关联关系解耦了抽象和实现之间固有的绑定关系使得抽象和实现可以沿着各自的维度来变化 2在很多情况下桥接模式可以取代多层继承方案多层继承方案违背了单一职责原则复用性差类的个数多桥接模式很好的解决了这些问题 3桥接模式提高了系统的扩展性在两个变化维度中任意扩展一个维度都不需要修改原 有系统符合开闭原则 2、桥接模式缺点 1桥接模式的使用会增加系统的理解和设计难度由于关联关系建立在抽象层要求开发者一开始就要对抽象层进行设计和编程 2桥接模式要求正确识别出系统中的两个独立变化的维度因此具有一定的局限性并且如果正确的进行维度的划分也需要相当丰富的经验 10 组合模式
10.1 定义 定义有时又叫作部分-整体模式它是一种将对象组合成树状的层次结构的模式用来表示“部分-整体”的关系使用户对单个对象和组合对象具有一致的访问性。 意图将对象组合成树形结构以表示部分-整体的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 主要解决它在我们树型结构的问题中模糊了简单元素和复杂元素的概念客户程序可以向处理简单元素一样来处理复杂元素从而使得客户程序与复杂元素的内部结构解耦。 何时使用 1、您想表示对象的部分-整体层次结构树形结构。 2、您希望用户忽略组合对象与单个对象的不同用户将统一地使用组合结构中的所有对象。 如何解决树枝和叶子实现统一接口树枝内部组合该接口。 关键代码树枝内部组合该接口并且含有内部属性 List里面放 Component。 10.2 模式结构 抽象构件Component角色它的主要作用是为树叶构件和树枝构件声明公共接口并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口在安全式的组合模式中不声明访问和管理子类的接口管理工作由树枝构件完成。 树叶构件Leaf角色是组合中的叶节点对象它没有子节点用于实现抽象构件角色中 声明的公共接口。 树枝构件Composite角色是组合中的分支节点对象它有子节点。它实现了抽象构件角色中声明的接口它的主要作用是存储和管理子部件通常包含 Add()、Remove()、GetChild() 等方法。 10.3 代码示例
举例访问一颗树
组件
public interface Component {public void add(Component c);public void remove(Component c);public Component getChild(int i);public void operation();}
叶子 public class Leaf implements Component{private String name;public Leaf(String name) {this.name name;}Overridepublic void add(Component c) {}Overridepublic void remove(Component c) {}Overridepublic Component getChild(int i) {// TODO Auto-generated method stubreturn null;}Overridepublic void operation() {// TODO Auto-generated method stubSystem.out.println(树叶name被访问); }
}
树枝
public class Composite implements Component {private ArrayListComponent children new ArrayListComponent();public void add(Component c) {children.add(c);}public void remove(Component c) {children.remove(c);}public Component getChild(int i) {return children.get(i);}public void operation() {for (Object obj : children) {((Component) obj).operation();}}
} 10.4 组合模式优缺点 组合模式的主要优点有 1组合模式使得客户端代码可以一致地处理单个对象和组合对象无须关心自己处理的是单个对象还是组合对象这简化了客户端代码 2更容易在组合体内加入新的对象客户端不会因为加入了新的对象而更改源代码满足“开闭原则” 其主要缺点是 1设计较复杂客户端需要花更多时间理清类之间的层次关系 2不容易限制容器中的构件 3不容易用继承的方法来增加构件的新功能 11 享元模式
11.1 定义 定义通过共享的方式高效的支持大量细粒度的对象。 主要解决在有大量对象时有可能会造成内存溢出我们把其中共同的部分抽象出来如果有相同的业务请求直接返回在内存中已有的对象避免重新创建。 何时使用 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组当把外蕴对象从对象中剔除出来时每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份这些对象是不可分辨的。 如何解决用唯一标识码判断如果在内存中有则返回这个唯一标识码所标识的对象。 关键代码用 HashMap 存储这些对象。 应用实例 1、JAVA 中的 String如果有则返回如果没有则创建一个字符串保存在字符串缓存池里面。 11.2 享元模式的结构图 1、Flyweight (享元抽象类)一般是接口或者抽象类定义了享元类的公共方法。这些方法可以分享内部状态的数据也可以调用这些方法修改外部状态。 2、ConcreteFlyweight(具体享元类)具体享元类实现了抽象享元类的方法为享元对象开辟了内存空间来保存享元对象的内部数据同时可以通过和单例模式结合只创建一个享元对象。 3、FlyweightFactory(享元工厂类)享元工厂类创建并且管理享元类享元工厂类针对享元类来进行编程通过提供一个享元池来进行享元对象的管理。一般享元池设计成键值对或者其他的存储结构来存储。当客户端进行享元对象的请求时如果享元池中有对应的享元对象则直接返回对应的对象否则工厂类创建对应的享元对象并保存到享元池。 11.3 代码示例
举例JAVA 中的 String如果有则返回如果没有则创建一个字符串保存在字符串缓存池里面
/**创建享元对象接口
*/
public interface IFlyweight {void print();
}/**创建具体享元对象
*/
public class Flyweight implements IFlyweight {private String id;public Flyweight(String id){this.id id;}Overridepublic void print() {System.out.println(Flyweight.id getId() ...);}public String getId() {return id;}
}/**创建工厂这里要特别注意为了避免享元对象被重复创建我们使用HashMap中的key值保证其唯一。
*/
public class FlyweightFactory {private MapString, IFlyweight flyweightMap new HashMap();public IFlyweight getFlyweight(String str){IFlyweight flyweight flyweightMap.get(str);if(flyweight null){flyweight new Flyweight(str);flyweightMap.put(str, flyweight);}return flyweight;}public int getFlyweightMapSize(){return flyweightMap.size();}
}/**测试我们创建三个字符串但是只会产生两个享元对象
*/
public class MainTest {public static void main(String[] args) {FlyweightFactory flyweightFactory new FlyweightFactory();IFlyweight flyweight1 flyweightFactory.getFlyweight(A);IFlyweight flyweight2 flyweightFactory.getFlyweight(B);IFlyweight flyweight3 flyweightFactory.getFlyweight(A);flyweight1.print();flyweight2.print();flyweight3.print();System.out.println(flyweightFactory.getFlyweightMapSize());//2}}11.4 享元模式优缺点 优点大大减少对象的创建降低系统的内存使效率提高。 缺点提高了系统的复杂度需要分离出外部状态和内部状态而且外部状态具有固有化的性质不应该随着内部状态的变化而变化否则会造成系统的混乱。 简单来说我们抽取出一个对象的外部状态不能共享和内部状态可以共享。然后根据外部状态的决定是否创建内部状态对象。内部状态对象是通过哈希表保存的当外部状态相同的时候不再重复的创建内部状态对象从而减少要创建对象的数量。 C、关系模式11种
策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、 命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
先来张图看看这11中模式的关系
第一类通过父类与子类的关系进行实现。
第二类两个类之间。
第三类类的状态。
第四类通过中间类。 12 策略模式
12.1 定义
定义 策略模式定义了一系列算法并将每个算法封装起来使他们可以相互替换且算法的变化不会影响到使用算法的客户。
意图定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决在有多种算法相似的情况下使用 if…else 所带来的复杂和难以维护。
何时使用一个系统有许多许多类而区分它们的只是他们直接的行为。
如何解决将这些算法封装成一个一个的类任意地替换。
关键代码实现同一个接口。
12.2 模式结构 抽象策略角色: 这个是一个抽象的角色通常情况下使用接口或者抽象类去实现。对比来说就是我们的Comparator接口。 具体策略角色: 包装了具体的算法和行为。对比来说就是实现了Comparator接口的实现一组实现类。 环境角色: 内部会持有一个抽象角色的引用给客户端调用。 12.3 代码示例
举例如下 实现一个加减的功能
//定义抽象策略角色
public interface Strategy {int calc(int num1,int num2);}//定义具体策略角色
public class AddStrategy implements Strategy {Overridepublic int calc(int num1, int num2) {// TODO Auto-generated method stubreturn num1 num2;}}public class SubstractStrategy implements Strategy {Overridepublic int calc(int num1, int num2) {// TODO Auto-generated method stubreturn num1 - num2;}}//环境角色
public class Environment {private Strategy strategy;public Environment(Strategy strategy) {this.strategy strategy;}public int calculate(int a, int b) {return strategy.calc(a, b);}
}//测试
public class MainTest {public static void main(String[] args) {Environment environmentnew Environment(new AddStrategy());int resultenvironment.calculate(20, 5);System.out.println(result);Environment environment1new Environment(new SubstractStrategy());int result1environment1.calculate(20, 5);System.out.println(result1);}} 12.4 策略模式优缺点 优点 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。 缺点 1、策略类会增多。 2、所有策略类都需要对外暴露。 13 模板模式
13.1 定义 定义定义一个操作中算法的骨架而将一些步骤延迟到子类中模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。 通俗点的理解就是 完成一件事情有固定的数个步骤但是每个步骤根据对象的不同而实现细节不同就可以在父类中定义一个完成该事情的总方法按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现由子类完成。 13.2 模式结构 抽象父类AbstractClass实现了模板方法定义了算法的骨架。 具体类ConcreteClass)实现抽象类中的抽象方法即不同的对象的具体实现细节。 13.3 代码示例
举例我们做菜可以分为三个步骤
1备料
2具体做菜
3盛菜端给客人享用
这三部就是算法的骨架 然而做不同菜需要的料做的方法以及如何盛装给客人享用都是不同的这个就是不同的实现细节。
a. 先来写一个抽象的做菜父类
public abstract class Dish {/*** 具体的整个过程*/protected void dodish(){this.preparation();this.doing();this.carriedDishes();}/*** 备料*/public abstract void preparation();/*** 做菜*/public abstract void doing();/*** 上菜*/public abstract void carriedDishes ();
}
b. 下来做两个番茄炒蛋EggsWithTomato和红烧肉Bouilli实现父类中的抽象方法
public class EggsWithTomato extends Dish {Overridepublic void preparation() {System.out.println(洗并切西红柿打鸡蛋。);}Overridepublic void doing() {System.out.println(鸡蛋倒入锅里然后倒入西红柿一起炒。);}Overridepublic void carriedDishes() {System.out.println(将炒好的西红寺鸡蛋装入碟子里端给客人吃。);}}
public class Bouilli extends Dish{Overridepublic void preparation() {System.out.println(切猪肉和土豆。);}Overridepublic void doing() {System.out.println(将切好的猪肉倒入锅中炒一会然后倒入土豆连炒带炖。);}Overridepublic void carriedDishes() {System.out.println(将做好的红烧肉盛进碗里端给客人吃。);}}c. 在测试类中我们来做菜
public class MainTest {public static void main(String[] args) {Dish eggsWithTomato new EggsWithTomato();eggsWithTomato.dodish();System.out.println(-----------------------------);Dish bouilli new Bouilli();bouilli.dodish();}}13.4 模板模式的优点和缺点 优点 1具体细节步骤实现定义在子类中子类定义详细处理算法是不会改变算法整体结构。 2代码复用的基本技术在数据库设计中尤为重要。 3存在一种反向的控制结构通过一个父类调用其子类的操作通过子类对父类进行扩展增加新的行为符合“开闭原则”。 缺点 每个不同的实现都需要定义一个子类会导致类的个数增加系统更加庞大。 14 观察者模式
14.1 定义 定义 定义对象间的一种一对多的依赖关系当一个对象的状态发生改变时所有依赖于它的对象都得到通知并被自动更新。 主要解决一个对象状态改变给其他对象通知的问题而且要考虑到易用和低耦合保证高度的协作。 何时使用一个对象目标对象的状态发生改变所有的依赖对象观察者对象都将得到通知进行广播通知。 如何解决使用面向对象技术可以将这种依赖关系弱化。 关键代码在抽象类里有一个 ArrayList 存放观察者们。 14.2 模式结构图 抽象被观察者角色也就是一个抽象主题它把所有对观察者对象的引用保存在一个集合中每个主题都可以有任意数量的观察者。抽象主题提供一个接口可以增加和删除观察者角色。一般用一个抽象类和接口来实现。 抽象观察者角色为所有的具体观察者定义一个接口在得到主题通知时更新自己。 具体被观察者角色也就是一个具体的主题在集体主题的内部状态改变时所有登记过的观察者发出通知。 具体观察者角色实现抽象观察者角色所需要的更新接口一边使本身的状态与制图的状态相协调。 14.3 代码示例
举例有一个微信公众号服务不定时发布一些消息关注公众号就可以收到推送消息取消关注就收不到推送消息。
a. 定义一个抽象被观察者接口
public interface Subject {public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObserver();}b. 定义一个抽象观察者接口
public interface Observer {public void update(String message);}c. 定义被观察者实现了Observerable接口对Observerable接口的三个方法进行了具体实现同时有一个List集合用以保存注册的观察者等需要通知观察者时遍历该集合即可。
public class WechatServer implements Subject {private ListObserver list;private String message;public WechatServer() {list new ArrayListObserver();}Overridepublic void registerObserver(Observer o) {// TODO Auto-generated method stublist.add(o);}Overridepublic void removeObserver(Observer o) {// TODO Auto-generated method stubif (!list.isEmpty()) {list.remove(o);}}Overridepublic void notifyObserver() {// TODO Auto-generated method stubfor (Observer o : list) {o.update(message);}}public void setInfomation(String s) {this.message s;System.out.println(微信服务更新消息 s);// 消息更新通知所有观察者notifyObserver();}}d. 定义具体观察者微信公众号的具体观察者为用户User
public class User implements Observer {private String name;private String message;public User(String name) {this.name name;}Overridepublic void update(String message) {this.message message;read();}public void read() {System.out.println(name 收到推送消息 message);}}e. 编写一个测试类
public class MainTest {public static void main(String[] args) {WechatServer server new WechatServer();Observer userZhang new User(ZhangSan);Observer userLi new User(LiSi);Observer userWang new User(WangWu);server.registerObserver(userZhang);server.registerObserver(userLi);server.registerObserver(userWang);server.setInfomation(PHP是世界上最好用的语言);System.out.println(----------------------------------------------);server.removeObserver(userZhang);server.setInfomation(JAVA是世界上最好用的语言);}}14.4 优缺点 优点 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。 缺点 1、如果一个被观察者对象有很多的直接和间接的观察者的话将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话观察目标会触发它们之间进行循环调用可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的而仅仅只是知道观察目标发生了变化。 15 迭代器模式
15.1 定义 定义提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。 简单来说不同种类的对象可能需要不同的遍历方式我们对每一种类型的对象配一个迭代器最后多个迭代器合成一个。 主要解决不同的方式来遍历整个整合对象。 何时使用遍历一个聚合对象。 如何解决把在元素之间游走的责任交给迭代器而不是聚合对象。 关键代码定义接口hasNext, next。 应用实例JAVA 中的 iterator。 15.2 模式结构 (1)迭代器角色Iterator:定义遍历元素所需要的方法一般来说会有这么三个方法取得下一个元素的方法next()判断是否遍历结束的方法hasNext()移出当前对象的方法remove(), (2)具体迭代器角色Concrete Iterator实现迭代器接口中定义的方法完成集合的迭代。 (3)容器角色(Aggregate): 一般是一个接口提供一个iterator()方法例如java中的Collection接口List接口Set接口等 (4)具体容器角色ConcreteAggregate就是抽象容器的具体实现类比如List接口的有序列表实现ArrayListList接口的链表实现LinkListSet接口的哈希列表的实现HashSet等。 15.3 代码示例
举例咖啡厅和中餐厅合并他们两个餐厅的菜单一个是数组保存的一个是ArrayList保存的。遍历方式不一样使用迭代器聚合访问只需要一种方式
1 迭代器接口
public interface Iterator {public boolean hasNext();public Object next();}2 咖啡店菜单和咖啡店菜单遍历器
public class CakeHouseMenu {private ArrayListMenuItem menuItems;public CakeHouseMenu() {menuItems new ArrayListMenuItem();addItem(KFC Cake Breakfast,boiled eggstoastcabbage,true,3.99f);addItem(MDL Cake Breakfast,fried eggstoast,false,3.59f);addItem(Stawberry Cake,fresh stawberry,true,3.29f);addItem(Regular Cake Breakfast,toastsausage,true,2.59f);}private void addItem(String name, String description, boolean vegetable,float price) {MenuItem menuItem new MenuItem(name, description, vegetable, price);menuItems.add(menuItem);}public Iterator getIterator(){return new CakeHouseIterator() ;}class CakeHouseIterator implements Iterator{ private int position0;public CakeHouseIterator(){position0;}Overridepublic boolean hasNext() {// TODO Auto-generated method stubif(positionmenuItems.size()){return true;}return false;}Overridepublic Object next() {// TODO Auto-generated method stubMenuItem menuItem menuItems.get(position);position;return menuItem;}};//鍏朵粬鍔熻兘浠爜}3 中餐厅菜单和中餐厅菜单遍历器
public class DinerMenu {private final static int Max_Items 5;private int numberOfItems 0;private MenuItem[] menuItems;public DinerMenu() {menuItems new MenuItem[Max_Items];addItem(vegetable Blt, baconlettucetomatocabbage, true, 3.58f);addItem(Blt, baconlettucetomato, false, 3.00f);addItem(bean soup, beanpotato salad, true, 3.28f);addItem(hotdog, onionscheesebread, false, 3.05f);}private void addItem(String name, String description, boolean vegetable,float price) {MenuItem menuItem new MenuItem(name, description, vegetable, price);if (numberOfItems Max_Items) {System.err.println(sorry,menu is full!can not add another item);} else {menuItems[numberOfItems] menuItem;numberOfItems;}}public Iterator getIterator() {return new DinerIterator();}class DinerIterator implements Iterator {private int position;public DinerIterator() {position 0;}Overridepublic boolean hasNext() {// TODO Auto-generated method stubif (position numberOfItems) {return true;}return false;}Overridepublic Object next() {// TODO Auto-generated method stubMenuItem menuItem menuItems[position];position;return menuItem;}};
}4 女服务员
public class Waitress {private ArrayListIterator iterators new ArrayListIterator();public Waitress() {}public void addIterator(Iterator iterator) {iterators.add(iterator);}public void printMenu() {Iterator iterator;MenuItem menuItem;for (int i 0, len iterators.size(); i len; i) {iterator iterators.get(i);while (iterator.hasNext()) {menuItem (MenuItem) iterator.next();System.out.println(menuItem.getName() *** menuItem.getPrice() *** menuItem.getDescription());}}}public void printBreakfastMenu() {}public void printLunchMenu() {}public void printVegetableMenu() {}
}15.4 优缺点 优点 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中增加新的聚合类和迭代器类都很方便无须修改原有代码。 缺点由于迭代器模式将存储数据和遍历数据的职责分离增加新的聚合类需要对应增加新的迭代器类类的个数成对增加这在一定程度上增加了系统的复杂性。 16 责任链模式
16.1 定义 定义如果有多个对象有机会处理请求责任链可使请求的发送者和接受者解耦请求沿着责任链传递直到有一个对象处理了它为止。 主要解决职责链上的处理者负责处理请求客户只需要将请求发送到职责链上即可无须关心请求的处理细节和请求的传递所以职责链将请求的发送者和请求的处理者解耦了。 何时使用在处理消息的时候以过滤很多道。 如何解决拦截的类都实现统一接口。 关键代码Handler 里面聚合它自己在 HandlerRequest 里判断是否合适如果没达到条件则向下传递向谁传递之前 set 进去。 16.2 模式的结构 抽象处理者Handler角色定义一个处理请求的接口包含抽象处理方法和一个后继连接。 具体处理者Concrete Handler角色实现抽象处理者的处理方法判断能否处理本次请求如果可以处理请求则处理否则将该请求转给它的后继者。 客户类Client角色创建处理链并向链头的具体处理者对象提交请求它不关心处理细节和请求的传递过程。 16.3 代码示例
举例购买请求决策价格不同要由不同的级别决定组长、部长、副部、总裁
1 决策者抽象类包含对请求处理的函数同时还包含指定下一个决策者的函数
public abstract class Approver {Approver successor;String Name;public Approver(String Name){this.NameName;}public abstract void ProcessRequest( PurchaseRequest request);public void SetSuccessor(Approver successor) {// TODO Auto-generated method stubthis.successorsuccessor;}
}2 客户端以及请求
public class PurchaseRequest {private int Type 0;private int Number 0;private float Price 0;private int ID 0;public PurchaseRequest(int Type, int Number, float Price) {this.Type Type;this.Number Number;this.Price Price;}public int GetType() {return Type;}public float GetSum() {return Number * Price;}public int GetID() {return (int) (Math.random() * 1000);}
}
public class Client {public Client() {}public PurchaseRequest sendRequst(int Type, int Number, float Price) {return new PurchaseRequest(Type, Number, Price);}}3 组长、部长。。。继承决策者抽象类
public class GroupApprover extends Approver {public GroupApprover(String Name) {super(Name GroupLeader);// TODO Auto-generated constructor stub}Overridepublic void ProcessRequest(PurchaseRequest request) {// TODO Auto-generated method stubif (request.GetSum() 5000) {System.out.println(**This request request.GetID() will be handled by this.Name **);} else {successor.ProcessRequest(request);}}}
public class DepartmentApprover extends Approver {public DepartmentApprover(String Name) {super(Name DepartmentLeader);}Overridepublic void ProcessRequest(PurchaseRequest request) {// TODO Auto-generated method stubif ((5000 request.GetSum()) (request.GetSum() 10000)) {System.out.println(**This request request.GetID() will be handled by this.Name **);} else {successor.ProcessRequest(request);}}}4 测试
public class MainTest {public static void main(String[] args) {Client mClient new Client();Approver GroupLeader new GroupApprover(Tom);Approver DepartmentLeader new DepartmentApprover(Jerry);Approver VicePresident new VicePresidentApprover(Kate);Approver President new PresidentApprover(Bush);GroupLeader.SetSuccessor(VicePresident);DepartmentLeader.SetSuccessor(President);VicePresident.SetSuccessor(DepartmentLeader);President.SetSuccessor(GroupLeader);GroupLeader.ProcessRequest(mClient.sendRequst(1, 10000, 40));}}17 命令模式
17.1 定义 定义将一个请求封装为一个对象使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通这样方便将命令对象进行储存、传递、调用、增加与管理。 意图将一个请求封装成一个对象从而使您可以用不同的请求对客户进行参数化。 主要解决在软件系统中行为请求者与行为实现者通常是一种紧耦合的关系但某些场合比如需要对行为进行记录、撤销或重做、事务等处理时这种无法抵御变化的紧耦合的设计就不太合适。 何时使用在某些场合比如要对行为进行记录、撤销/重做、事务等处理这种无法抵御变化的紧耦合是不合适的。在这种情况下如何将行为请求者与行为实现者解耦将一组行为抽象为对象可以实现二者之间的松耦合。 如何解决通过调用者调用接受者执行命令顺序调用者→接受者→命令。 17.2 模式结构 1抽象命令类Command角色声明执行命令的接口拥有执行命令的抽象方法 execute()。 2具体命令角色Concrete Command角色是抽象命令类的具体实现类它拥有接收者对象并通过调用接收者的功能来完成命令要执行的操作。 3实现者/接收者Receiver角色执行命令功能的相关操作是具体命令对象业务的真正实现者。 4调用者/请求者Invoker角色是请求的发送者它通常拥有很多的命令对象并通过访问命令对象来执行相关请求它不直接访问接收者。 17.3 代码示例
代码举例开灯和关灯
1 命令抽象类
public interface Command {public void excute();public void undo();}
2 具体命令对象
public class TurnOffLight implements Command {private Light light;public TurnOffLight(Light light) {this.light light;}Overridepublic void excute() {// TODO Auto-generated method stublight.Off();}Overridepublic void undo() {// TODO Auto-generated method stublight.On();}}
3 实现者
public class Light {String loc ;public Light(String loc) {this.loc loc;}public void On() {System.out.println(loc On);}public void Off() {System.out.println(loc Off);}}
4 请求者
public class Contral{public void CommandExcute(Command command) {// TODO Auto-generated method stubcommand.excute();}public void CommandUndo(Command command) {// TODO Auto-generated method stubcommand.undo();}}
18 状态模式
18.1 定义 定义 在状态模式中我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。 简单理解一个拥有状态的context对象在不同的状态下其行为会发生改变。 意图允许对象在内部状态发生改变时改变它的行为对象看起来好像修改了它的类。 主要解决对象的行为依赖于它的状态属性并且可以根据它的状态改变而改变它的相关行为。 何时使用代码中包含大量与对象状态有关的条件语句。 如何解决将各种具体的状态类抽象出来。 关键代码通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且状态模式的实现类的方法一般返回值或者是改变实例变量的值。也就是说状态模式一般和对象的状态有关。实现类的方法有不同的功能覆盖接口中的方法。状态模式和命令模式一样也可以用于消除 if…else 等条件选择语句。 18.2 模式结构 State抽象状态角色接口或抽象类负责对象状态定义并且封装环境角色以实现状态切换。 ConcreteState具体状态角色具体状态主要有两个职责一是处理本状态下的事情二是从本状态如何过渡到其他状态。 Context环境角色定义客户端需要的接口并且负责具体状态的切换。 18.3 代码示例
举例人物在地点A向地点B移动在地点B向地点A移动
1 state接口
public interface State {public void stop();public void move();}
2 状态实例
public class PlaceA implements State {private Player context;public PlaceA(Player context) {this.context context;}Overridepublic void move() {System.out.println(处于地点A,开始向B移动);System.out.println(--------);context.setDirection(AB);context.setState(context.onMove);}Overridepublic void stop() {// TODO Auto-generated method stubSystem.out.println(正处在地点A不用停止移动);System.out.println(--------);}}
3 context(player)拥有状态的对象
public class Player {State placeA;State placeB;State onMove;private State state;private String direction;public Player() {direction AB;placeA new PlaceA(this);placeB new PlaceB(this);onMove new OnMove(this);this.state placeA;}public void move() {System.out.println(指令:开始移动);state.move();}public void stop() {System.out.println(指令:停止移动);state.stop();}public State getState() {return state;}public void setState(State state) {this.state state;}public void setDirection(String direction) {this.direction direction;}public String getDirection() {return direction;}}
18.4 优缺点 优点 1、封装了转换规则。 2、枚举可能的状态在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中并且可以方便地增加新的状态只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象从而减少系统中对象的个数。 缺点 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂如果使用不当将导致程序结构和代码的混乱。 3、状态模式对开闭原则的支持并不太好对于可以切换状态的状态模式增加新的状态类需要修改那些负责状态转换的源代码否则无法切换到新增状态而且修改某个状态类的行为也需修改对应类的源代码。 19 备忘录模式
19.1 定义 定义 在不破坏封装性的前提下捕获一个对象的内部状态并在该对象之外保存这个状态以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。 19.2 模式结构 1发起人Originator角色记录当前时刻的内部状态信息提供创建备忘录和恢复备忘录数据的功能实现其他业务功能它可以访问备忘录里的所有信息。 2备忘录Memento角色负责存储发起人的内部状态在需要的时候提供这些内部状态给发起人。 3管理者Caretaker角色对备忘录进行管理提供保存与获取备忘录的功能但其不能对备忘录的内容进行访问与修改。 19.3 代码示例
1 备忘录接口
public interface MementoIF {
} 2 备忘录
public class Memento implements MementoIF{private String state;public Memento(String state) {this.state state;}public String getState(){return state;}} 3 发起者
public class Originator {private String state;public String getState() {return state;}public void setState(String state) {this.state state;}public Memento saveToMemento() {return new Memento(state);}public String getStateFromMemento(MementoIF memento) {return ((Memento) memento).getState();}} 4 管理者
public class CareTaker {private ListMementoIF mementoList new ArrayListMementoIF();public void add(MementoIF memento) {mementoList.add(memento);}public MementoIF get(int index) {return mementoList.get(index);}}
19.4 优缺点 备忘录模式是一种对象行为型模式其主要优点如下。 1提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。 2实现了内部状态的封装。除了创建它的发起人之外其他对象都不能够访问这些状态信息。 3简化了发起人类。发起人不需要管理和保存其内部状态的各个备份所有状态信息都保存在备忘录中并由管理者进行管理这符合单一职责原则。 其主要缺点是 资源消耗大。如果要保存的内部状态信息过多或者特别频繁将会占用比较大的内存资源。 20 访问者模式
20.1 定义 定义将作用于某种数据结构中的各元素的操作分离出来封装成独立的类使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离。 20.2 优缺点 访问者Visitor模式是一种对象行为型模式其主要优点如下。 1扩展性好。能够在不修改对象结构中的元素的情况下为对象结构中的元素添加新的功能。 2复用性好。可以通过访问者来定义整个对象结构通用的功能从而提高系统的复用程度。 3灵活性好。访问者模式将数据结构与作用于结构上的操作解耦使得操作集合可相对自由地演化而不影响系统的数据结构。 4符合单一职责原则。访问者模式把相关的行为封装在一起构成一个访问者使每一个访问者的功能都比较单一。 访问者Visitor模式的主要缺点如下。 1增加新的元素类很困难。在访问者模式中每增加一个新的元素类都要在每一个具体访问者类中增加相应的具体操作这违背了开闭原则。 2破坏封装。访问者模式中具体元素对访问者公布细节这破坏了对象的封装性。 3违反了依赖倒置原则。访问者模式依赖了具体类而没有依赖抽象类。 20.3 模式结构 访问者模式包含以下主要角色。 抽象访问者Visitor角色定义一个访问具体元素的接口为每个具体元素类对应一个访问操作 visit() 该操作中的参数类型标识了被访问的具体元素。 具体访问者ConcreteVisitor角色实现抽象访问者角色中声明的各个访问操作确定访问者访问一个元素时该做什么。 抽象元素Element角色声明一个包含接受操作 accept() 的接口被接受的访问者对象作为 accept() 方法的参数。 具体元素ConcreteElement角色实现抽象元素角色提供的 accept() 操作其方法体通常都是 visitor.visit(this) 另外具体元素中可能还包含本身业务逻辑的相关操作。 对象结构Object Structure角色是一个包含元素角色的容器提供让访问者对象遍历容器中的所有元素的方法通常由 List、Set、Map 等聚合类实现。 20.4 代码示例
1 抽象访问者
public interface Visitor {abstract public void Visit(Element element);
}
2 具体访问者
public class CompensationVisitor implements Visitor {Overridepublic void Visit(Element element) {// TODO Auto-generated method stubEmployee employee ((Employee) element);System.out.println(employee.getName() s Compensation is (employee.getDegree() * employee.getVacationDays() * 10));}}
3 抽象元素
public interface Element {abstract public void Accept(Visitor visitor);}
4 具体元素
public class CompensationVisitor implements Visitor {Overridepublic void Visit(Element element) {// TODO Auto-generated method stubEmployee employee ((Employee) element);System.out.println(employee.getName() s Compensation is (employee.getDegree() * employee.getVacationDays() * 10));}}
5 对象结构
public class ObjectStructure {private HashMapString, Employee employees;public ObjectStructure() {employees new HashMap();}public void Attach(Employee employee) {employees.put(employee.getName(), employee);}public void Detach(Employee employee) {employees.remove(employee);}public Employee getEmployee(String name) {return employees.get(name);}public void Accept(Visitor visitor) {for (Employee e : employees.values()) {e.Accept(visitor);}}}
21 中介者模式
21.1定义 定义定义一个中介对象来封装一系列对象之间的交互使原有对象之间的耦合松散且可以独立地改变它们之间的交互。中介者模式又叫调停模式它是迪米特法则的典型应用。 21.2 优缺点 中介者模式是一种对象行为型模式其主要优点如下。 1降低了对象之间的耦合性使得对象易于独立地被复用。 2将对象间的一对多关联转变为一对一的关联提高系统的灵活性使得系统易于维护和扩展。 其主要缺点是 当同事类太多时中介者的职责将很大它会变得复杂而庞大以至于系统难以维护。 21.3 模式结构 抽象中介者Mediator角色它是中介者的接口提供了同事对象注册与转发同事对象信息的抽象方法。 具体中介者ConcreteMediator角色实现中介者接口定义一个 List 来管理同事对象协调各个同事角色之间的交互关系因此它依赖于同事角色。 抽象同事类Colleague角色定义同事类的接口保存中介者对象提供同事对象交互的抽象方法实现所有相互影响的同事类的公共功能。 具体同事类Concrete Colleague角色是抽象同事类的实现者当需要与其他同事对象交互时由中介者对象负责后续的交互。 21.4 代码示例
举例通过中介卖方
1 抽象中介者
public interface Mediator {void register(Colleague colleague); // 客户注册void relay(String from, String to,String ad); // 转发}
2 具体中介者
public class ConcreteMediator implements Mediator {private ListColleague colleagues new ArrayListColleague();Overridepublic void register(Colleague colleague) {// TODO Auto-generated method stubif (!colleagues.contains(colleague)) {colleagues.add(colleague);colleague.setMedium(this);}}Overridepublic void relay(String from, String to, String ad) {// TODO Auto-generated method stubfor (Colleague cl : colleagues) {String name cl.getName();if (name.equals(to)) {cl.receive(from, ad);}}}}
3 抽象同事类
public abstract class Colleague {protected Mediator mediator;protected String name;public Colleague(String name) {this.name name;}public void setMedium(Mediator mediator) {this.mediator mediator;}public String getName() {return name;}public abstract void Send(String to, String ad);public abstract void receive(String from, String ad);}
4 具体同事类
public class Buyer extends Colleague {public Buyer(String name) {super(name);}Overridepublic void Send(String to, String ad) {// TODO Auto-generated method stubmediator.relay(name, to, ad);}Overridepublic void receive(String from, String ad) {// TODO Auto-generated method stubSystem.out.println(name 接收到来自 from 的消息: ad);}}