万字整理设计模式在框架中的应用

一、适配器模式

1. 描述

将一个类的接口适配成另一个类的接口所期望的形式,适配器模式实际上是一种补救模式,为了适应两种不同的标准而产生的一种兼容方案。

在现实生活中,电源适配器就是一个非常容易帮助我们理解适配器模式的案例,比如有些国家标准电压是110V,我们国家则是220V,两个标准都不能修改,又不得不使用,于是就有了电源适配器,可以接入110V,输出220V,或者接入220V,输出110V。

类似的场景还有很多,比如翻译、转接头等等。

2. 标准结构

在这里插入图片描述

/**
 * 目标接口,即客户端本身所期望请求的接口
 */
public interface Target {

    void request();

}
/**
 * 适配者,需要适配的接口
 */
public class Adaptee {

    public void specificRequest(){
        System.out.println("特殊的请求");
    }

}
/**
 * 适配器,转换的接口
 */
public class Adapter implements Target {

    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {
        System.out.println("正常请求");
        adaptee.specificRequest();
    }
}
public class Test {
    public static void main(String[] args) {
        // 对客户端来说,只知道一个request接口
        Target target = new Adapter();
        target.request();
    }
}

3. 使用场景

  1. 系统需要使用现有的类,而此类的接口不符合系统的需要

  2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。

  3. 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

优点

  1. 可以让任何两个没有关联的类一起运行。

  2. 提高了类的复用

  3. 增加了类的透明度

  4. 灵活性好。

缺点

  1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
  2. 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

4. 使用案例

4.1 spring-webmvc

适配器,spring-webmvc定义的标准处理请求的方法为handle,这个方法中有一个handler参数,实际上就是适配者

public interface HandlerAdapter {

	boolean supports(Object handler);

	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

	long getLastModified(HttpServletRequest request, Object handler);

}

调用handle方法,实际上调用的是Servlet协议中定义的service方法,也就是我们适配的接口

public class SimpleServletHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Servlet);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((Servlet) handler).service(request, response);
		return null;
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		return -1;
	}

}

HandlerAdapter接口的其他实现类,在handle方法中逻辑更为复杂,但无论怎样最终都会调用到目标接口:service

4.2 MyBatis

MyBatis中有一个Log接口,其定义了自己的日志级别,但同时为了能够适应其他日志框架的日志级别,于是又针对每一种日志框架做了不同的实现。

MyBatis中自己的日志级别标准

public interface Log {

  boolean isDebugEnabled();

  boolean isTraceEnabled();

  void error(String s, Throwable e);

  void error(String s);

  void debug(String s);

  void trace(String s);

  void warn(String s);

}

适应别的日志框架

public class Jdk14LoggingImpl implements Log {

  private final Logger log;

  public Jdk14LoggingImpl(String clazz) {
    log = Logger.getLogger(clazz);
  }

  @Override
  public boolean isDebugEnabled() {
    return log.isLoggable(Level.FINE);
  }

  @Override
  public boolean isTraceEnabled() {
    return log.isLoggable(Level.FINER);
  }

  @Override
  public void error(String s, Throwable e) {
    log.log(Level.SEVERE, s, e);
  }

  @Override
  public void error(String s) {
    log.log(Level.SEVERE, s);
  }

  @Override
  public void debug(String s) {
    log.log(Level.FINE, s);
  }

  @Override
  public void trace(String s) {
    log.log(Level.FINER, s);
  }

  @Override
  public void warn(String s) {
    log.log(Level.WARNING, s);
  }

}

4.3 JDK

Jdk中的WindowAdapter、MouseAdapter、KeyAdapter等等awt.event包下有很多类似这样的适配类,这是一种非典型的使用方法,他们分别实现了各自的XXXListener接口,但却没有对其中的逻辑进行重写,都是空现实,把具体实现逻辑交给了具体的调用者,这样做的目的主要是因为这些XXXListener接口中的方法较多,但调用者又不会每一个接口方法都会用到,所以就通过适配类来全部实现,而调用者只需要继承适配类,重写自己需要的方法即可。

public abstract class WindowAdapter
    implements WindowListener, WindowStateListener, WindowFocusListener
{
    /**
     * Invoked when a window has been opened.
     */
    public void windowOpened(WindowEvent e) {}

    /**
     * Invoked when a window is in the process of being closed.
     * The close operation can be overridden at this point.
     */
    public void windowClosing(WindowEvent e) {}

    /**
     * Invoked when a window has been closed.
     */
    public void windowClosed(WindowEvent e) {}

    /**
     * Invoked when a window is iconified.
     */
    public void windowIconified(WindowEvent e) {}

    /**
     * Invoked when a window is de-iconified.
     */
    public void windowDeiconified(WindowEvent e) {}

    /**
     * Invoked when a window is activated.
     */
    public void windowActivated(WindowEvent e) {}

    /**
     * Invoked when a window is de-activated.
     */
    public void windowDeactivated(WindowEvent e) {}

    /**
     * Invoked when a window state is changed.
     * @since 1.4
     */
    public void windowStateChanged(WindowEvent e) {}

    /**
     * Invoked when the Window is set to be the focused Window, which means
     * that the Window, or one of its subcomponents, will receive keyboard
     * events.
     *
     * @since 1.4
     */
    public void windowGainedFocus(WindowEvent e) {}

    /**
     * Invoked when the Window is no longer the focused Window, which means
     * that keyboard events will no longer be delivered to the Window or any of
     * its subcomponents.
     *
     * @since 1.4
     */
    public void windowLostFocus(WindowEvent e) {}
}

public static void main(String[] args) {
    addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e) {
            super.windowClosing(e);
        }
    });
}

4.4 Spring

同样的方式,在Spring事务中也有运用到,也是为了实现者不必每次都要实现TransactionSynchronization接口的所有方法,不过从spring 5.3版本开始,由于JDK支持接口可以有默认的实现方法,因此接口实现者也就不需要实现全部接口了,自然TransactionSynchronizationAdapter也就没有存在的意义了。

/**
 * Simple {@link TransactionSynchronization} adapter containing empty
 * method implementations, for easier overriding of single methods.
 *
 * <p>Also implements the {@link Ordered} interface to enable the execution
 * order of synchronizations to be controlled declaratively. The default
 * {@link #getOrder() order} is {@link Ordered#LOWEST_PRECEDENCE}, indicating
 * late execution; return a lower value for earlier execution.
 *
 * @author Juergen Hoeller
 * @since 22.01.2004
 * @deprecated as of 5.3, in favor of the default methods on the
 * {@link TransactionSynchronization} interface
 */
