【问题标题】:Wrapper Classes are not suited for callback frameworks包装类不适合回调框架
【发布时间】:2015-01-31 16:57:53
【问题描述】:

包装类的缺点很少。一个警告是包装类不适合在回调框架中使用,其中对象将自引用传递给其他对象以进行后续调用(“回调”)。因为被包装的对象不知道它的包装器,所以它会传递一个对自身的引用(this)并且回调会避开包装器。

也许有人可以用一个例子来解释这意味着什么。它是用 Effective 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。

【问题讨论】:

    标签: java design-patterns callback coding-style wrapper


    【解决方案1】:

    如果你能保证你总是在任何地方(为了将来的回调)传递一个转发对象的引用,那么一切都好。尽管如此,您可以创建一个对象,用某个类包装它,但该对象本身可以有一些方法将 this 传递到某个地方,例如某个侦听器或其他地方。在这种情况下,您的包装器不知道被包装的对象发生了什么。例如:

    // 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++;
        } 
    }
    

    Model 的包装器只是避开来自Controller 回调的makeChange() 方法的调用。

    【讨论】:

    • 所以基本上,如果我理解正确,这是因为“模型”正在使用自己进行一些回调,但它不知道(它不能) ModelChangesCounter 因此不考虑(在这个案例,makeChange())?在这种情况下,与组合相比,继承/扩展会有所帮助。对吗?
    • @ChotaBheem 实际上,这完全取决于您的对象图应该如何构建。尝试遵循松散耦合原则是一件好事,什么套件更好取决于具体情况。我的意思是,一些单独的类创建模型和控制器并相互初始化。该初始化程序(或工厂模式)具有全面且排他的控制。此外,一些 DI 框架可以提供帮助。如果您从 Model 继承了 ModelChangesCounter,则在父构造函数调用链上 ModelChangesCounter 实例将被传递给 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 所说的“回调躲避包装”时所指的内容。

    参考:Link

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-31
      • 2012-09-08
      • 2012-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-26
      • 2015-03-11
      相关资源
      最近更新 更多