一、代理模式
1、概述
1、代理模式(Proxy)定义:
为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象,这样做的好处是可以在目标对象实现的基础上,增加额外的功能操作,即扩展目标对象的功能。
2、代理模式分类:
-
静态代理
-
动态代理(JDK代理、接口代理)
-
CgLib代理(可以在内存动态的创建对象,而不需要实现接口,是属于动态代理的范畴)
2、主要角色
1、抽象主体(Subject)类:通过接口或抽象类声明真实主体和代理对象实现的业务方法。
2、真实主体(Real Subject)类:实现了抽象主体中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
3、代理(Proxy)类:提供了与真实主体相同的接口,其内部含有对真实主体的引用,它可以访问、控制或扩展真实主题的功能。
3、静态代理模式
1、静态代理在使用的时候,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
public interface ITeacher {
void teach();
}
public class ChineseTeacher implements ITeacher {
@Override
public void teach() {
System.out.println("正在授课中...");
}
}
public class TeacherProxy implements ITeacher {
private ITeacher iTeacher;
public TeacherProxy(ITeacher iTeacher) {
this.iTeacher = iTeacher;
}
@Override
public void teach() {
System.out.println("课前准备...");
iTeacher.teach();
System.out.println("结束上课...");
}
}
public class Client {
public static void main(String[] args) {
ChineseTeacher chineseTeacher = new ChineseTeacher();
TeacherProxy proxy = new TeacherProxy(chineseTeacher);
proxy.teach();
}
}
1、优点:
在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
2、缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护。
4、动态代理模式
1、代理类不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
,代理对象的生成,是利用JDK的API,动态的在内存中构件代理对象。也叫JDK代理。运用反射机制动态创建。
2、代理类所在的包:java.lang.reflect.Proxy,JDK实现代理只需要使用newProxyInstance方法
3、注意:JDK要求被代理类必须是一个接口类型
public interface ITeacher {
void teach();
}
public class ChineseTeacher implements ITeacher {
@Override
public void teach() {
System.out.println("正在授课中...");
}
}
public class TeacherProxy<T> implements InvocationHandler {
private T target;
public TeacherProxy(T target) {
this.target = target;
}
public static<T> T getProxyInstance(T t) {
Object proxyInstance = Proxy.newProxyInstance(t.getClass().getClassLoader(),
t.getClass().getInterfaces(),
new TeacherProxy(t));
return (T) proxyInstance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理开始...");
Object result = method.invoke(target, args);
System.out.println("返回值:" + result);
return result;
}
}
public class Client {
public static void main(String[] args) {
ITeacher iTeacher = new ChineseTeacher();
ITeacher proxy = TeacherProxy.getProxyInstance(iTeacher);
System.out.println("proxy = " + proxy.getClass());
proxy.teach();
}
}
5、CgLib代理
1、静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可使用目标对象子类来实现代理,这就是CgLib代理
。
2、CgLib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。
3、CgLib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
4、在AOP编程中如何选择代理模式:
-
目标对象需要实现接口,用JDK代理。
-
目标对象不需要实现接口,用Cglib代理。
5、CgLib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
6、实现步骤:
-
需要引入CgLib的jar文件。
-
在内存中动态构建子类,注意代理的类不能为final
,否则报错java.lang.IllegalArgumentException
-
目标对象的方法如果为private/final/static
,那么就不会被拦截,即不会执行目标对象额外的业务方法。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
public class Teacher {
public void teach() {
System.out.println("老师讲课中...");
}
}
public class TeacherProxy implements MethodInterceptor {
private Object target;
public TeacherProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理模式开始");
Object invoke = method.invoke(target, objects);
System.out.println("cglib代理模式结束");
return invoke;
}
}
public class Client {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Teacher proxy = (Teacher) new TeacherProxy(teacher).getProxyInstance();
proxy.teach();
}
}
6、优缺点说明
1、优点:
-
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
-
代理对象可以扩展目标对象的功能。
-
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。
2、缺点:
-
代理模式会造成系统设计中类的数量增加。
-
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢。
-
增加了系统的复杂度。
二、模板方法模式
1、概述
1、模板方法模式(Template Method)定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
2、主要角色
1、抽象类/抽象模板(Abstract Class):抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下:
-
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
-
基本方法:是整个算法中的一个步骤,包含以下几种类型。
-
抽象方法:在抽象类中声明,由具体子类实现。
-
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写。
-
钩子方法:默认不做任何事,在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
2、具体子类/具体实现(Concrete Class):具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
3、模板方法模式实现
public abstract class AbstractClass {
public final void templateMethod() {
specificMethod();
if (condiction()) {
abstractMethod();
}
clear();
}
public void specificMethod() {
System.out.println("第一步:选择好的大米");
}
public void clear() {
System.out.println("第三步:清洗浸泡大米开始煮粥");
}
public abstract void abstractMethod();
public boolean condiction() {
return true;
}
}
public class ConcreteClass1 extends AbstractClass {
@Override
public void abstractMethod() {
System.out.println("第二步:加入绿豆开始煮稀饭");
}
}
public class ConcreteClass2 extends AbstractClass {
@Override
public void abstractMethod() {
System.out.println("第二步:加入红豆开始煮稀饭");
}
}
public class ConcreteClass3 extends AbstractClass {
@Override
public void abstractMethod() {
}
@Override
public boolean condiction() {
return false;
}
}
public class Client {
public static void main(String[] args) {
System.out.println("开始制作绿豆粥");
AbstractClass green = new ConcreteClass1();
green.templateMethod();
System.out.println("开始制作红豆粥");
AbstractClass red = new ConcreteClass2();
red.templateMethod();
}
}
4、优缺点说明
1、优点:
-
它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
-
它在父类中提取了公共的部分代码,便于代码复用。
-
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
2、缺点:
-
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
-
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
-
由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
5、应用场景
1、算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
2、当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
3、当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
三、命令模式
1、概述
1、命令(Command)模式定义:
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。同时命令模式也支持可撤销的操作。
2、主要角色
1、抽象命令类(Command):声明执行命令的接口,拥有执行命令的抽象方法execute()。
2、具体命令类(Concreate Command):是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
3、实现者/接收者(Receiver):执行命令功能的相关操作,是具体命令对象业务的真正实现者。
4、调用者/请求者(Invoker):是请求的发送者,它通过拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
3、命令模式实现
public interface Command {
public void execute();
public void undo();
}
public class Receiver {
public void on() {
System.out.println("电灯打开了...开始学习");
}
public void off() {
System.out.println("电灯关闭了...准备睡觉");
}
}
public class ConcreteCommand1 implements Command {
private Receiver receiver;
public ConcreteCommand1(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.on();
}
@Override
public void undo() {
receiver.off();
}
}
public class ConcreteCommand2 implements Command {
private Receiver receiver;
public ConcreteCommand2(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.off();
}
@Override
public void undo() {
receiver.on();
}
}
public class NoCommand implements Command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
public class Invoker {
Command[] onCommands;
Command[] offCommands;
Command undoComand;
public Invoker() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
public void setCommand(int no,Command onCommand,Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
public void executeOn(int no) {
onCommands[no].execute();
undoComand = onCommands[no];
}
public void executeOff(int no) {
offCommands[no].execute();
undoComand = offCommands[no];
}
public void executeUndo(int no) {
undoComand.undo();
}
}
public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
ConcreteCommand1 open = new ConcreteCommand1(receiver);
ConcreteCommand2 close = new ConcreteCommand2(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(0,open,close);
System.out.println("--------按下打开按钮--------");
invoker.executeOn(0);
System.out.println("--------按下关闭按钮--------");
invoker.executeOff(0);
System.out.println("--------按下撤销按钮--------");
invoker.executeUndo(0);
}
}
4、优缺点说明
1、优点:
-
将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到 了纽带桥梁的作用。
-
容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令。
-
扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
-
可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
2、缺点:
-
可能导致某些系统有过多的具体命令类,增加了系统的复杂度。
-
命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。
5、应用场景
1、请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
2、系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
3、当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
四、访问者模式
1、概述
1、访问者(Visitor)模式:
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
2、工作原理:在被访问的类里面加一个对外提供接待访问者的接口。
2、主要角色
1、抽象访问者(Visitor):定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作visit() ,该操作中的参数类型标识了被访问的具体元素。
2、具体访问者(ConcreteVisitor):实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
3、抽象元素(Element):声明一个包含接受操作accept()的接口,被接受的访问者对象作为accept()方法的参数。
4、具体元素(ConcreteElement):实现抽象元素角色提供的accept()操作,其方法体通常都是visitor.visit(this),另外具体元素中可能还包含本身业务逻辑的相关操作。
5、对象结构(Object Structure):是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由List、Set、Map等聚合类实现。
3、访问者模式实现
public interface Visitor {
void visit(ConcreteElementA concreteElementA);
void visit(ConcreteElementB concreteElementB);
}
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("具体访问者A访问===>" + concreteElementA.operationA());
}
@Override
public void visit(ConcreteElementB concreteElementB) {
System.out.println("具体访问者A访问===>" + concreteElementB.operationB());
}
}
public class ConcreteElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具体元素B的操作";
}
}
public interface Element {
void accept(Visitor visitor);
}
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具体元素A的操作";
}
}
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("具体访问者B访问===>" + concreteElementA.operationA());
}
@Override
public void visit(ConcreteElementB concreteElementB) {
System.out.println("具体访问者B访问===>" + concreteElementB.operationB());
}
}
public class ObjectStructure {
private List<Element> list = new LinkedList<>();
public void accept(Visitor visitor) {
Iterator<Element> i = list.iterator();
while (i.hasNext()) {
((Element) i.next()).accept(visitor);
}
}
public void add(Element element) {
list.add(element);
}
public void remove(Element element) {
list.remove(element);
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(new ConcreteElementA());
objectStructure.add(new ConcreteElementB());
Visitor visitorA = new ConcreteVisitorA();
objectStructure.accept(visitorA);
}
}
4、优缺点说明
1、优点:
-
扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
-
复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
-
灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
-
符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
2、缺点:
-
增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
-
破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
-
违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
5、应用场景
1、对象结构相对稳定,但其操作算法经常变化的程序。
2、对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
3、对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。