3
\$\begingroup\$

I have an Android application that is using an EventBus architecture with publishing and subscribing events. Usually this is from Controllers (Activities) to a "Manager" class that handles all the subscriptions.

Here is an example:

Controller Class

public abstract class EventBusActivity extends Activity {

    protected Bus mBus;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBusFailureEventListener = createBusFailureListener();
        mBus = getBus();
    }

    @Override
    public void onPause() {
        super.onPause();
        mBus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        mBus.register(this);
    }

}

public class ActivityController extends EventBusActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_controller);
        initViews();
        sendEvent();
    }

    private void initViews() {
        // Initialize views
    }

    private void sendEvent() {
        mBus.post(new UpdateEvent());
    }

    @Subscribe
    public void onOtherStuff(OtherStuff otherStuff) {
        String someString = otherStuff.getOtherString();
        Intent i = new Intent(this, SecondActivityController.class);
        i.putString("some_string", someString);
        startActivity(i);
    }

}

Manager Class

private class Manager {

    private Client sClient;
    private Bus mBus;
    private Context mContext;

    private Manager(Context context, Bus bus) {
        this.mContext = context;
        this.mBus = bus;
        this.sClient = Client.getInstance();
    }

    @Subscribe
    public void onUpdateEvent(UpdateEvent updateEvent) {

        Callback<ModelObject> callback = new Callback<>() {
            @Override
            public void onSuccess(ModelObject model, Response response) {
                mBus.post(new OtherStuff(model.getString());
            }
            @Override
            public void onFailure(Error error) {
               // Handle error
            }
        };
        sClient.updateEvent();
    }

    // Other Subscription methods....

    // Example
    @Subscribe
    public void onLoadUserEvent(LoadUserEvent loadUserEvent) {

        Callback<User> callback = new Callback<>() {
            @Override
            public void onSuccess(User user, Response response) {
                mBus.post(new LoadedUserEvent(user));
            }
            @Override
            public void onFailure(Error error) {
                // Handle error
            }
        };
        sClient.getUser();
    }

}

Client Class

public class Client {

    private static String API_URL = "http://api.com"
    private static Client mClient;
    private RestAdapter mAsyncRestAdapter;

    private Client() {
        mAsyncRestAdapter = RestAdapter.Builder()
            .setEndpoint(API_URL)
            .build();
    }

    public void updateEvent() {
        IUpdate update = mAsyncRestAdapter.create(IUpdate.class);
        update.updateEvent();
    }

    // Other implementation methods
    // Example
    public void getUser() {
        IUser user = mAsyncRestAdapter.create(IUser.class);
        user.getUser();
    }

}

Event Classes

public class UpdateEvent {
    // Empty class to just trigger event
}

public class OtherStuff {

    private String mOtherString;

    public OtherStuff(String otherString) {
        this.mOtherString = otherString;
    }

    public String getOtherString() { return mOtherString; }

}

// Other Event Classes...
// Example

public class LoadUserEvent() {
    // Empty class to just trigger event
}

public class LoadedUserEvent {

    private User mUser;

    public LoadedUserEvent(User user) {
        this.mUser = user;
    }

    public User getUser() {
        return mUser;
    }

}

API Interfaces

public interface IUpdate {
    @PUT("http://api.com/update")
    void updateEvent(Callback<ModelObject> callback);
}

// Other API Interfaces...
// Example

public interface IUser {
    @GET("http://api.com/getUser")
    void getUser(Callback<User> callback);
}

So to just recap, my ActivityController classes post events to the Bus, since the Bus is handled in the Manager class all published events from Controllers end up there, the Client is then activated to return something from an API in a callback and then a new Event is posted back to the Controller. The event bus is name space specific, an Event only gets triggered if its name matches the parameter in the subscription method (you can see how this might create some issues).

I am trying to follow clean architecture in my application using this diagram:

enter image description here

Problem:

What's starting to occur is that my manager class is getting bloated with a lot of subscription events, and my Client class is also getting bloated with implementations.

How would I go about reducing bloat in these classes? I have a few thoughts:

  • Implement a factory pattern on the events, but then how should I handle threading? For example some events share similar traits like loading a user but need to be subscribed to different subscription methods in the current application I've built. I would like to be reusing classes instead of creating event classes that duplicate code
  • Create a Service class that handles these events dynamically?
  • Create a presentation layer between controller and manager?

A diagram showing what I mean by presentation layer:

enter image description here