@Deprecated
public abstract class TransactionSynchronizationAdapter implements TransactionSynchronization, Ordered {

	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	@Override
	public void suspend() {
	}

	@Override
	public void resume() {
	}

	@Override
	public void flush() {
	}

	@Override
	public void beforeCommit(boolean readOnly) {
	}

	@Override
	public void beforeCompletion() {
	}

	@Override
	public void afterCommit() {
	}

	@Override
	public void afterCompletion(int status) {
	}

}

二、责任链模式

1. 描述

责任链模式也是非常实用的一种设计模式,尤其是在许多框架开发中都有应用,因为它可以用来提供框架的扩展点,一种非常经典的应用场景类型就是过滤器、拦截器。

责任链模式是一种行为型的设计模式,主要是为了将请求的发送和接收解耦,这样就可以方便扩展多个对象来处理请求,每个对象之间的处理顺序可以自由调配,每个对象处理后的数据也可以互相传递。

2. 标准结构

在这里插入图片描述

public abstract class Handler {

    private Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public Handler getSuccessor() {
        return successor;
    }

    public abstract void handleRequest(String msg);

}
import org.apache.commons.lang3.StringUtils;

public class ConcreteHandlerA extends Handler {

    @Override
    public void handleRequest(String msg) {
        if (StringUtils.isNotEmpty(msg)) {
            System.out.println("ConcreteHandlerA pass");
            if (getSuccessor() != null) {
                getSuccessor().handleRequest(msg);
            }
        } else {
            System.out.println("msg 不能为空!");
        }
    }
}
public class ConcreteHandlerB extends Handler {

    @Override
    public void handleRequest(String msg) {
        if (msg.length() <= 16) {
            System.out.println("ConcreteHandlerB pass");
            if (getSuccessor() != null) {
                getSuccessor().handleRequest(msg);
            }
        } else {
            System.out.println("msg长度不能超过16!");
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandlerA();
        Handler handler2 = new ConcreteHandlerB();
        handler1.setSuccessor(handler2);
        handler1.handleRequest("text");
    }
}

3. 使用场景

前面有介绍了,责任链模式主要就是为了避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

因此责任链模式在针对这种需要多个对象配合处理的情况下非常好用,比如:多级别的审批流程、多级别的数据安全防护处理,还有框架中常见的过滤器、拦截器等。

优点
  1. 降低耦合度,它将请求的发送者和接收者解耦。

  2. 简化了对象,使得对象不需要知道链的结构。

  3. 增强给对象指派职责的灵活性,通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。

  4. 增加新的请求处理类很方便。

缺点
  1. 不能保证请求一定被接收。
  2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
  3. 责任链容易配置错误,增加了代码的出错风险。

4. 使用案例

4.1 Servlet Filter

Servlet规范中有一个非常经典的设计就是Filter,它主要用来实现对HTTP请求的过滤,比如:鉴权、限流、参数校验、记录日志等等。

在这里插入图片描述

Filter接口

Filter就相当于Handler

public interface Filter {

	public void init(FilterConfig filterConfig) throws ServletException;
	
    public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException;

	public void destroy();
}

FilterChain

FilterChain相当于前面main方法中的逻辑,通常会做一层封装,封装后就是HandlerChain.

public interface FilterChain {

    public void doFilter ( ServletRequest request, ServletResponse response ) throws IOException, ServletException;

}

我们知道Servlet只定义标准,具体实现由各个web容器来完成,比如ApplicationFilterChain,就是tomcat中提供的实现类。

    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        // 省略部分代码...
        
        internalDoFilter(request,response);
        
    }
    
    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
           
            Filter filter = filterConfig.getFilter();
				
			// 省略部分代码...
			// 通过递归 + pos下标的方式,遍历处理所有的filter	
            filter.doFilter(request, response, this);
                
            
            return;
        }
        
        // 省略部分代码...
        servlet.service(request, response);
        
    }

给filterChain添加filter

for (FilterMap filterMap : filterMaps) {
    if (!matchDispatcher(filterMap, dispatcher)) {
        continue;
    }
    if (!matchFiltersURL(filterMap, requestPath))
        continue;
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMap.getFilterName());
    if (filterConfig == null) {
        // FIXME - log configuration problem
        continue;
    }
    filterChain.addFilter(filterConfig);
}

4.2 Spring Interceptor

Spring Interceptor和Servlet Filter差不多,是spring框架中的功能,当客户端的请求发送过来后,会先经过Servlet Filter再经过Spring Interceptor,最后才到达业务代码中。

HandlerInterceptor

HandlerInterceptor接口相当于Handler

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}

HandlerExecutionChain

HandlerExecutionChain接口相当于HandlerChain

public class HandlerExecutionChain {

	private final Object handler;
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
	private int interceptorIndex = -1;

	public void addInterceptor(HandlerInterceptor interceptor) {
		this.interceptorList.add(interceptor);
	}
	
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		for (int i = 0; i < this.interceptorList.size(); i++) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
		return true;
	}

	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {
		for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}

	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
		for (int i = this.interceptorIndex; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			try {
				interceptor.afterCompletion(request, response, this.handler, ex);
			}
			catch (Throwable ex2) {
				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
			}
		}
	}
}



4.3 hutool Chain

public static void main(String[] args) {
    CharSequence str = "<html><p>hello world</p></html>";
    String escape = EscapeUtil.escapeHtml4(str);
    System.out.println(escape);
    String unescape = EscapeUtil.unescapeHtml4(escape);
    System.out.println(unescape);
}

输出结果

&lt;html&gt;&lt;p&gt;hello world&lt;/p&gt;&lt;/html&gt;
<html><p>hello world</p></html>

escapeHtml4方法可以转义html中的特殊字符,其中就使用到了责任链模式来完成特殊字符的替换。

写明了就是要用责任链模式,设计了一个通用的接口。

Chain

/**
 * 责任链接口
 * @author Looly
 *
 * @param <E> 元素类型
 * @param <T> 目标类类型,用于返回this对象
 */
public interface Chain<E, T> extends Iterable<E>{
	/**
	 * 加入责任链
	 * @param element 责任链新的环节元素
	 * @return this
	 */
	T addChain(E element);
}

ReplacerChain

/**
 * 字符串替换链,用于组合多个字符串替换逻辑
 * 
 * @author looly
 * @since 4.1.5
 */
public class ReplacerChain extends StrReplacer implements Chain<StrReplacer, ReplacerChain> {
	private static final long serialVersionUID = 1L;

