Wrapper Classes are not suited for callback frameworks
Asked Answered
M

2

15

The disadvantages of wrapper classes are few. One caveat is that wrapper classes are not suited for use in callback frameworks, wherein objects pass self references to other objects for subsequent invocations (“callbacks”). Because a wrapped object doesn’t know of its wrapper, it passes a reference to itself (this) and callbacks elude the wrapper.

Can someone explain what this means with an example perhaps. It is written in Effective Java but I did not completely understand it.

To add to the context, instead of inheritance we should favor composition which leads to instead to sub-classing Set we should use something like this:

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); }
 ...
}

But, how this will fail, I am still not able to understand for callbacks. In JavaScript we can use function callbacks but how the same concept applies to Java if someone can explain.

Maryn answered 31/1, 2015 at 16:57 Comment(0)
B
11

If you can guarantee that you always pass anywhere(for future callbacks) a reference of an object that is forwarded, then everything is ok. Nevertheless you can create an object, wrap it with some class, but that object itself can have some method that passes this somewhere, e.g to some listener, or somewhere else. In this case your wrapper has no clue about what is happening to the wrapped object. E.g.:

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

A wrapper for Model just eludes invokations of makeChange() method which come from Controller callback.

Bridgetbridgetown answered 26/9, 2015 at 23:0 Comment(2)
So basically, If I undertand it right, this is because 'Model' is using itself for some callback but it has not knowledge(it can't) of ModelChangesCounter hence that not be considered(in this case, makeChange())? And this is the case where inhertiance/extends would help compared to composition. Correct?Dincolo
@ChotaBheem Actually it's all about how your object graph is supposed to be built. It's nice thing to try following loose coupling principle, and what suites better depends on situation. I mean, some separate class which creates Model and Controller and initializes one with another. That initializer(or factory pattern) has a comprehensive and exclusive control. Futhermore some DI framework can help. If you would have inherited ModelChangesCounter from Model, on parent constructor call chain ModelChangesCounter instance would be passed to Controller. You're correct.Bridgetbridgetown
G
5
    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();
    }   

The problem is that even though we called doSomething() on the wrapper, the callback of the wrapped object got called, not the callback of the wrapper. This is what Joshua Bloch refers to when he says that "callbacks elude the wrapper".

Reference: Link

Gary answered 29/1, 2019 at 6:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.