代理模式與Kotlin中的委託模式

fb0122發表於2019-03-06

代理模式與Kotlin中的委託模式

圖片來自必應

代理模式(Proxy):Proxy是“代理人”的意思,意思是有一些工作不一定需要本人去完成,而是可以委託代理人去完成。代理模式屬於結構型模式,相當於我們為委託物件提供了一個訪問控制,當外部客戶端想要訪問委託物件的時候,通過代理物件訪問。

Java中的代理模式(靜態代理與動態代理)

首先我們來看一下代理模式的UML類圖:

代理模式與Kotlin中的委託模式

從UML類圖中可以看到,當客戶端想要訪問一個物件的時候,可以通過訪問Proxy這個代理類,這樣就達到了訪問控制的目的。下面我們通過一個例子來說明一下代理模式的具體作用。比如現在有一個圖書館,我們可以從圖書館中借書或者買書,那麼我們可以定義一個介面型別如下,對比UML類圖中的Subject

public interface ILibrary{
    void borrow();
    void buy();
}
複製程式碼

那麼我們可以定義一個ILibrary的實現類,對比UML類圖中的RealSubject

public class Library implements ILibrary{
	@Override
    public void buy(){
        System.out.println("buy book");
    }
    
    @Override
    public void borrow(){
        System.out.println("borrow book");        
    }
}
複製程式碼

那麼當我們定義好Library這個例項後,比如我們現在想要在借書之前做一個標記“書被借出了”。那麼我們有幾種方式可以去實現,比如我們可以直接修改Library,但可能下次又會加一個類似“哪種書被借出了”、“第幾區第幾排的書被借出了”類似的需求,那麼我們就就要不斷的去修改原有的類,或者不斷的繼承原有的類去修改方法,所以我們直接修改原有的類是不可行的。這時候,我們就需要一個代理類,幫助我們去實現類似的方法、對比UML類圖中的Proxy:

public class LibraryProxy implements ILibrary{
    private Library library;
    
    public LibraryProxy(Library library){
        this.library = library;
    }
    
    @Override
    public void buy(){
        library.buy();
    }
    
    @Override
    public void borrow(){
        System.out.println("---tag---");
        library.borrow();        
    }
    
}
複製程式碼

那麼我們可以在使用中:

public class ProxyTest{
    public static void main(String[] args){
        ILibrary library = new LibraryProxy(new Library());
        library.buy();
        library.borrow();
    }
}
複製程式碼

上面程式碼輸出的結果為:

buy book
---tag---
borrow book
複製程式碼

從上面的程式碼可以看出,我們通過聚合的方法,通過代理持有一個委託類的例項,客戶端通過代理類訪問委託類的物件,從而實現一些操作。這就是代理模式的簡單實現。那麼還有個問題,如果我們想在買書的上面也加上類似的需求,我們或許可以在代理中直接加上程式碼,但如果我們在ILibrary中再加入一些方法,比如“還書”、“下架書”、“上架書”等等等的方法,我們都要去代理中給這些方法寫上同樣的程式碼,很麻煩,這時候我們就可以使用動態代理去解決這個問題。如果要使用動態代理,我們來新建一個類,名字叫做DynamicLibraryHandler

public class DynamicLibraryHandler implements InvocationHandler {
  private Object library;

  public DynamicLibraryHandler(Object library){
    this.library = library;
  }

  @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    preStart(); // 執行一些操作
    System.out.println("---pre---");
    method.invoke(pingpai, args);
    System.out.println("---end---");
    doEnd();   // 執行一些操作
    return null;
  }
}
複製程式碼

然後修改一下我們的main方法:

public class ProxyTest{
    public static void main(String[] args){
        // ILibrary library = new LibraryProxy(new Library());
        // library.buy();
        // library.borrow();
        DynamicLibraryHandler h = new DynamicLibraryHandler(new Library());
        ILibrary lib = Proxy.newProxyInstance(ILibrary.class.getClassLoader(), new Class[]{ILibrary.class}, h);   //獲取代理物件
        lib.buy();
        lib.borrow();
    }
}
複製程式碼

上面的程式碼輸出結果為:

---pre---
buy book
---end---
---pre---
borrow book
---end---
複製程式碼

可以看到,我們並沒有實現代理類,也對每個方法都加上了我們想要的需求,關鍵點就在InvocationHandlerProxy.newProxyInstance() 上,那麼首先來看一下InvocationHandler 的原始碼:

/**
 * {@code InvocationHandler} is the interface implemented by
 * the <i>invocation handler</i> of a proxy instance.
 *
 * 每個代理例項都有一個與其關聯的Invocation handler, 當呼叫例項的方法的時候,方法呼叫將會
 * 被分發到其InvocationHandler的invoke方法。
 * ...
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */
public interface InvocationHandler {
	...
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
複製程式碼

InvocationHandler的註釋可以看出來,當生成的代理類呼叫它的方法的時候,該方法將會被InvocationHandler 轉發,呼叫其invoke() 方法,實現代理。

從上面的例子中可以看出,代理類的建立是通過 Proxy.newProxyInstance() 建立的,再來看下其原始碼:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs); // 通過ClassLoader和interface找到相關類
    try {
        // Android-changed: sm is always null
        // if (sm != null) {
        //     checkNewProxyPermission(Reflection.getCallerClass(), cl);
        // }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            // Android-changed: Removed AccessController.doPrivileged
            cons.setAccessible(true);
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
    	...
    }
}
複製程式碼

從上面的原始碼中可以看出來,代理類是通過 getProxyClass0(loader, intfs) 獲取的,那麼再來看一下 getProxyClass0(loader, intfs) 的原始碼:

     private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
            return proxyClassCache.get(loader, interfaces);
        }
複製程式碼

從原始碼看出來是通過 proxyClassCache 獲取的:

private static final ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
    ...
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            Class<?> interfaceClass = null;
            
            try {
            //通過interface找到介面類
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            ...
        }
        /*
         * 將每個非公共的介面所在的包記錄下來,是為了將代理類與介面定義在同一個包下
         * 並且驗證所有的非公共的介面都在同一個包內
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
      		...
            return generateProxy(proxyName, interfaces, loader, methodsArray,
                                 exceptionsArray);
        }
    }
}
複製程式碼

可以看出來,通過native的 generateProxy() 方法生成的代理類。動態代理在很多地方都有用到,比如我們常用的網路框架Retrofit。

Kotlin中的委託

Kotlin中的委託模式的實現方式,在程式碼上顯得更加清晰一些,我們首先來引用一段官方的例子:

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}
複製程式碼

這裡我們看到kotlin中使用到了一個by 的關鍵字,by就表示b就是我們在前面所說的UML類圖中的RealSubject。我們可以將這段程式碼使用Android Studio轉為Java程式碼看一下就可以看出來了:

public final class Derived implements Base {
    // $FF: synthetic field
    private final Base $$delegate_0;
    
    public Derived(@NotNull Base b) {
        Intrinsics.checkParameterIsNotNull(b, "b");
        super();
        this.$$delegate_0 = b;
        }
    
    public void println() {
          this.$$delegate_0.println();
       }
    }
複製程式碼

上面程式碼已經很清楚了,和我們的代理模式寫法一樣。我們把上面的程式碼稍作修改:

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(val b: Base) : Base by b{
    override fun print(){
        b.print()
        print("adc")
    }
}

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}
複製程式碼

這樣我們的程式輸出結果就是

10
abc
複製程式碼

相關文章