	private final List<StrReplacer> replacers = new LinkedList<>();

	/**
	 * 构造
	 * 
	 * @param strReplacers 字符串替换器
	 */
	public ReplacerChain(StrReplacer... strReplacers) {
		for (StrReplacer strReplacer : strReplacers) {
			addChain(strReplacer);
		}
	}

	@SuppressWarnings("NullableProblems")
	@Override
	public Iterator<StrReplacer> iterator() {
		return replacers.iterator();
	}
	
	@Override
	public ReplacerChain addChain(StrReplacer element) {
		replacers.add(element);
		return this;
	}

	@Override
	protected int replace(CharSequence str, int pos, StrBuilder out) {
		int consumed = 0;
		for (StrReplacer strReplacer : replacers) {
			consumed = strReplacer.replace(str, pos, out);
			if (0 != consumed) {
				return consumed;
			}
		}
		return consumed;
	}

}

添加chain的逻辑放在构造方法中

public static String escapeHtml4(CharSequence html) {
	Html4Escape escape = new Html4Escape();
	return escape.replace(html).toString();
}
public Html4Escape() {
	addChain(new LookupReplacer(BASIC_ESCAPE));
	addChain(new LookupReplacer(ISO8859_1_ESCAPE));
	addChain(new LookupReplacer(HTML40_EXTENDED_ESCAPE));
}

责任链的写法有多种,可以使用一开始demo中的方式,省去HandlerChain,直接在每个Handler中设置后链的关系,也可以像FilterInterceptorHutool那样定义一个集合,把所有的Handler都放入集合中,并让HandlerChain去维护,调用的时候只需要遍历集合去处理即可。

三、策略模式

1. 描述

策略模式是一种常用又特别容易理解的设计模式,它也是一种行为型模式,最常用于解决if...else...这样的分支逻辑判断,每一个if分支都对应一种不同的策略,或者叫算法,每一种算法独立存在,互不影响,互可替换,很明显策略模式起到的作用就是解耦、可扩展。

2. 标准结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-it6yH5Gq-1658796551934)(C:\Users\wangyilun\AppData\Roaming\Typora\typora-user-images\1655726975579.png)]

public interface Strategy {

    void algorithmInterface();

}
public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithmInterface() {

    }
}
public class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithmInterface() {
        
    }
}
public class ConcreteStrategyC implements Strategy {
    @Override
    public void algorithmInterface() {
        
    }
}
public class Context {

    Strategy strategy;

    public Context(Strategy strategy){
        this.strategy = strategy;
    }

    public void contextInterface(){
        strategy.algorithmInterface();
    }
}
public class Test {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStrategyA());
        context.contextInterface();
    }
}

3. 使用场景

策略模式不仅仅可以用来解决if...else...问题,更重要的是可以解耦不同的业务处理逻辑,让代码更容易扩展,更容易维护,减少复杂度,满足开闭原则,所以一般而言只要某种业务存在多种算法或者行为,并且又需要方便切换,变更的场景,就可以使用策略模式。

优点
  1. 解耦,容易扩展,满足开闭原则
缺点
  1. 过多的策略模式容易造成类的数量太多
  2. 上层应用需要知道到底有哪些策略,以及应该使用哪一种,这不符合设计原则中的迪米特法则,不过完全可以结合其他方式来解决。

4. 使用案例

源码中使用策略模式的也非常多,这里不一一列举,但是我们有必要来看看一些没有完全按照标准结构来实现的策略模式。

4.1 策略枚举
public enum Strategy {

		/**
		 * Default: Retain the first value only.
		 */
		RETAIN_FIRST,

		/**
		 * Retain the last value only.
		 */
		RETAIN_LAST,

		/**
		 * Retain all unique values in the order of their first encounter.
		 */
		RETAIN_UNIQUE

	}

	void dedupe(HttpHeaders headers, Config config) {
		String names = config.getName();
		Strategy strategy = config.getStrategy();
		if (headers == null || names == null || strategy == null) {
			return;
		}
		for (String name : names.split(" ")) {
			dedupe(headers, name.trim(), strategy);
		}
	}

	private void dedupe(HttpHeaders headers, String name, Strategy strategy) {
		List<String> values = headers.get(name);
		if (values == null || values.size() <= 1) {
			return;
		}
		switch (strategy) {
		case RETAIN_FIRST:
			headers.set(name, values.get(0));
			break;
		case RETAIN_LAST:
			headers.set(name, values.get(values.size() - 1));
			break;
		case RETAIN_UNIQUE:
			headers.put(name, values.stream().distinct().collect(Collectors.toList()));
			break;
		default:
			break;
		}
	}

	public static class Config extends AbstractGatewayFilterFactory.NameConfig {

		private Strategy strategy = Strategy.RETAIN_FIRST;

		public Strategy getStrategy() {
			return strategy;
		}

		public Config setStrategy(Strategy strategy) {
			this.strategy = strategy;
			return this;
		}

	}
4.2 匿名策略类
  private interface Strategy {
    Iterator<String> iterator(Splitter splitter, CharSequence toSplit);
  }
  public static Splitter on(final CharMatcher separatorMatcher) {
    checkNotNull(separatorMatcher);

    return new Splitter(
        new Strategy() {
          @Override
          public SplittingIterator iterator(Splitter splitter, final CharSequence toSplit) {
            return new SplittingIterator(splitter, toSplit) {
              @Override
              int separatorStart(int start) {
                return separatorMatcher.indexIn(toSplit, start);
              }

              @Override
              int separatorEnd(int separatorPosition) {
                return separatorPosition + 1;
              }
            };
          }
        });
  }
  private static Splitter on(final CommonPattern separatorPattern) {
    checkArgument(
        !separatorPattern.matcher("").matches(),
        "The pattern may not match the empty string: %s",
        separatorPattern);

    return new Splitter(
        new Strategy() {
          @Override
          public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) {
            final CommonMatcher matcher = separatorPattern.matcher(toSplit);
            return new SplittingIterator(splitter, toSplit) {
              @Override
              public int separatorStart(int start) {
                return matcher.find(start) ? matcher.start() : -1;
              }

              @Override
              public int separatorEnd(int separatorPosition) {
                return matcher.end();
              }
            };
          }
        });
  }
  public static Splitter fixedLength(final int length) {
    checkArgument(length > 0, "The length may not be less than 1");

    return new Splitter(
        new Strategy() {
          @Override
          public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) {
            return new SplittingIterator(splitter, toSplit) {
              @Override
              public int separatorStart(int start) {
                int nextChunkStart = start + length;
                return (nextChunkStart < toSplit.length() ? nextChunkStart : -1);
              }

              @Override
              public int separatorEnd(int separatorPosition) {
                return separatorPosition;
              }
            };
          }
        });
  }
