【设计模式】委派模式(Delegate Pattern)

1,264 阅读7分钟

前言

本文将阐述设计模式中的委派模式,包括委派模式的应用场景、框架源码分析等,最后综合阐述下委派模式的优缺点。

希望可以帮忙大家更好的理解委派模式。@空歌白石

委派模式定义

委派模式(Delegate Pattern)又称之为委托模式。它的基本作用是负责任务的调度和任务的分配,将任务的分配和执行分离开。

委托可以看做是一种特殊情况下的静态代理的全权代理。

委托模式并不属于GOF23种设计模式之一,属于行为型模式。

应用场景

  1. 委派对象本身不知道如何处理一个任务或一个请求,把请求交给其他对象来处理。
  2. 可以实现程序的解耦

授权委托书

如果需要处理某个事情,但是没有时间来办理,这时候是可以委托他人代为处理的。

7e63a8f837d16ab5f740f18d1a19f12e.jpeg

老板给员工下达任务

很多时候老板并不具有在某个特殊领域的处理能力,这时候就是需要找一位有能力的下属来完成想要做的事情。这时候就可以看做老板委派员工处理某些事情。

c35e6920b6c181cfa46090cf51cd63ed.jpeg

委派模式的实现

任务实际执行角色

这里我们定义两种视频流服务,一种是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加载逻辑图,由于本文是讲述设计模式,关于双亲委派的其他逻辑,不过多的展开,有兴趣的话可以查阅相关的资料。

设计模式.png

以下是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的实现类图。

MethodAccessor.png

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的主要职责是封装配置信息的在内存的状态。

DefaultBeanDefinitionDocumentReaderdoRegisterBeanDefinitions方法中使用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);
            }
        }
    }
}

优缺点

优点

通过任务委派能够将一个大型的任务细化,然后通过统一管理子任务的完成情况实现任务的跟进,能够加快任务执行的效率。

缺点

委派模式需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下可能需要进行多重委派,容易造成系统过于复杂。

与代理模式、门面模式区别

  1. 委派模式是行为模式,代理模式是结构型模式
  2. 委派模式注重的是任务的派遣分发,注重处理结果;代理模式注重代码的增强和优化,注重处理过程的优雅。
  3. 委派模式是一种特殊的静态代理,相当于全权代理。
    1. 门面模式也是一种特殊的代理,但不是全权代理,可以认为是一个多重代理
    2. 委派模式是单一的代理,将同一类型的任务放在同一个委派类中。

结束语

设计模式可以使得代码更加优雅,增强代码的扩展性。但是万万不能为了设计模式而设计模式,如果真的这样做了,反而会适得其反,画蛇添足,大幅度增加代码复杂度和降低可维护性。只有在充分的分析业务场景、代码结构的前提下合理的使用设计模式,才能发挥出设计模式最大的作用。

最后一句:设计模式是道法,并不是术法。理解内涵最为重要。

192e0e94cf70d76b0fafc55b59fa1ad2.jpeg