包装类不适合回调框架

包装类的缺点很少。需要注意的是,包装类不适合在回调框架中使用,其中对象将自引用传递给其他对象以进行后续调用(“回调”)。由于包装对象不知道其包装器,因此它会传递对自身的引用(this),并且回调会避开包装器。

有人可以用一个例子来解释这意味着什么吗?它是用有效的Java编写的,但我并不完全理解它。

为了添加到上下文中,我们应该支持组合,而不是继承,这会导致子类,我们应该使用这样的东西:Set

public class ForwardingSet<E> implements Set<E> {
 private final Set<E> s;
 public ForwardingSet(Set<E> s) { this.s = s; }
 public void clear() { s.clear(); }
 public boolean contains(Object o) { return s.contains(o); }
 ...
}

但是,这将如何失败,我仍然无法理解回调。在JavaScript中,我们可以使用函数回调,但是如果有人可以解释,那么相同的概念如何适用于Java。


答案 1

如果您可以保证始终在任何地方传递转发对象的引用(以便将来回调),那么一切都没问题。尽管如此,你可以创建一个对象,用某个类包装它,但该对象本身可以有一些方法将它传递到某个地方,例如传递给某个侦听器,或者其他地方。在这种情况下,您的包装器对包装对象发生的情况一无所知。例如:

// basic class which we will wrap
public class Model{ 
    Controller controller;

    Model(Controller controller){
        this.controller = controller; 
        controller.register(this); //Pass SELF reference
    }

    public void makeChange(){
        ... 
    }
} 

public class Controller{
    private final Model model;

    public void register(Model model){
        this.model = model;
    }

    // Here the wrapper just fails to count changes, 
    // because it does not know about the wrapped object 
    // references leaked
    public void doChanges(){
        model.makeChange(); 
    } 
}

// wrapper class
public class ModelChangesCounter{
    private final Model; 
    private int changesMade;

    ModelWrapper(Model model){
        this.model = model;
    }

    // The wrapper is intended to count changes, 
    // but those changes which are invoked from 
    // Controller are just skipped    
    public void makeChange(){
        model.makeChange(); 
        changesMade++;
    } 
}

的包装器只是逃避来自回调的方法的调用。ModelmakeChange()Controller


答案 2
    interface SomethingWithCallback {

      void doSomething();

      void call();

    }


    class WrappedObject implements SomethingWithCallback {

      private final SomeService service;

      WrappedObject(SomeService service) {
        this.service = service;
      }

      @Override
      public void doSomething() {
        service.performAsync(this);
      }

      @Override
      public void call() {
        System.out.println("WrappedObject callback!");
      }
    }


    class Wrapper implements SomethingWithCallback {

      private final WrappedObject wrappedObject;

      Wrapper(WrappedObject wrappedObject) {
        this.wrappedObject = wrappedObject;
      }

      @Override
      public void doSomething() {
        wrappedObject.doSomething();
      }

      void doSomethingElse() {
        System.out.println("We can do everything the wrapped object can, and more!");
      }

      @Override
      public void call() {
        System.out.println("Wrapper callback!");
      }
    }

    final class SomeService {

      void performAsync(SomethingWithCallback callback) {
        new Thread(() -> {
          perform();
          callback.call();
        }).start();
      }

      void perform() {
        System.out.println("Service is being performed.");
      }
    }
    public static void main(String[] args) {
        SomeService   service       = new SomeService();
        WrappedObject wrappedObject = new WrappedObject(service);
        Wrapper       wrapper       = new Wrapper(wrappedObject);
        wrapper.doSomething();
    }   

问题是,即使我们在包装器上调用了doSomething(),包装对象的回调也被调用,而不是包装器的回调。这就是Joshua Bloch所说的“回调逃避包装器”时所指的。

参考:链接


推荐