4.3 策略+工厂

常见的组合方式

Spring AOP中的应用
public interface AopProxy {
	
	Object getProxy();
	
	Object getProxy(@Nullable ClassLoader classLoader);

}

不同的策略实现

class CglibAopProxy implements AopProxy, Serializable {
}
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
}
class ObjenesisCglibAopProxy extends CglibAopProxy {
}

策略工厂定义

public interface AopProxyFactory {

	AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;

}

具体策略工厂实现

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);

	// 根据不同参数,选择不同的策略(动态代理方式)
	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (!IN_NATIVE_IMAGE &&
				(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

	private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
		Class<?>[] ifcs = config.getProxiedInterfaces();
		return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
	}

}
直接使用switch来构造
public class Context {
    
    public static Strategy getStrategy(String Type) {
        switch (Type) {
            case "A":
                return new ConcreteStrategyA();
            case "B":
                return new ConcreteStrategyB();
            case "C":
                return new ConcreteStrategyC();
            default:
                throw new IllegalArgumentException("Type error!");
        }
    }
    
}
public class Main {
    public static void main(String[] args) {
        Strategy strategyA = Context.getStrategy("A");
    }
}
Spring Bean自动注入
public interface Strategy {
	// 增加一个策略枚举类
    StrategyEnum getStrategyType();

    void algorithmInterface();
}
public enum StrategyEnum {

    StrategyA,

    StrategyB,

    StrategyC

}
@Component
public class ConcreteStrategyA implements Strategy {
    @Override
    public StrategyEnum getStrategyType() {
        return StrategyEnum.StrategyA;
    }

    @Override
    public void algorithmInterface() {
        
    }
}
@Component
public class ConcreteStrategyB implements Strategy {

    @Override
    public StrategyEnum getStrategyType() {
        return StrategyEnum.StrategyB;
    }

    @Override
    public void algorithmInterface() {
        
    }
}
@Component
public class ConcreteStrategyC implements Strategy {

    @Override
    public StrategyEnum getStrategyType() {
        return StrategyEnum.StrategyC;
    }

    @Override
    public void algorithmInterface() {
        
    }
}
@Component
public class StrategyContext implements ApplicationContextAware {

    private static final Map<StrategyEnum, Strategy> registerMap = new ConcurrentHashMap<>();

    public void algorithmInterface(StrategyEnum strategyEnum) {
        registerMap.get(strategyEnum).algorithmInterface();
    }
	
    // 自动注入到map中
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Strategy> strategyMap = applicationContext.getBeansOfType(Strategy.class);
        strategyMap.values().forEach(strategyService -> registerMap.put(strategyService.getStrategyType(), strategyService));
    }
}

使用方式

@Resource
private StrategyContext strategyContext;

@Test
public void contextRegister(){
    strategyContext.algorithmInterface(StrategyEnum.StrategyA);
    strategyContext.algorithmInterface(StrategyEnum.StrategyB);
    strategyContext.algorithmInterface(StrategyEnum.StrategyC);
}

四、建造者模式

1. 描述

建造者模式属于创建型模式,它可以用来优雅的构建一个复杂的对象,其定义如下:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

2. 标准结构

在这里插入图片描述

public class Product {

    private String id;

    private String name;

}

public interface Builder {

    void buildId();

    void buildName();

    Product build();
}
public class ConcreteBuilder implements Builder {

    private Product product = new Product();

    @Override
    public void buildId() {
        product.setId("1");
    }

    @Override
    public void buildName() {
        product.setName("abc");
    }

    @Override
    public Product build() {
        return product;
    }

}
public class Director {

    public Product getProduct(Builder builder){
        builder.buildId();
        builder.buildName();
        return builder.build();
    }

}
public class Test {
    public static void main(String[] args) {
        Director director = new Director();
        Product product = director.getProduct(new ConcreteBuilder());
        System.out.println(product);
    }
}

3. 使用场景

当一个对象构建的属性特别多,又或者是构建起来步骤特别的多,内部结构复杂,但整体的组成逻辑通常是稳定的,其对象的组成部件基本不会改变,但组合方式或者部件的内部逻辑经常会发生改变,当遇到这类场景时,就可以考虑使用构造者模式了。

现在建造者模式也常常用来简化普通对象的构建,比如lombok提供的@Builder注解。

平时我们构建对象时一般都是像如下这样,在属性比较少,且不复杂时完全没有问题,但如果属性很多,就不太合适了。

// 构造方法
Product p = new Product("2","def");

// set方法
Product p = new Product();
p.setId("2");
p.setName("def");

lombok提供的@Builder

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {

    private String id;

    private String name;

}

直接这样使用即可,看起来非常的简洁

Product p = Product.builder()
	.id("2")
	.name("def")
	.build();

编辑后的class文件,可以看到具体的实现方式

public class Product {
    private String id;
    private String name;

    public static Product.ProductBuilder builder() {
        return new Product.ProductBuilder();
    }

