结构型模式
适配器模式
定义: 将一个接口转换成客户期待的另一个接口.
使原本接口不兼容的类可以一起工作.
类型: 结构型
使用场景:
- 已经存在的类, 它的方法和需求不匹配时(方法结果相同或相似).
- 不是软件设计阶段考虑的设计模式, 是随着软件维护, 由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案.
优点:
- 能提高类的透明性和复用, 现有的类复用但不需要改变.
- 目标类和适配器类解耦, 提高程序扩展性.
- 符合开闭原则.
缺点:
- 适配器编写过程需要全面考虑, 可能会增加系统的复杂性.
- 增加系统代码可读难度.
扩展:
- 对象适配器(符合组合复用原则, 使用委托机制实现.继承和组合的时候, 优先使用组合.)
- 类适配器(通过类继承实现)
相关设计模式
- 适配器模式和外观模式
Adapter Demo UML类图
类适配器
对象适配器
JDK中使用适配器模式的例子
javax.xml.bind.annotation.adapters.XmlAdapter
Spring中使用适配器模式的例子
org.springframework.aop.framework.adapter.AdvisorAdapter
org.springframework.orm.jpa.jpaVendorAdapter
org.springframework.web.servlet.HandlerAdapter
DispatcherServlet
-> doDispatch
-> 映射对应的controller
桥接模式
定义: 将抽象部分与它的具体实现部分分离, 使它们都可以独立地变化.
通过组合的方式建立两个类之间的联系, 而不是继承.
类型: 结构型
适用场景:
- 抽象和具体实现之间增加更多的灵活性.
- 一个类存在两个(或多个)独立变化的维度, 并且这两个(或多个)维度都需要独立进行扩展.
- 不希望适用继承, 或因为多层继承导致系统类的个数剧增.
优点:
- 分离抽象部分以及具体实现部分(因为使用组合, 就是说使用对象之间的关联解耦了抽象和实现之间固有的绑定关系, 使抽象和实现可以延着各自的维度变化扩展).
- 提高系统的可扩展性.
- 符合开闭原则.
- 符合合成复用原则.
缺点:
- 增加了系统的理解与设计难度.
- 需要正确地识别出系统中两个独立变化的维度.
相关设计模式:
- 桥接模式和组合模式(组合强调部分和整体之间的关系, 桥接是平衡级别上不同类的组合)
- 桥接模式和适配器模式(相同点: 都是让2样东西配合工作;目的不一样, 适配器是改已有的接口, 让他们之间可以相互配合兼容, 而桥接是分离抽象和具体的实现, 然后在此之上使这些层次结构结合起来)
Bridge Demo UML类图
JDK中使用桥接模式的例子
JDBC
就是一个很好的例子.JDBC
为不同据库提供了统一的接口, 具体的实现让数据库的厂商来实现.
public interface Driver {}
com.mysql.cj.jdbc.Driver
java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
// 注册驱动
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
java.sql.DriverManager
public class DriverManager {
// ...
public static void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {
/* Register the driver if it has not already been added to our list */
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
// ...
// getConnection
}
装饰者模式
定义: 在不改变原有对象的基础上, 将功能附加到对象上.
提供了比继承更有弹性的替代方案(扩展对象原有功能).
类型: 结构型
适用场景:
- 扩展一个类的功能或者给一个类添加附加职责.
- 动态地给一个对象添加功能, 这些功能可以动态撤销.
优点:
- 继承的有力补充, 比继承灵活, 不改变原有对象的情况下给一个对象扩展功能.
- 通过使用不同装饰类以及这些装饰类的排列组合, 可以实现不同效果.
- 符合开闭原则.
缺点:
- 会出现更多的代码, 更多的类, 增加程序复杂性.
- 动态装饰、多层装饰时会更复杂.
相关模式:
- 装饰者模式和代理模式(装饰者模式关注一个对象动态地添加方法, 代理模式关注控制对对象的访问)
- 装饰者模式和适配器模式(都可以叫做包装模式)
Decorator Demo UML类图
JDK中装饰者模式例子
BufferedReader
public class BufferedReader extends Reader {
// 被修饰者
private Reader in;
// ...
/**
* Creates a buffering character-input stream that uses an input buffer of
* the specified size.
*
* @param in A Reader
* @param sz Input-buffer size
*
* @exception IllegalArgumentException If {@code sz <= 0}
*/
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
// ...
}
Spring中装饰者模式的例子
在 spring-context-support
依赖包下.
org.springframework.cache.transaction.TransactionAwareCacheDecorator
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
// ...
// 传入被装饰对象 Cache
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
}
org.springframework.session.web.http.SessionRepositoryFilter
.该类在spring-session-core
包下.
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
private final HttpServletResponse response;
// ...
// 继承的是HttpServletRequestWrapper, 传入的是 HttpServletRequest.
// 实际 HttpServletRequestWrapper 继承 HttpServletRequest .
private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response) {
super(request);
this.response = response;
}
}
}
Mybatis中使用装饰者的例子
LruCache
public class LruCache implements Cache {
private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
// ...
}
组合模式
定义: 将对象组合成树型结构以表示”部分-整体”的层次结构.
组合模式使客户端对单个对象和组合对象保持一致的方式处理.
注意要有统一的接口实现或统一的抽象父类.
类型: 结构型
适用场景:
- 希望客户端可以忽略组合对象与单个对象的差异时.
- 处理一个树型结构时.
优点:
- 清楚地定义分层次的复杂对象, 表示对象的全部或部分层次.
- 让客户端忽略了层次的差异, 方便对整个层次结构进行控制.
- 简化客户端代码.
- 符合开闭原则.
缺点:
- 限制类型时会较为复杂.
- 使设计变得更加抽象.
相关模式:
- 组合模式和访问者模式
Composite Demo UML类图
JDK中使用组合模式的例子
java.awt.Container
public class Container extends Component {
// ... 添加自己的父类
public Component add(Component comp) {
addImpl(comp, null, -1);
return comp;
}
// ...
}
Hashmap
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// ...
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
// ...
}
ArrayList
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// ... List 接口继承了Collection接口
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}
// ...
}
AbstractList
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
// ...
public E set(int index, E element) {
throw new UnsupportedOperationException();
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
// ...
}
Mybatis中使用组合模式的例子
SqlNode
.核心是MixedSqlNode
.
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
SqlNode UML类图(部分)
代理模式
定义: 为其他对象提供一种代理, 以控制对这个对象的访问.
代理对象在客户端和目标对象之间起到中介的作用.
类型: 结构型
适用场景:
- 保护目标对象.
- 增强目标对象.
优点:
- 代理模式能将代理对象与真实被调用的目标对象分离.
- 一定程度上降低了系统的耦合度, 扩展性好.
- 保护目标对象.
- 增强目标对象.
缺点:
- 代理模式会造成系统设计中类的数目增加.
- 在客户端和目标对象增加一个代理对象, 会造成请求处理速度变慢.
- 增加系统的复杂度
扩展:
- 静态代理
- 动态代理(无法代理类, 但是可以代理接口, 生成新类)
- CGLib代理(通过继承来实现, 生成的代理类就是业务类的子类)
Spring代理选择:
- 当Bean有实现接口时, Spring就会用JDK的动态代理
- 可以强制使用 Cglib
- 在 spring 配置中加入
<aop: aspect- autoproxy proxy-target-class="true"/>
- 在 spring 配置中加入
- 参考链接: https: //docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html
代理速度对比:
- CGLib, 基于 ASM 生成字节码的.比 java 反射效率要高.
- JDK动态代理.
相关设计模式
- 代理模式和装饰者模式(一个是加行为, 一个是控制访问)
- 代理模式和适配器模式
Proxy Demo UML类图
JDK中使用代理模式的例子
java.lang.reflect.proxy
Spring中使用代理模式的例子
ProxyFactoryBean
JdkDynamicAopProxy
CglibAopProxy
Mybatis中使用代理模式的例子
MapperProxyFactory
, Mapper的代理工厂.
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 使用JDK的动态代理模式
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
// ...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// 享元模式 命中就返回, 没有就创建
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
}
享元模式
定义: 提供了减少对象数量从而改善应用所需对象结构的方式
运用共享技术有效地支持大量细粒度的对象.
类型: 结构型
适用场景:
- 常常应用于系统底层的开发, 以便解决系统的性能问题(例如: String, 存在就返回, 不存在就创建放在缓存池当中; 数据库连接池).
- 系统有大量的相似对象、需要缓冲池的场景.
优点:
- 减少对象的创建, 降低内存中对象的数量, 降低系统内存, 提高效率
- 减少内存之外的其他资源占用(例如: 时间,文件句柄,窗口句柄)
缺点:
- 关注内/外部状态, 关注线程安全问题(Hashmap,Hashtable).
- 使系统、程序的逻辑复杂化.
扩展:
- 内部状态
- 外部状态(不可以共享)
相关设计模式
- 享元模式和代理模式(代理类创建消耗大)
- 享元模式和单例模式(容器单例)
Flyweight Demo UML类图
JDK中使用享元模式的例子
java.lang.Integer
public final class Integer extends Number implements Comparable<Integer> {
// 常出现面试题
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
// -128 ~ 127
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
// new出来的肯定不是同一个对象
return new Integer(i);
}
// ...
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
VM.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int k = 0; k < c.length; k++)
c[k] = new Integer(j++);
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
同理, java.lang.Long
也使用了享元模式.
Tomcat中使用享元模式的例子
public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T>
implements KeyedObjectPool<K, T>, GenericKeyedObjectPoolMXBean<K> {
// ...
public T borrowObject(final K key, final long borrowMaxWaitMillis) throws Exception {
// ...
PooledObject<T> p = null;
// 双端队列.用来保存对象池的对象
final ObjectDeque<T> objectDeque = register(key);
try {
while (p == null) {
create = false;
p = objectDeque.getIdleObjects().pollFirst();
if (p == null) {
p = create(key);
if (p != null) {
create = true;
}
}
if (blockWhenExhausted) {
if (p == null) {
if (borrowMaxWaitMillis < 0) {
p = objectDeque.getIdleObjects().takeFirst();
} else {
p = objectDeque.getIdleObjects().pollFirst(
borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
} else {
if (p == null) {
throw new NoSuchElementException("Pool exhausted");
}
}
// ...
}
} finally {
deregister(key);
}
updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
return p.getObject();
}
}
@Override
public void returnObject(final K key, final T obj) {
// private final Map<K, ObjectDeque<T>> poolMap = new ConcurrentHashMap<>();
final ObjectDeque<T> objectDeque = poolMap.get(key);
if (objectDeque == null) {
throw new IllegalStateException(
"No keyed pool found under the given key.");
}
final PooledObject<T> p = objectDeque.getAllObjects().get(new IdentityWrapper<>(obj));
if (p == null) {
throw new IllegalStateException(
"Returned object not currently part of this pool");
}
// ...
}
外观模式
定义: 又叫门面模式, 提供了统一的接口, 用来访问子系统中的一群接口.
外观模式定义了一个高层接口, 让子系统更容易使用.
类型: 结构型
适用场景:
- 子系统越来越复杂, 增加外观模式提供简单接口调用.
- 构建多层系统结构, 利用外观对象作为每层的入口, 简化层间调用.
优点:
- 简化了调用过程, 无需了解深入子系统, 防止带来风险.
- 减少系统依赖, 松散耦合.
- 更好地划分访问层次.
- 符合迪米特原则, 即最少知道原则.
缺点:
- 增加子系统、扩展子系统行为容易引入风险.
- 不符合开闭原则.
与之相关的模式
- 外观模式和中介模式
- 外观模式和单例模式(结合使用)
- 外观模式和抽象工厂模式
Facade Demo UML类图
Spring中外观模式例子
JdbcUtils
public abstract class JdbcUtils {
public static <T> T extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback<T> action)
throws MetaDataAccessException {
Connection con = null;
try {
con = DataSourceUtils.getConnection(dataSource);
DatabaseMetaData metaData;
metaData = con.getMetaData();
}
catch (SQLException ex) {
if (DataSourceUtils.isConnectionTransactional(con, dataSource)) {
// Probably a closed thread-bound Connection - retry against fresh Connection
DataSourceUtils.releaseConnection(con, dataSource);
con = null;
logger.debug("Failed to obtain DatabaseMetaData from transactional Connection - " +
"retrying against fresh Connection", ex);
con = dataSource.getConnection();
metaData = con.getMetaData();
}else {
throw ex;
}
}
if (metaData == null) {
// should only happen in test environments
throw new MetaDataAccessException("DatabaseMetaData returned by Connection [" + con + "] was null");
}
return action.processMetaData(metaData);
}
catch (CannotGetJdbcConnectionException ex) {
throw new MetaDataAccessException("Could not get Connection for extracting meta-data", ex);
}
catch (SQLException ex) {
throw new MetaDataAccessException("Error while extracting DatabaseMetaData", ex);
}
catch (AbstractMethodError err) {
throw new MetaDataAccessException(
"JDBC DatabaseMetaData method not implemented by JDBC driver - upgrade your driver", err);
}
finally {
DataSourceUtils.releaseConnection(con, dataSource);
}
}
}
Mybatis中外观模式例子
org.apache.ibatis.session.Configuration
public class Configuration {
// ...
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
// ...
}
Tomcat中外观模式例子
RequestFacade
和Request
是同级的, 而Request
类中实际的操作是RequestFacade
.
org.apache.catalina.connector.RequestFacade
public class RequestFacade implements HttpServletRequest {
// ...
private final class GetAttributePrivilegedAction
implements PrivilegedAction<Enumeration<String>> {
@Override
public Enumeration<String> run() {
return request.getAttributeNames();
}
}
private final class GetParameterMapPrivilegedAction
implements PrivilegedAction<Map<String,String[]>> {
@Override
public Map<String,String[]> run() {
return request.getParameterMap();
}
}
// ...
}
org.apache.catalina.connector.Request
public class Request implements HttpServletRequest {
/**
* The facade associated with this request.
*/
protected RequestFacade facade = null;
}
org.apache.catalina.session.StandardSessionFacade
public class StandardSessionFacade implements HttpSession {
@Override
public long getCreationTime() {
return session.getCreationTime();
}
@Override
public String getId() {
return session.getId();
}
@Override
public long getLastAccessedTime() {
return session.getLastAccessedTime();
}
}