  • Implement Promises?

Would appreciate any thoughts on how to best tackle this.

\$\endgroup\$
6
  • \$\begingroup\$ Welcome to CR. Could you please post the rest of your code? There's an alarming amount of 'example' code in here; we don't review example code, and it's off-topic, which is a shame because this has the potential to be a really good question! \$\endgroup\$
    – Dan
    Aug 18, 2015 at 10:42
  • \$\begingroup\$ I'm afraid this question does not match what this site is about. Code Review is about improving existing, working code. The example code that you have posted is not reviewable in this form because it leaves us guessing at your intentions. Unlike Stack Overflow, Code Review needs to look at concrete code in a real context. Please see Why is hypothetical example code off-topic for CR? \$\endgroup\$
    – Vogel612
    Aug 18, 2015 at 10:43
  • \$\begingroup\$ I can update the code, I thought this would be enough to understand the structure of the problem. Very well... \$\endgroup\$
    – AndyRoid
    Aug 18, 2015 at 10:47
  • \$\begingroup\$ I updated the question. \$\endgroup\$
    – AndyRoid
    Aug 18, 2015 at 11:14
  • \$\begingroup\$ I would appreciate input on this, since I did update the question otherwise I think ill just delete this. \$\endgroup\$
    – AndyRoid
    Aug 18, 2015 at 23:16

1 Answer 1

1
\$\begingroup\$

To make my point I have to reach back a little bit. I suggest to read this all or nothing.

I currently came from a project where they used an "event bus"-like mechanism. It was used in the context of the web ui framework Wicket.

Wicket itself provides an event handling. In the special case of Wicket they had to solve some problems. The Wicket internal event handling was based on defining an event and provide it to an event sink. This sink was an ui component in the wicket ui component tree and there was no need to use the root. Furthermore you have to define in which direction the event should be sent: upwards the hierarchy or downwards. So with this mechanism some parts of the hierarchy may not be reachable with only ONE event. So often there were more than two events involved to achieve the wished goal (in their case to update the ui component). In one case they had defined a cycle by making two receivers also senders passing event to each other forever.

Then they came up with the idea of a central instance responsible for registering as an event sender and registering as an event receiver. This is nearly exactly the szenario you have. Honestly, this is a much better approach than the original wicket event handling. And many sites write recommendations on that and all in different contexts.

But there are also some problems remaining. For example if an object registers as a sender of an event that already was registered you have to decide wether to allow it or not. Will a change of a sender or an additional sender affect the receivers? One other thing is you have to keep your events semantically unique. How do you ensure not to have two events (accidentially differently named or typed) that mean the same. Or vice versa: One event triggers receivers that accidentially listen to this event? This nearly always happens if you have abstract/generic algorithms that fire events like "Update". You have to ensure that your events remain semantically concrete. And because you can only identify an event by its type the information who sends the event cannot be evaluated usefully by the receiver even the event contains the sender. The receiver cannot distinguish between different senders of the same event. This information cannot be reconstructed.

Here is the thing: An event bus should replace the observer-pattern but it doesn't. An observer-pattern decouples two objects in one direction. The receivers were nearby when they registered themselves to the sender. An event bus decouples two objects bidirectional. The receiver is now coupled to the event type. Because of that and event types should be semantically unique the receiver is implicitly coupled to the type of the sender. So the receiver is not able to distinguish between different senders of the same type anymore not as the observer-pattern. And if abstraction and inheritence is involved you can easily crash your event bus.

I don't say: Do not use an event bus. But I strongly recommend to prefer the standard observer pattern. Everything else will lead to things you have to memorize additionally in every day you use this. You always have to be aware of the restrictions of such an event bus. The observer-pattern really has no disadvantages in its concept. Maybe you have ONE disadvantage within concrete programming languages: effort to write "a lot of" code. Some mainstream developers also address that you have to think about how to reach the sender within the object structure to register as a receiver. Yes, you have addtional effort to think about the composition of your objects. But that you always should do. Why are you thinking about clean architecture?

By using the observer pattern you maybe have to write more code but it is not producing ANY PROBLEMS or has ANY RESTRCICTIONS you have to be aware of when apllying it correctly. In implementation deviations from this concept you may write less code but you definitly produce problems. This has something to do with irreducable complexity. No I am not a creationist. You have to think about it like a compression algorithm. An observer pattern is a lossless compression of a modelling problem. An event bus is a lossy compression of the same modelling problem (at least in OO). And because it is a lossy compression must be aware of the errors.

The rethorical question is: If you bring up clean architecture as the base and the effort of design why then using a "lazy" implementation of the observer pattern?

\$\endgroup\$
3
  • \$\begingroup\$ So starting 2 months ago, I started using a newer pattern which I had not worked with previously. The model-view-presenter pattern. I then divided each mvp by feature type. I was able to define out custom callbacks as well just like in Objective-C where you can define blocks. Although I have to write more code, there is no event bus and you can clearly understand what is happening! At some point in the future I want to look at RxJava / RxAndroid which utilizes the Observer pattern like you are mentioning. \$\endgroup\$
    – AndyRoid
    Dec 19, 2015 at 23:01
  • \$\begingroup\$ I looked at RxJava and I found it very verbose, counter intuitve and "heavy" but the core seems to me the application of the standard observer pattern. So I won't reject it as a proper replacement. But for the sake of simplicity I still prefer the standarrd way. In RxJava you have a lot of addditional functionality. The most critical one I found was the "blocking" observer functionality. You should really think about that when you introduce it in your code. I admit there may be applications but they were rare and maybe it should solved differently. \$\endgroup\$
    – oopexpert
    Dec 21, 2015 at 9:29
  • \$\begingroup\$ Long answer, appreciate your effort and verbosity 👍 What about some headings and highlighting of arguments you mentioned? Would promote the key points and ease reading 😉 \$\endgroup\$
    – hc_dev
    Mar 10, 2021 at 22:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.