    public String getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Product)) {
            return false;
        } else {
            Product other = (Product)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                Object this$id = this.getId();
                Object other$id = other.getId();
                if (this$id == null) {
                    if (other$id != null) {
                        return false;
                    }
                } else if (!this$id.equals(other$id)) {
                    return false;
                }

                Object this$name = this.getName();
                Object other$name = other.getName();
                if (this$name == null) {
                    if (other$name != null) {
                        return false;
                    }
                } else if (!this$name.equals(other$name)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(Object other) {
        return other instanceof Product;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $id = this.getId();
        int result = result * 59 + ($id == null ? 43 : $id.hashCode());
        Object $name = this.getName();
        result = result * 59 + ($name == null ? 43 : $name.hashCode());
        return result;
    }

    public String toString() {
        return "Product(id=" + this.getId() + ", name=" + this.getName() + ")";
    }

    public Product() {
    }

    public Product(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public static class ProductBuilder {
        private String id;
        private String name;

        ProductBuilder() {
        }

        public Product.ProductBuilder id(String id) {
            this.id = id;
            return this;
        }

        public Product.ProductBuilder name(String name) {
            this.name = name;
            return this;
        }

        public Product build() {
            return new Product(this.id, this.name);
        }

        public String toString() {
            return "Product.ProductBuilder(id=" + this.id + ", name=" + this.name + ")";
        }
    }
}
优点
  1. 构建与表示分离,使代码更容易扩展。
  2. lombok使用的方式让代码的可读性更强。
  3. 使对象构建更加灵活,且更容易掌控构建细节。
缺点
  1. 标准的建造者模式结构过于臃肿

4. 使用案例

源码中大多数的建造者模式都没有按照标准的结构来,所以我们学习设计模式一定要学会变通,万万不可生搬硬套。

4.1 JDK

JDK中Calendar类,就可以通过构造者方法的方式构建

其有一个内部对象Builder,提供了一系列的set方法,以及最后构建的build方法。

在这里插入图片描述

		public Calendar build() {
            if (locale == null) {
                locale = Locale.getDefault();
            }
            if (zone == null) {
                zone = TimeZone.getDefault();
            }
            Calendar cal;
            if (type == null) {
                type = locale.getUnicodeLocaleType("ca");
            }
            if (type == null) {
                if (locale.getCountry() == "TH"
                    && locale.getLanguage() == "th") {
                    type = "buddhist";
                } else {
                    type = "gregory";
                }
            }
            switch (type) {
            case "gregory":
                cal = new GregorianCalendar(zone, locale, true);
                break;
            case "iso8601":
                GregorianCalendar gcal = new GregorianCalendar(zone, locale, true);
                // make gcal a proleptic Gregorian
                gcal.setGregorianChange(new Date(Long.MIN_VALUE));
                // and week definition to be compatible with ISO 8601
                setWeekDefinition(MONDAY, 4);
                cal = gcal;
                break;
            case "buddhist":
                cal = new BuddhistCalendar(zone, locale);
                cal.clear();
                break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone, locale, true);
                break;
            default:
                throw new IllegalArgumentException("unknown calendar type: " + type);
            }
            cal.setLenient(lenient);
            if (firstDayOfWeek != 0) {
                cal.setFirstDayOfWeek(firstDayOfWeek);
                cal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
            }
            if (isInstantSet()) {
                cal.setTimeInMillis(instant);
                cal.complete();
                return cal;
            }

            if (fields != null) {
                boolean weekDate = isSet(WEEK_YEAR)
                                       && fields[WEEK_YEAR] > fields[YEAR];
                if (weekDate && !cal.isWeekDateSupported()) {
                    throw new IllegalArgumentException("week date is unsupported by " + type);
                }

                // Set the fields from the min stamp to the max stamp so that
                // the fields resolution works in the Calendar.
                for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
                    for (int index = 0; index <= maxFieldIndex; index++) {
                        if (fields[index] == stamp) {
                            cal.set(index, fields[NFIELDS + index]);
                            break;
                        }
                    }
                }

                if (weekDate) {
                    int weekOfYear = isSet(WEEK_OF_YEAR) ? fields[NFIELDS + WEEK_OF_YEAR] : 1;
                    int dayOfWeek = isSet(DAY_OF_WEEK)
                                    ? fields[NFIELDS + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
                    cal.setWeekDate(fields[NFIELDS + WEEK_YEAR], weekOfYear, dayOfWeek);
                }
                cal.complete();
            }

            return cal;
        }

4.2 MyBatis

MyBatis中用到Build的方式还是挺多的,CacheBuilder、 MappedStatement、SqlSessionFactoryBuilder 等。

Cache

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

在这里插入图片描述

MappedStatement

public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

在这里插入图片描述

4.3 Guava

无独有偶,Guava中的缓存对象,也使用了Builder的方式

在这里插入图片描述

4.4 Spring MVC

SpringMVC中构建ServerHttpRequest对象的方式,到还挺符合建造者模式的标准结构的

ServerHttpRequest接口内部还有一个Builder接口

在这里插入图片描述

注意这个默认方法

default ServerHttpRequest.Builder mutate() {
    return new DefaultServerHttpRequestBuilder(this);
}
interface Builder {

		Builder uri(URI uri);

		Builder path(String path);

		Builder contextPath(String contextPath);

		Builder header(String headerName, String... headerValues);

		Builder headers(Consumer<HttpHeaders> headersConsumer);

		Builder sslInfo(SslInfo sslInfo);

		Builder remoteAddress(InetSocketAddress remoteAddress);

		ServerHttpRequest build();
	}

然后有具体的实现类

class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {

	private URI uri;

	private HttpHeaders headers;

	private String httpMethodValue;

	@Nullable
	private String uriPath;

	@Nullable
	private String contextPath;

	@Nullable
	private SslInfo sslInfo;

	@Nullable
	private InetSocketAddress remoteAddress;

	private Flux<DataBuffer> body;

	private final ServerHttpRequest originalRequest;


	public DefaultServerHttpRequestBuilder(ServerHttpRequest original) {
		Assert.notNull(original, "ServerHttpRequest is required");

		this.uri = original.getURI();
		this.headers = HttpHeaders.writableHttpHeaders(original.getHeaders());
		this.httpMethodValue = original.getMethodValue();
		this.contextPath = original.getPath().contextPath().value();
		this.remoteAddress = original.getRemoteAddress();
		this.body = original.getBody();
		this.originalRequest = original;
	}


	@Override
	public ServerHttpRequest.Builder method(HttpMethod httpMethod) {
		this.httpMethodValue = httpMethod.name();
		return this;
	}

	@Override
	public ServerHttpRequest.Builder uri(URI uri) {
		this.uri = uri;
		return this;
	}

	@Override
	public ServerHttpRequest.Builder path(String path) {
		Assert.isTrue(path.startsWith("/"), "The path does not have a leading slash.");
		this.uriPath = path;
		return this;
	}

	@Override
	public ServerHttpRequest.Builder contextPath(String contextPath) {
		this.contextPath = contextPath;
		return this;
	}

	@Override
	public ServerHttpRequest.Builder header(String headerName, String... headerValues) {
		this.headers.put(headerName, Arrays.asList(headerValues));
		return this;
	}

	@Override
	public ServerHttpRequest.Builder headers(Consumer<HttpHeaders> headersConsumer) {
		Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
		headersConsumer.accept(this.headers);
		return this;
	}

	@Override
	public ServerHttpRequest.Builder sslInfo(SslInfo sslInfo) {
		this.sslInfo = sslInfo;
		return this;
	}

	@Override
	public ServerHttpRequest.Builder remoteAddress(InetSocketAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
		return this;
	}

	@Override
	public ServerHttpRequest build() {
		return new MutatedServerHttpRequest(getUriToUse(), this.contextPath,
				this.httpMethodValue, this.sslInfo, this.remoteAddress, this.body, this.originalRequest);
	}
    
    // ...省略部分代码
}	
	

具体用法,先调用mutate方法,获得默认的构造者实现类,即:DefaultServerHttpRequestBuilder,然后再根据需要选择是否重写方法。

比如AddRequestHeaderGatewayFilterFactory重写了header方法

ServerHttpRequest request = exchange.getRequest().mutate().header(config.getName(), value).build();

ForwardPathFilter重写了path方法

exchange = exchange.mutate().request(exchange.getRequest().mutate().path(routeUri.getPath()).build()).build();

五、工厂模式

1. 描述

工厂模式应该是我们最常遇见的设计模式了,我们见过太多XXXFactory这样的类名了,同样,它也是创建型模式,工厂模式又分为简单工厂模式、工厂模式、抽象工厂模式,其中前两种是非常常见、实用的方式,相比抽象工厂模式比较复杂,实际中使用的也非常少,所以不过多介绍。

2. 标准结构

简单工厂模式

在这里插入图片描述

工厂模式

在这里插入图片描述

public interface OrderFactory {
    Order createOrder();
}
public class NormalOrderFactory implements OrderFactory {
    @Override
    public Order createOrder() {
        return new NormalOrder();
    }
}
public class VIPOrderFactory implements  OrderFactory {
    @Override
    public Order createOrder() {
        return new VIPOrder();
    }
}
public interface Order {
    void create();
}
public class NormalOrder implements Order {
    @Override
    public void create() {
        System.out.println("普通的订单");
    }
}
public class VIPOrder implements Order {
    @Override
    public void create() {
        System.out.println("VIP的订单");
    }
}

简单工厂模式

public class SimpleOrderFactory {

    private static final Map<String, Order> orderMap = new HashMap<>();

    static {
        orderMap.put("normal", new NormalOrder());
        orderMap.put("vip", new VIPOrder());
    }

    public static Order createOrder(String orderType) {
        return orderMap.get(orderType);
    }

}

public class Test {
    public static void main(String[] args) {
    	
    	// 使用工厂模式
        OrderFactory normalOrderFactory = new NormalOrderFactory();
        Order normalOrder = normalOrderFactory.createOrder();
        normalOrder.create();

        OrderFactory vipOrderFactory = new VIPOrderFactory();
        Order vipOrder = vipOrderFactory.createOrder();
        vipOrder.create();
        
        // 使用简单工厂模式
        Order vip = SimpleOrderFactory.createOrder("vip");
        vip.create();

    }
}

3. 使用场景

工厂模式使用起来非常简单,只要稍微复杂一点的对象,都可以使用工厂模式来创建,以此来简化客户端的逻辑。

优点

扩展性好、避免大量的if...else...代码,将复杂的逻辑封装起来,客户端直接调用即可,对客户端友好。

缺点

每增加一个新的产品时,就需要实现一套具体的类和工厂,这样会使得相关业务中的类数量非常多,一定程度上也增加了系统的复杂度。

4. 使用案例

工厂模式的应用真的非常多,最典型的应用应属Spring IOC容器,其中BeanFactory是其核心的底层接口。

此外,工厂模式还有很多变种,有一些使用了工厂模式的思想,但并没有完全按照标准的结构来实现,这也是能运用好设计模式的关键。

比如JDK中的Calendar类中的getInstance方法,根据传入不同的参数,创建出不同的Calendar对象

public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }


    public static Calendar getInstance(TimeZone zone)
    {
        return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
    }

   
    public static Calendar getInstance(Locale aLocale)
    {
        return createCalendar(TimeZone.getDefault(), aLocale);
    }

    
    public static Calendar getInstance(TimeZone zone,
                                       Locale aLocale)
    {
        return createCalendar(zone, aLocale);
    }

