23 种设计模式详解
设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、
组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、
责任链模式、 命令模式、备忘录模式、状态模式、访问者模式、
中介者模式、解释器模式。
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()无法保证顺序性。我们知道创建一个对象分三步:
memory=allocate();//1:初始化内存空间
ctorInstance(memory);//2:初始化对象
instance=memory();//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(instance==null)判断语句里面,保证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工厂一共生产三种类型的pizza:chesse,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 {
@Override
public 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 三种工厂模式的使用选择
简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)
抽象工厂 :用来生产不同产品族的全部产品。(支持拓展增加产品;支持增加产品族)
简单工厂的适用场合:只有伦敦工厂(只有这一个等级),并且这个工厂只生产三种类型的pizza:chesse,pepper,greak(固定产品)。
工厂方法的适用场合:现在不光有伦敦工厂,还增设了纽约工厂(仍然是同一等级结构,但是支持了产品的拓展),这两个工厂依然只生产三种类型的pizza:chesse,pepper,greak(固定产品)。
抽象工厂的适用场合:不光增设了纽约工厂(仍然是同一等级结构,但是支持了产品的拓展),这两个工厂还增加了一种新的类型的pizza:chinese 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 {
@Override
public void buildMaster() {
// TODO Auto-generated method stub
computer.setMaster("i7,16g,512SSD,1060");
System.out.println("(i7,16g,512SSD,1060)的惠普主机");
}
@Override
public void buildScreen() {
// TODO Auto-generated method stub
computer.setScreen("1080p");
System.out.println("(1080p)的惠普显示屏");
}
@Override
public void buildKeyboard() {
// TODO Auto-generated method stub
computer.setKeyboard("cherry 青轴机械键盘");
System.out.println("(cherry 青轴机械键盘)的键盘");
}
@Override
public void buildMouse() {
// TODO Auto-generated method stub
computer.setMouse("MI 鼠标");
System.out.println("(MI 鼠标)的鼠标");
}
@Override
public void buildAudio() {
// TODO Auto-generated method stub
computer.setAudio("飞利浦 音响");
System.out.println("(飞利浦 音响)的音响");
}
}
/**
戴尔电脑的builder
*/
public class DELLComputerBuilder extends ComputerBuilder {
@Override
public void buildMaster() {
// TODO Auto-generated method stub
computer.setMaster("i7,32g,1TSSD,1060");
System.out.println("(i7,32g,1TSSD,1060)的戴尔主机");
}
@Override
public void buildScreen() {
// TODO Auto-generated method stub
computer.setScreen("4k");
System.out.println("(4k)的dell显示屏");
}
@Override
public void buildKeyboard() {
// TODO Auto-generated method stub
computer.setKeyboard("cherry 黑轴机械键盘");
System.out.println("(cherry 黑轴机械键盘)的键盘");
}
@Override
public void buildMouse() {
// TODO Auto-generated method stub
computer.setMouse("MI 鼠标");
System.out.println("(MI 鼠标)的鼠标");
}
@Override
public void buildAudio() {
// TODO Auto-generated method stub
computer.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 stub
Director director = new Director();
ComputerBuilder hp = new HPComputerBuilder();
director.setComputerBuilder(hp);
director.constructComputer();
//get the pc
Computer 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 block
e.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 start=System.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 end=System.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 {
//通过继承家庭电压类和实现笔记本接口,重写笔记本类中的输出电压方法,使输出的电压适配笔记本的电压
@Override
public 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的灵活性增强了。
类适配器模式缺点:
(1)Java是单继承机制,所以类适配器需要继承src类,因为这要求dst必须是接口,有一定局限性
(2)src类的方法在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();
@Override
public 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() {
}
@Override
public void m2() {
}
@Override
public 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 ; //转成5v
return 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.0
6.4 总结
装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。
7 代理模式
7.1 定义
定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。
7.2 代理模式优缺点
代理模式的主要优点有:
(1)代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
(2)代理对象可以扩展目标对象的功能;
(3)代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;其主要缺点是:
(1)在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
(2)增加了系统的复杂度;
7.3 代理模式分类
静态代理 :由程序员创建或第三方工具生成代理类的源代码,再进行编译。 其代理类和委托。类在编译期间就确定下来。
动态代理 : 代理类在程序运行时通过反射等机制动态生成,无需程序员显式地创建代理类。
(1)JDK 动态代理
(2)CGLib 动态代理
虚拟代理 : 当有一个需要创建开销较大的对象时,通过虚拟代理来存放实例化需要较长时间的真实对象。
7.3.1 静态代理
7.3.1.1 代码示例
举例:保存数据
/**
* 代理模式之静态代理实现
* 模拟一个保存用户数据的功能
*
* 定义一个接口,用于规定实现具体业务(保存数据)的方法,即抽象主题
*/
public interface IUserDao {
void save();
}
/*******************************************************
* 具体目标类,即代理模式中的具体主题
*
*******************************************************/
public class UserDaoImpl implements IUserDao{
@Override
public void save() {
System.out.println("保存数据");
}
}
/*******************************************************
* 静态代理类
*******************************************************/
public class ProxyUserDao implements IUserDao{
private IUserDao target;
public ProxyUserDao(IUserDao target){
this.target = target;
}
@Override
public 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文件
2)jdk动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
7.3.2.2 jdk动态代理涉及到的jdk api
jdk动态代理主要涉及到的类Proxy的newProxyInstance 方法和接口InvocationHandler的
invoke 方法,详情如下:
1)java.lang.reflect.Proxy 的 newProxyInstance 方法如下:
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 static Object newProxyInstance( ClassLoader loader, //指定当前目标对象使用类加载器 Class<?>[] interfaces, //目标对象实现的接口的类型 InvocationHandler h //事件处理器 )
2)java.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
*/
@Override
public 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)最底层是字节码
2)ASM是操作字节码的工具
3)cglib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)
4)SpringAOP基于cglib进行封装,实现cglib方式的动态代理
因为cglib是第三方插件,使用cglib 需要引入cglib 的jar包,如果你已经有spring-core的 jar包,则无需引入,因为spring中包含了cglib ,如下所示:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
7.3.3.2 CGLIB动态代理代码示例
/*******************************************************
* 目标类
*******************************************************/
public class UserServiceImpl {
//查询功能
public List<User> 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
*/
@Override
public 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());
//执行代理对象目标方法
List<User> 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、三种代理模式实现方式对比
1)cglib代理和jdk代理对比
I)cglib底层采用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个类:
1)Pay抽象类(支付渠道)
I)支付渠道子类: 微信支付
II)支付渠道子类: 支付宝支付
2)IPayMode接口(支付方式)
I)支付模式实现: 刷脸支付
II)支付模式实现: 指纹支付
III)密码支付
类图如下:
示例代码如下:
实现化角色代码:
/**
* 支付模式接口
* 实现化(Implementor)角色
*/
public interface IPayMode {
//安全校验功能: 对各种支付模式进行风控校验
boolean security(String uId);
}
/*******************************************************
* 密码支付及风控校验
* 具体实现化(Concrete Implementor)角色
*
*******************************************************/
public class PayCypher implements IPayMode{
@Override
public boolean security(String uId) {
return false;
}
}
/*******************************************************
* 刷脸支付及风控校验
* 具体实现化(Concrete Implementor)角色
*
*******************************************************/
public class PayFaceMode implements IPayMode{
@Override
public boolean security(String uId) {
return true;
}
}
/*******************************************************
* 指纹支付及风控校验
* 具体实现化(Concrete Implementor)角色
*
*******************************************************/
public class PayFingerprintMode implements IPayMode{
@Override
public 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);
}
@Override
public 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);
}
@Override
public 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;
}
@Override
public void add(Component c) {
}
@Override
public void remove(Component c) {
}
@Override
public Component getChild(int i) {
// TODO Auto-generated method stub
return null;
}
@Override
public void operation() {
// TODO Auto-generated method stub
System.out.println("树叶"+name+":被访问!");
}
}
树枝:
public class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
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;
}
@Override
public void print() {
System.out.println("Flyweight.id = " + getId() + " ...");
}
public String getId() {
return id;
}
}
/**
创建工厂,这里要特别注意,为了避免享元对象被重复创建,我们使用HashMap中的key值保证其唯一。
*/
public class FlyweightFactory {
private Map<String, 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 {
@Override
public int calc(int num1, int num2) {
// TODO Auto-generated method stub
return num1 + num2;
}
}
public class SubstractStrategy implements Strategy {
@Override
public int calc(int num1, int num2) {
// TODO Auto-generated method stub
return 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 environment=new Environment(new AddStrategy());
int result=environment.calculate(20, 5);
System.out.println(result);
Environment environment1=new Environment(new SubstractStrategy());
int result1=environment1.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 {
@Override
public void preparation() {
System.out.println("洗并切西红柿,打鸡蛋。");
}
@Override
public void doing() {
System.out.println("鸡蛋倒入锅里,然后倒入西红柿一起炒。");
}
@Override
public void carriedDishes() {
System.out.println("将炒好的西红寺鸡蛋装入碟子里,端给客人吃。");
}
}
public class Bouilli extends Dish{
@Override
public void preparation() {
System.out.println("切猪肉和土豆。");
}
@Override
public void doing() {
System.out.println("将切好的猪肉倒入锅中炒一会然后倒入土豆连炒带炖。");
}
@Override
public 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 List<Observer> list;
private String message;
public WechatServer() {
list = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
// TODO Auto-generated method stub
list.add(o);
}
@Override
public void removeObserver(Observer o) {
// TODO Auto-generated method stub
if (!list.isEmpty()) {
list.remove(o);
}
}
@Override
public void notifyObserver() {
// TODO Auto-generated method stub
for (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;
}
@Override
public 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接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
15.3 代码示例
举例:咖啡厅和中餐厅合并,他们两个餐厅的菜单一个是数组保存的,一个是ArrayList保存的。遍历方式不一样,使用迭代器聚合访问,只需要一种方式
1 迭代器接口
public interface Iterator {
public boolean hasNext();
public Object next();
}
2 咖啡店菜单和咖啡店菜单遍历器
public class CakeHouseMenu {
private ArrayList<MenuItem> menuItems;
public CakeHouseMenu() {
menuItems = new ArrayList<MenuItem>();
addItem("KFC Cake Breakfast","boiled eggs&toast&cabbage",true,3.99f);
addItem("MDL Cake Breakfast","fried eggs&toast",false,3.59f);
addItem("Stawberry Cake","fresh stawberry",true,3.29f);
addItem("Regular Cake Breakfast","toast&sausage",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 position=0;
public CakeHouseIterator()
{
position=0;
}
@Override
public boolean hasNext() {
// TODO Auto-generated method stub
if(position<menuItems.size())
{
return true;
}
return false;
}
@Override
public Object next() {
// TODO Auto-generated method stub
MenuItem menuItem =menuItems.get(position);
position++;
return menuItem;
}};
//鍏朵粬鍔熻兘浠g爜
}
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", "bacon&lettuce&tomato&cabbage", true, 3.58f);
addItem("Blt", "bacon&lettuce&tomato", false, 3.00f);
addItem("bean soup", "bean&potato salad", true, 3.28f);
addItem("hotdog", "onions&cheese&bread", 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;
}
@Override
public boolean hasNext() {
// TODO Auto-generated method stub
if (position < numberOfItems) {
return true;
}
return false;
}
@Override
public Object next() {
// TODO Auto-generated method stub
MenuItem menuItem = menuItems[position];
position++;
return menuItem;
}
};
}
4 女服务员
public class Waitress {
private ArrayList<Iterator> iterators = new ArrayList<Iterator>();
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.Name=Name;
}
public abstract void ProcessRequest( PurchaseRequest request);
public void SetSuccessor(Approver successor) {
// TODO Auto-generated method stub
this.successor=successor;
}
}
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
}
@Override
public void ProcessRequest(PurchaseRequest request) {
// TODO Auto-generated method stub
if (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");
}
@Override
public void ProcessRequest(PurchaseRequest request) {
// TODO Auto-generated method stub
if ((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;
}
@Override
public void excute() {
// TODO Auto-generated method stub
light.Off();
}
@Override
public void undo() {
// TODO Auto-generated method stub
light.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 stub
command.excute();
}
public void CommandUndo(Command command) {
// TODO Auto-generated method stub
command.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;
}
@Override
public void move() {
System.out.println("处于地点A,开始向B移动");
System.out.println("--------");
context.setDirection("AB");
context.setState(context.onMove);
}
@Override
public void stop() {
// TODO Auto-generated method stub
System.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 List<MementoIF> mementoList = new ArrayList<MementoIF>();
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 {
@Override
public void Visit(Element element) {
// TODO Auto-generated method stub
Employee 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 {
@Override
public void Visit(Element element) {
// TODO Auto-generated method stub
Employee employee = ((Employee) element);
System.out.println(
employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));
}
}
5 对象结构
public class ObjectStructure {
private HashMap<String, 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 List<Colleague> colleagues = new ArrayList<Colleague>();
@Override
public void register(Colleague colleague) {
// TODO Auto-generated method stub
if (!colleagues.contains(colleague)) {
colleagues.add(colleague);
colleague.setMedium(this);
}
}
@Override
public void relay(String from, String to, String ad) {
// TODO Auto-generated method stub
for (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);
}
@Override
public void Send(String to, String ad) {
// TODO Auto-generated method stub
mediator.relay(name, to, ad);
}
@Override
public void receive(String from, String ad) {
// TODO Auto-generated method stub
System.out.println(name + "接收到来自" + from + "的消息:" + ad);
}
}
原文地址:https://blog.csdn.net/weixin_46764753/article/details/143742042
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!