结构型主要解决特定场景类和对象组合使用的经典结构。结构型的设计模式有代理模式,桥接模式,装饰器模式,适配器模式,门面模式,组合模式和享元模式。
代理模式-Proxy
代理模式是指在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
通用代码-基础类
public interface IBusinessService {
void invoke();
void invoke2();
}
public class BusinessService implements IBusinessService{
public void invoke(){
System.out.println("invoke");
}
public void invoke2() {
System.out.println("invoke2");
}
}
现在需要对invoke函数增加记录操作的功能。
public class OperationService {
public void record(){
System.out.println("operation");
}
}
静态代理
public class BusinessServiceProxy implements IBusinessService{
private OperationService operationService = new OperationService();
private IBusinessService businessService = new BusinessService();
@Override
public void invoke() {
businessService.invoke();
operationService.record();
}
@Override
public void invoke2() {
businessService.invoke2();
}
}
静态代理需要对每个类都手动生成一个代理类,并且代理类需要实现接口的每个方法。
动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public Object createProxy(Object proxiedObject) {
Class[] interfaces = proxiedObject.getClass().getInterfaces();
// 把具体的类传递进去
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(proxiedObject);
return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, dynamicProxyHandler);
}
public class DynamicProxyHandler implements InvocationHandler {
private Object proxiedObject;
public DynamicProxyHandler(Object proxiedObject) {
this.proxiedObject = proxiedObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(proxiedObject, args);
// 可以进行特殊处理
if (method.getName().equals("invoke")) {
OperationService operationService = new OperationService();
operationService.record();
} else {
System.out.println(method.getName());
}
return result;
}
}
}
上面的动态代理是使用JDK动态代理,JDK动态代理有个要求就是原始类必须要有实现的接口。Proxy.newProxyInstance方法中的入参必须是Interface。如果解决这个硬性要求可以使用cglib动态代理。SpringAOP是优先使用JDK动态代理,不符合条件时使用cglib动态代理。
通用代码-调用类
public class Main {
public static void main(String[] args) {
// 静态代理类
IBusinessService businessServiceProxy = new BusinessServiceProxy();
businessServiceProxy.invoke();
// 动态代理类
IBusinessService businessService = (IBusinessService) new DynamicProxy().createProxy(new BusinessService());
businessService.invoke();
}
}
打印结果如下:
invoke
operation
invoke
operation
桥接模式-Bridge
桥接模式是指将抽象和实现解耦,让它们可以独立变化。注意这里的抽象和实现并非抽象类和实现类。
以下拿数据库驱动来举例说明,Driver为数据库驱动的抽象,DriverManager为驱动的实现;Connection为数据库驱动的实现。例如mysql可以有一套具体的Connection实现。
抽象-Driver
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
......
@CallerSensitive
public static Connection getConnection(String url) throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}
......
}
实现-Connection
为具体的数据库驱动的操作函数。
装饰器模式-Decorator
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。
通用代码-基础类
public interface IBusinessService {
void invoke();
void invoke2();
}
public class BusinessService implements IBusinessService{
public void invoke(){
System.out.println("invoke");
}
public void invoke2() {
System.out.println("invoke2");
}
}
现在需要对invoke函数增加记录操作的功能。
装饰器思路
继承相同接口,将原始类当作参数传入,增强功能。
public class OperationService implements IBusinessService {
private IBusinessService businessService;
// 这里通过构造函数参数
public OperationService(IBusinessService businessService) {
this.businessService = businessService;
}
@Override
public void invoke() {
businessService.invoke();
// 增强功能
System.out.println("operation");
}
@Override
public void invoke2() {
businessService.invoke2();
}
}
通用代码-调用类
public class Main {
public static void main(String[] args) {
IBusinessService businessService = new BusinessService();
// 注意,这里传入的实参
IBusinessService operationService = new OperationService(businessService);
operationService.invoke();
}
}
适配器模式-Adapter
适配器模式是为了原本由于接口不兼容而不能一起工作的类可以一起工作。
通用代码-基础类
public interface OldInterface {
void invoke(OldParameter parameter);
}
public class OldInterfaceImpl implements OldInterface{
@Override
public void invoke(OldParameter parameter) {
System.out.println(parameter.toString());
}
}
public class OldParameter {
private String a;
private String b;
// 省略get和set
}
public interface NewInterface {
void invoke(NewParameter parameter);
}
public class NewParameter {
private String newA;
private String newB;
// 省略get和set
}
类适配器
当新接口和老接口提供的函数大部分相同时,可以使用类适配器来提高代码的复用率。否则建议使用对象适配器,避免接口暴露太多不可用的接口,降低接口的易用性。
public class ClassAdapter extends OldInterfaceImpl implements NewInterface{
@Override
public void invoke(NewParameter parameter) {
OldParameter oldParameter = new OldParameter();
oldParameter.setA(parameter.getNewA());
oldParameter.setB(parameter.getNewB());
super.invoke(oldParameter);
}
}
对象适配器
只暴露合适的接口,再通过组合的方式来提高代码的复用率。
public class ObjectAdapter implements NewInterface{
private OldInterface oldInterface = new OldInterfaceImpl();
@Override
public void invoke(NewParameter parameter) {
OldParameter oldParameter = new OldParameter();
oldParameter.setA(parameter.getNewA());
oldParameter.setB(parameter.getNewB());
oldInterface.invoke(oldParameter);
}
}
通用代码-调用类
public class Main {
public static void main(String[] args) {
NewParameter parameter = new NewParameter();
parameter.setNewA("a");
parameter.setNewB("b");
new ClassAdapter().invoke(parameter);
new ObjectAdapter().invoke(parameter);
}
}
门面模式-Facade
门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。
public interface OldInterface {
void invoke(OldParameter parameter);
}
public class OldParameter {
private String a;
private String b;
// 省略get和set
}
上面代码中接口的入参为一个对象,该对象有a,b两个属性,可以将代码优化成:
public interface OldInterface {
// 作为案例展示,这里的参数的命名应该要有意义
void invoke(String a, String b);
}
这样可以降低调用方的使用门槛,提高接口的易用性。
组合模式-Composite
组合模式和组合关系是完全两码事。组合模式像是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。
享元模式-Flyweight
享元是指共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。
IntegerCache就是享元模式的使用。
总结
代理模式
代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非增加功能。
代理分为静态代理和动态代理,其中动态代理有JDK动态代理和cglib,JDK动态代理要求被代理类必须实现某一个接口,而cglib则没有这个要求。
桥接模式
桥接模式是将抽象和实现分离。
装饰器模式
代理模式在不改变原始类接口的条件下,对原始类的功能进行增强。
可以参考InputStream和OutputStream
适配器模式
适配器模式是用来做适配的,它将不兼容的接口转换为可兼容的接口。
适配器可以分为类适配器和对象适配器,类适配器适用于新旧接口重合度很高,否则建议使用对象适配器。
门面模式
门面模式通过封装细粒度的接口,提供组合各个细粒度接口的高层次接口,来提高接口的易用性,或者解决性能、分布式事务等问题。
组合模式
组合模式主要是用来处理树形结构数据。正因为其应用场景的特殊性,数据必须能表示成树形结构
享元模式
享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。