private static Calendar createCalendar(TimeZone zone,
                                           Locale aLocale)
    {
        CalendarProvider provider =
            LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                                 .getCalendarProvider();
        if (provider != null) {
            try {
                return provider.getInstance(zone, aLocale);
            } catch (IllegalArgumentException iae) {
                // fall back to the default instantiation
            }
        }

        Calendar cal = null;

        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                switch (caltype) {
                case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
                }
            }
        }
        if (cal == null) {
            // If no known calendar type is explicitly specified,
            // perform the traditional way to create a Calendar:
            // create a BuddhistCalendar for th_TH locale,
            // a JapaneseImperialCalendar for ja_JP_JP locale, or
            // a GregorianCalendar for any other locales.
            // NOTE: The language, country and variant strings are interned.
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                       && aLocale.getCountry() == "JP") {
                cal = new JapaneseImperialCalendar(zone, aLocale);
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
        return cal;
    }

六、模板方法模式

1. 描述

模板方法模式一般用于有标准流程性的业务场景,总体流程不变,但流程中的每一个环节可能有所不同,用标准定义来描述就是,模板方法模式定义了一个算法的步骤,并允许子类为一个或多个步骤提供具体的实现,让子类可以在不改变算法架构的情况下,重新定义算法中的某些步骤。

2. 标准结构

在这里插入图片描述

public abstract class AbstractClass {

    public final void templateMethod(){
        method1();
        method2();
        method3();
    }

    protected abstract void method3();

    protected final void method2(){
        System.out.println("AbstractClass method2");
    }

    protected abstract void method1();

}
public class ConcreteClassA extends AbstractClass {
    @Override
    protected void method3() {
        System.out.println("ConcreteClassA method3");
    }

    @Override
    protected void method1() {
        System.out.println("ConcreteClassA method1");
    }
}
public class ConcreteClassB extends AbstractClass {
    @Override
    protected void method3() {
        System.out.println("ConcreteClassB method3");
    }

    @Override
    protected void method1() {
        System.out.println("ConcreteClassB method1");
    }
}

3. 使用场景

当业务逻存在一些需要多个步骤来完成,某些步骤是标准统一的,某些步骤根据不同的场景又有不同的处理逻辑,如果全部写在一个类中,可能那些不同场景步骤会出现各种条件判断,无论哪个场景发生修改,都会影响整个流程,这不符合开闭原则,但如果为每个场景都分别实现一套,同样对应相同的步骤就会出现重复的逻辑,又会带来大量的重复代码。

那么,模板方法就是解决上述问题的一种非常好的模式,利用模板方法模式可以实现代码的复用和扩展,这实际上在对代码重构时就非常有帮助。

优点

对于不变的部分,与变的部分做到了隔离,符合开闭原则,使用业务非常容易扩展。

对于公用逻辑易于维护。

