Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Break cyclic dependency in order to use dependency injection

I just started using dagger 2 and have not used any other dependency injection framework before. Now I'm stuck with a cyclic dependency and I don't know how to solve it properly. Consider the following example in a server application, which uses the Reactor pattern with Java NIO:

I have a Handler object attached to a selection key that is executed when new information arrives on the network:

class Handler implements Runnable {
  Server server;
  Client client;

  public void run {
    // static factory method that eventually calls a method on server, passing in 'client' as argument
    Command.parse(input).execute(server, client); 

  }

  public void send(String string) {
    // enqueu string and inform reactor about interest in sending
  }
}

The Client class holds some state about the connected client. All connected Clients are managed in the Server class.

class Client {
  Handler h;

  public send(String response) {
    h.send(response);          
  }
}

When new input arrives, the Handler creates the Command objects, executes them on the server, and the server eventually will respond to the client.

So what I'm doing right now, is creating a Client object manually in Handler, passing in a this reference, in order to be able to send the response:

client = new Client(this);

So my question now is: Is something wrong with the design? Is it possible to decouple Client and Handler? Or should I just live with this and don't use dependency injection everywhere?

I appreciate your suggestions

like image 994
S1lentSt0rm Avatar asked Jan 28 '15 11:01

S1lentSt0rm


2 Answers

If you want the client to be able to send a message back through handler, then the following can break your cycle:

// lives in a common package both classes access
public interface ResponseClass {
     public void sendSomeMessage(String message);
}

public class Handler { // handler can also implement ResponseClass directly but I prefer using an additional class for flexibility.
     public void whenYouCreateClient() {
         Client client = new Client(new HandlerWrapper(this)); 
     }

     public static class HandlerWrapper implements ResponseClass {
         private final Handler handler;

         public HandlerWrapper(Handler handler) { this.handler = handler; }

         public void sendSomeMessage(String message) {
             handler.send(message);
         }
     }

     public void send(String string) {
         // enqueu string and inform reactor about interest in sending
     }
}

public class Client {
    ResponseClass rc; // naming should be improved :)

    public void sendMessage(String message) {
        rc.sendSomeMessage(message);
    }
}

Now runtime your classes are still tied together but as far as your design is concerned your Client is only attached to a generic ResponseClass.

you can have a hierarchy like:

common <-- client <-- handler

where handler knows of client and common and client only knows of common. (assuming you put the interface in the common package)

instead of client <--> handler

I deliberately used sendSomeMessage to highlight that it's a different method you call ont he wrapper/interface but ofcourse you can name them however you like.

One remark: I didn't use dagger2 so I cannot say for sure that what I do can be done using that product, but this is how I would decouple this sort of cyclic dependency

like image 190
Joeblade Avatar answered Oct 13 '22 11:10

Joeblade


I realized that what I really was trying to solve was not breaking the dependency between Client and Handler, but to use dependency injection instead of the new operator.

The solution I was looking for: Inject a ClientFactory into the constructor of Handler and use clientFactory.create(this) to create a Client object instead. The brilliant library AutoFactory allows you to create such a factory with a simple @AutoFactory annotation. The constructor of the created class is automatically annotated with @Inject.

like image 39
S1lentSt0rm Avatar answered Oct 13 '22 10:10

S1lentSt0rm