前言
本文将阐述设计模式中的委派模式
,包括委派模式的应用场景、框架源码分析等,最后综合阐述下委派模式的优缺点。
希望可以帮忙大家更好的理解委派模式。@空歌白石
委派模式定义
委派模式(Delegate Pattern)
又称之为委托模式
。它的基本作用是负责任务的调度和任务的分配,将任务的分配和执行分离开。
委托
可以看做是一种特殊情况下的静态代理的全权代理。
委托模式
并不属于GOF23种设计模式之一,属于行为型模式。
应用场景
- 委派对象本身不知道如何处理一个任务或一个请求,把请求交给其他对象来处理。
- 可以实现程序的解耦
授权委托书
如果需要处理某个事情,但是没有时间来办理,这时候是可以委托他人代为处理的。
老板给员工下达任务
很多时候老板并不具有在某个特殊领域的处理能力,这时候就是需要找一位有能力的下属来完成想要做的事情。这时候就可以看做老板委派
员工处理某些事情。
委派模式的实现
任务实际执行角色
这里我们定义两种视频流服务,一种是Netflix
,一种是YouTube
。
public interface VideoStreamingService {
void doProcessing();
}
public class NetflixService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("NetflixService is now processing");
}
}
public class YouTubeService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("YouTubeService is now processing");
}
}
对不同任务的封装
BusinessLookup
负责根据moive
的不同,判断使用哪种视频播放器。
public class BusinessLookup {
private NetflixService netflixService;
private YouTubeService youTubeService;
public VideoStreamingService getBusinessService(String movie) {
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
return netflixService;
} else {
return youTubeService;
}
}
}
大家应该可以看出来,实际上这里有一个可以优化的点,在getBusinessService
方法中,有着复杂的if else
处理逻辑,如果我们要加第三种视频播放器,比如IQIYI
,那么势必要增加一个新的if else
判断或者用switch
处理,这时候就可以用另外一种设计模式:策略模式
。大家可以看之前写的关于策略模式的文章,这里就不再赘述了。【设计模式】策略模式(Strategy Pattern)。
实际的被委托角色
被委托角色类一般以Delegate
结尾,在后面的源码分析中也可以看到这点。在BusinessDelegate
中,依赖了 BusinessLookup
并开发了一个方法给实际的调用者,调用者仅仅和BusinessDelegate
打交道,不用关心后台具体的实现是怎样的了。
public class BusinessDelegate {
private BusinessLookup lookupService;
public void playbackMovie(String movie) {
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
videoStreamingService.doProcessing();
}
}
具体应用
通过一下client可以实现具体的播放功能。
public class MobileClient {
private final BusinessDelegate businessDelegate;
public MobileClient(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public void playbackMovie(String movie) {
businessDelegate.playbackMovie(movie);
}
}
调用者
调用方首先准备具体的想要做的任务,接着由delegate负责具体的执行。
public class App {
public static void main(String[] args) {
BusinessDelegate businessDelegate = new BusinessDelegate();
BusinessLookup businessLookup = new BusinessLookup();
businessLookup.setNetflixService(new NetflixService());
businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
MobileClient client = new MobileClient(businessDelegate);
client.playbackMovie("Die Hard 2");
client.playbackMovie("Maradona: The Greatest Ever");
}
}
框架源码分析
JDK ClassLoader
JVM的类加载机制是双亲委派机制,下图是整体的ClassLoader加载逻辑图,由于本文是讲述设计模式,关于双亲委派的其他逻辑,不过多的展开,有兴趣的话可以查阅相关的资料。
以下是loadClass
的双亲委派的实现。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 空歌白石:这里实现了双亲委派的核心逻辑
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
JDK Method
JDK的反射中经常会使用到Method
类,在具体的invoke
方法中,Method
将实际调用的逻辑交给MethodAccessor
实现。MethodAccessor
便是delegate类的抽象接口。
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
@IntrinsicCandidate
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
public interface MethodAccessor {
/** Matches specification in {@link java.lang.reflect.Method} */
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}
MethodAccessor
的实现类图。
abstract class MethodAccessorImpl extends MagicAccessorImpl
implements MethodAccessor {
/** Matches specification in {@link java.lang.reflect.Method} */
public abstract Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;
DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
setDelegate(delegate);
}
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
return delegate.invoke(obj, args);
}
void setDelegate(MethodAccessorImpl delegate) {
this.delegate = delegate;
}
}
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private static final Unsafe U = Unsafe.getUnsafe();
private static final long GENERATED_OFFSET
= U.objectFieldOffset(NativeMethodAccessorImpl.class, "generated");
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;
private volatile int generated;
NativeMethodAccessorImpl(Method method) {
this.method = method;
}
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
// We can't inflate methods belonging to vm-anonymous classes because
// that kind of class can't be referred to by name, hence can't be
// found from the generated bytecode.
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !method.getDeclaringClass().isHidden()
&& generated == 0
&& U.compareAndSetInt(this, GENERATED_OFFSET, 0, 1)) {
try {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
} catch (Throwable t) {
// Throwable happens in generateMethod, restore generated to 0
generated = 0;
throw t;
}
}
return invoke0(method, obj, args);
}
void setParent(DelegatingMethodAccessorImpl parent) {
this.parent = parent;
}
private static native Object invoke0(Method m, Object obj, Object[] args);
}
Spring BeanDefinition
BeanDefinition
的主要职责是封装配置信息的在内存的状态。
DefaultBeanDefinitionDocumentReader
的doRegisterBeanDefinitions
方法中使用createDelegate
创建委托类。
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
protected BeanDefinitionParserDelegate createDelegate(
XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
delegate.initDefaults(root, parentDelegate);
return delegate;
}
SpringMVC DispatcherServlet
DispatcherServlet
实现了针对不同的HttpRequest处理不同的任务,本身不处理任务,是负责Request的调度。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
优缺点
优点
通过任务委派能够将一个大型的任务细化,然后通过统一管理子任务的完成情况实现任务的跟进,能够加快任务执行的效率。
缺点
委派模式
需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下可能需要进行多重委派,容易造成系统过于复杂。
与代理模式、门面模式区别
委派模式
是行为模式,代理模式
是结构型模式委派模式
注重的是任务的派遣分发,注重处理结果;代理模式
注重代码的增强和优化,注重处理过程的优雅。委派模式
是一种特殊的静态代理,相当于全权代理。门面模式
也是一种特殊的代理,但不是全权代理,可以认为是一个多重代理委派模式
是单一的代理,将同一类型的任务放在同一个委派类中。
结束语
设计模式可以使得代码更加优雅,增强代码的扩展性。但是万万不能为了设计模式而设计模式,如果真的这样做了,反而会适得其反,画蛇添足,大幅度增加代码复杂度和降低可维护性。只有在充分的分析业务场景、代码结构的前提下合理的使用设计模式,才能发挥出设计模式最大的作用。
最后一句:设计模式是道法
,并不是术法
。理解内涵最为重要。