缺点

由于每一个不同的实现都要一个子类来实现,会导致类的个数变得更多。

4. 使用案例

4.1 Spring

AbstractApplicationContext类的refresh方法,这里就用到了模板方法模式,有一些子方法是由AbstractApplicationContext自己实现的,有一些子方法则是让不同的实现类各自实现。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}

还有BeanPostProcessor类中定义的两个方法postProcessBeforeInitialization和postProcessAfterInitialization,我们一般会称之为前置处理器和后置处理器,光从名字就能感觉到有模板方法含义,实际上也是spring在某处留好了这两个方法的调用,所以应该说是模板方法中的钩子方法。

4.2 Spring MVC

AbstractController提供了handleRequest方法,负责请求处理并返回ModelAndView,其也是通过模板方法模式来实现,这点作者甚至直接写在了文档说明中。

在这里插入图片描述

public abstract class AbstractController extends WebContentGenerator implements Controller {

	@Override
	@Nullable
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

		if (HttpMethod.OPTIONS.matches(request.getMethod())) {
			response.setHeader("Allow", getAllowHeader());
			return null;
		}

		// Delegate to WebContentGenerator for checking and preparing.
		checkRequest(request);
		prepareResponse(response);

		// Execute handleRequestInternal in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					return handleRequestInternal(request, response);
				}
			}
		}

		return handleRequestInternal(request, response);
	}

	/**
	 * Template method. Subclasses must implement this.
	 * The contract is the same as for {@code handleRequest}.
	 * @see #handleRequest
	 */
	@Nullable
	protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
			throws Exception;

}

4.3 JDK

InputStream类
public abstract class InputStream implements Closeable {
    
    // 留给子类实现
	public abstract int read() throws IOException;
    
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;
        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
	}
    
}

其他IO、NIO类库中也有很多类似的设计。

HttpServlet类

这也是比较经典的应用,其定义的doPost/doGet等方法是可以让子类进行重写的

在这里插入图片描述

public abstract class HttpServlet extends GenericServlet
    implements java.io.Serializable
{
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }


    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }
    
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();
        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
            } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                        // Round down to the nearest second for a proper compare
                        // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
            }
        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);	

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
    
}

4.4 MyBatis

BaseExecutor是MyBatis中经典的模板方法模式应用,其主要是用来执行SQL。

query方法可以看作是定义的主流程,doQuery方法是其留给子类实现的。

public abstract class BaseExecutor implements Executor {
    
  // 几个do开头的方法都是留给子类实现的
  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;  
    
    
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
}


  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 具体query方式,交由子类实现
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

七、装饰者模

1. 描述

装饰者模式也是一种比较常见和易理解的一种设计模式,它可以在比修改原类以及使用继承的情况下,动态的给一个对象添加一些额外的功能,通过一个包装对象来包装真实的对象。

2. 标准结构

在这里插入图片描述

Component是一个抽象类或者接口,一般就是最核心的对象,即需要被附加功能的对象。

ConcreteComponent:抽象类或者接口的实现类,也就是真正要装饰的对象。

Decorator:一般也是一个抽象类,继承Component,实现其方法,并在内部持有一个具体的Component对象。

ConcreteDecorator:就是具体的Decorator实现了,真正给ConcreteComponent对象附加功能的对象。

public abstract class Component {    
    public abstract void operate();
}
public class ConcreteComponent extends Component {

    @Override
    public void operate() {
        System.out.println("do something");
    }

}
public abstract class Decorator extends Component {

    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        component.operate();
    }

}
public class ConcreteDecorator extends Decorator {

    public ConcreteDecorator(Component component) {
        super(component);
    }

    public void operate() {
        System.out.println("before do something");
        super.operate();
    }

}

3. 使用场景

装饰者应用场景一个重要的特点就是对功能的增强,听起来和代理模式的意图很像,但要注意的是代理模式增强的往往是与原始类无关的功能,而装饰者附加的都是与原始类相关的功能。

装饰者模式是利用组合代替继承,因此如果想要给一个类增强一些功能,又不要通过继承的方式来完成,那么就可以使用装饰者模式。

优点

  1. 组合优于继承的思想。
  2. 装饰者类与被装饰者类互不影响。
  3. 如果要附加的功能种类较多,可以轻易的实现各种功能的组合。

缺点

如果包装的层级过多,也会比较复杂。

4. 使用案例

Spring

Spring中的TransactionAwareCacheDecorator从名字也看出来了是运用了装饰器模式,增加了缓存对事物的支持。

通过组合的方式,对Cache实现了增强。

public class TransactionAwareCacheDecorator implements Cache {
    private final Cache targetCache;

    public TransactionAwareCacheDecorator(Cache targetCache) {
        Assert.notNull(targetCache, "Target Cache must not be null");
        this.targetCache = targetCache;
    }

    public String getName() {
        return this.targetCache.getName();
    }

    public Object getNativeCache() {
        return this.targetCache.getNativeCache();
    }

    public ValueWrapper get(Object key) {
        return this.targetCache.get(key);
    }

    public <T> T get(Object key, Class<T> type) {
        return this.targetCache.get(key, type);
    }

    public <T> T get(Object key, Callable<T> valueLoader) {
        return this.targetCache.get(key, valueLoader);
    }

    public void put(final Object key, final Object value) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                public void afterCommit() {
                    TransactionAwareCacheDecorator.this.targetCache.put(key, value);
                }
            });
        } else {
            this.targetCache.put(key, value);
        }

    }

    public ValueWrapper putIfAbsent(Object key, Object value) {
        return this.targetCache.putIfAbsent(key, value);
    }

    public void evict(final Object key) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                public void afterCommit() {
                    TransactionAwareCacheDecorator.this.targetCache.evict(key);
                }
            });
        } else {
            this.targetCache.evict(key);
        }

    }

    public void clear() {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                public void afterCommit() {
                    TransactionAwareCacheDecorator.this.targetCache.clear();
                }
            });
        } else {
            this.targetCache.clear();
        }

    }
}

MyBatis

同样是与缓存相关,MyBatis中通过装饰者模式动态的缓存附加了各种功能。

缓存接口

public interface Cache {

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();

  ReadWriteLock getReadWriteLock();

}

有一个最基础的缓存实现类PerpetualCache

public class PerpetualCache implements Cache {

  private final String id;

  private Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

然后在此基础之上,还定义很多附加功能的缓存实现。

比如基于淘汰策略的

可以看出也是内部有一个Cache的引用,以此来实现对Cache功能的增强

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);
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    cycleKeyList(key);
  }

  @Override
  public Object getObject(Object key) {
    keyMap.get(key); //touch
    return delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
    keyMap.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

除此之外,还有很多可以增强的功能。

在这里插入图片描述

JDK

JDK中I/O包中针对数据的读写实现就利用了装饰者模式,其有大量种类的对于数据读写的实现,如果不通过组合的方式,将会带来不可想象的类爆炸现象。

记得刚学习Java I/O类的时候,总是会new一个对象,一层包一层,就好像这样InputStream in = new BufferedInputStream(new FileInputStream("/xxx.txt"));

当时也是搞不懂,也是学了装饰者模式以后才了解。

InputStream接口

public abstract class InputStream implements Closeable {
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

    public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }

    public int available() throws IOException {
        return 0;
    }

    public void close() throws IOException {}

    public synchronized void mark(int readlimit) {}

    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    public boolean markSupported() {
        return false;
    }

}

BufferedInputStream继承了FilterInputStream,其内部同样持有了一个InputStream对象。

public
class BufferedInputStream extends FilterInputStream {
}
public
class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;
}    

要知道InputStream的实现类非常多,唯有组合的方式才能更加自由、灵活的控制附加在一起的功能。

八、组合模式

1. 描述

组合模式又叫部分-整体模式,组合模式将对象组合成树的结构,并且让客户端对单个对象和组合对象的使用能够保持一致,组合模式使用的场景不是非常多,但如果一旦数据满足树形结构,那么使用组合模式将会发挥巨大的作用。

2. 标准结构

在这里插入图片描述

“根节点”

public abstract class Company {
    
    public abstract void method();
    
}

“子节点”

public class Dept extends Company {

    private List<Company> companyList = new ArrayList();

    public void method() {

    }

    public void addCompany(Company company) {

    }

    public void removeCompany(Company company) {

    }

    public Company getChild(int index) {
        return companyList.get(index);
    }
}

“叶子节点”

public class Employee extends Company {
    
    public void method() {

    }
    
}

3. 使用场景

组合模式使用的场景不是非常多,但如果一旦数据满足树形结构,那么使用组合模式将会发挥巨大的作用。

4. 使用案例

Spring

spring为了方便管理缓存,提供了CacheManager接口,主要就提供了两个方法,一个根据缓存名称查找缓存对象,一个获取所管理的所有缓存名称。

/**
 * Spring's central cache manager SPI.
 * Allows for retrieving named {@link Cache} regions.
 *
 * @author Costin Leau
 * @since 3.1
 */
public interface CacheManager {

	/**
	 * Return the cache associated with the given name.
	 * @param name the cache identifier (must not be {@code null})
	 * @return the associated cache, or {@code null} if none found
	 */
	@Nullable
	Cache getCache(String name);

	/**
	 * Return a collection of the cache names known by this manager.
	 * @return the names of all caches known by the cache manager
	 */
	Collection<String> getCacheNames();

}

那么,针对这两个接口要如何实现呢?spring定义了CompositeCacheManager来统一处理,把CacheManager当做“子树”,把Cache当做“叶子”,内部维护了一个List集合,向集合中添加元素时,即可以是CompositeCacheManager本身(spring考虑了可能根据不同的业务,需要独立、隔离CacheManager的需求),也可以是其他的CacheManager实现类(一个CacheManager可以有不同的实现),比如:NoOpCacheManager、CaffeineCacheManager等。

/**
 * Composite {@link CacheManager} implementation that iterates over
 * a given collection of delegate {@link CacheManager} instances.
 *
 * <p>Allows {@link NoOpCacheManager} to be automatically added to the end of
 * the list for handling cache declarations without a backing store. Otherwise,
 * any custom {@link CacheManager} may play that role of the last delegate as
 * well, lazily creating cache regions for any requested name.
 *
 * <p>Note: Regular CacheManagers that this composite manager delegates to need
 * to return {@code null} from {@link #getCache(String)} if they are unaware of
 * the specified cache name, allowing for iteration to the next delegate in line.
 * However, most {@link CacheManager} implementations fall back to lazy creation
 * of named caches once requested; check out the specific configuration details
 * for a 'static' mode with fixed cache names, if available.
 *
 * @author Costin Leau
 * @author Juergen Hoeller
 * @since 3.1
 * @see #setFallbackToNoOpCache
 * @see org.springframework.cache.concurrent.ConcurrentMapCacheManager#setCacheNames
 */
public class CompositeCacheManager implements CacheManager, InitializingBean {

	private final List<CacheManager> cacheManagers = new ArrayList<>();

	private boolean fallbackToNoOpCache = false;


	/**
	 * Construct an empty CompositeCacheManager, with delegate CacheManagers to
	 * be added via the {@link #setCacheManagers "cacheManagers"} property.
	 */
	public CompositeCacheManager() {
	}

	/**
	 * Construct a CompositeCacheManager from the given delegate CacheManagers.
	 * @param cacheManagers the CacheManagers to delegate to
	 */
	public CompositeCacheManager(CacheManager... cacheManagers) {
		setCacheManagers(Arrays.asList(cacheManagers));
	}


	/**
	 * Specify the CacheManagers to delegate to.
	 */
	public void setCacheManagers(Collection<CacheManager> cacheManagers) {
		this.cacheManagers.addAll(cacheManagers);
	}

	/**
	 * Indicate whether a {@link NoOpCacheManager} should be added at the end of the delegate list.
	 * In this case, any {@code getCache} requests not handled by the configured CacheManagers will
	 * be automatically handled by the {@link NoOpCacheManager} (and hence never return {@code null}).
	 */
	public void setFallbackToNoOpCache(boolean fallbackToNoOpCache) {
		this.fallbackToNoOpCache = fallbackToNoOpCache;
	}

	@Override
	public void afterPropertiesSet() {
		if (this.fallbackToNoOpCache) {
			this.cacheManagers.add(new NoOpCacheManager());
		}
	}


	@Override
	@Nullable
	public Cache getCache(String name) {
		for (CacheManager cacheManager : this.cacheManagers) {
			Cache cache = cacheManager.getCache(name);
			if (cache != null) {
				return cache;
			}
		}
		return null;
	}

	@Override
	public Collection<String> getCacheNames() {
		Set<String> names = new LinkedHashSet<>();
		for (CacheManager manager : this.cacheManagers) {
			names.addAll(manager.getCacheNames());
		}
		return Collections.unmodifiableSet(names);
	}

}

可以看到getCache,getCacheNames都是通过递归完成。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值