Compartmentalizing Code

Aaron Harris
8 min readFeb 12, 2020

A geek-fiction story about Separation of Concern in UI centric systems.

Forgive me if this smells a bit like Android — I spend a lot of time there. However, these ideas should be applicable anywhere.

Today we’re talking about encapsulating your views. Just like you’d encapsulate classes by placing them in packages and privatizing or protecting members — you can (and should) do the same for Views.

We often see code where all the UI and logic is shoved up into an Activity or RootViewController, then later we discover we want to reuse some of those parts or maybe extract some logic for the sake of testing, or maybe just break things down so we can understand the code better. Whatever the reason (there are many), encapsulation is your friend.

Let’s say all the blue boxes are Fragments or custom Views.

What we want to see in application design vs. what we usually see in application design

Above is a silly example of what you might see in a very simple Application. Whatever, don’t judge — it gets the point across.

Let’s say all the blue boxes are Fragments or custom Views.

A good (subjective) application design would suggest:

  • (A) Sibling elements are unaware of each other.
  • (B) Siblings can react based on events from other siblings.
  • (C) A parent element in this tree should have very little knowledge of its children’s implementation.
  • (D) A parent should consider dispatching events to its children
  • (E) A Child’s reliance on a parent should be limited to child creation.
  • (F) Elements should be self-contained / reusable / pluggable (smaller).

Why? More on that later…

Often what we see in applications — due to short deadlines, or rapid growth — is all the views and view logic shoved into an Activity or RootViewController because it was quick and easy at the time.

What did that rapid growth do to our good (subjective) application design?

  • (A) Fail — Elements are in the same Activity and are aware and communicate directly which causes rigid coupling and difficulty extracting later.
  • (B) Success — Siblings can react.
  • (C) Fail — The parent (Activity/RootViewController) is the owner of the implementation details for its children. Too much codependence, snip that umbilical cord.
  • (D) Success— No dispatching is needed because the Activity does everything. This is a success because the events are handled, however it’s all one big mess and good luck separating it later.
  • (E) Fail — Parent and child are inseparable.
  • (F) Fail — All clumped together.

The essential parts necessary to get our application working were a success, unfortunately the parts that lead to future maintainability, ease of understanding, and general engineery pleasure were failures… Says me.

So why should we focus on these if they’re not essential to get our application working?

Okay, now it’s time to answer the Why’s…

(A) Sibling elements are unaware of each other.

We’re talking about coupling. Tight coupling between two parts requires both parts to be refactored together. Loose coupling allows one part to refactor without concerning the other. This is a simple description but good enough for this discussion.

In the diagram up top, take [UserProfile] and [ContentStats]. It doesn’t make sense to refactor one when the other changes. So we’d want these two to be completely decoupled (unrelated) or loosely coupled such that they have a means to communicate but unaware of each other.

Typically what we see is a button on [UserProfile] that would trigger some change on [ContentStats]. That is tight coupling because [UserProfile] now tightly depends on a link to [ContentStats] as a way of accomplishing its button-click task.

What else is there? See (B).

(B) Siblings can react based on events from other siblings.

As we discussed in (A) we can accomplish this “react” with a tight link from

Class MyDetailActivity:
contentStats = findView( content_stats )
userProfile = findView( user_profile )
userProfileButton = userProfile.findView( button )
userProfileButton.onClick( v -> contentStats.changeColor(color) )

The above approach forces ContentStats and UserProfile to expose their implementation. Later when this implementation changes (and it will), we are forced to refactor this Activity as well as any other activity that composes the view.

An alternative approach that is loosely coupled would be:

Class UserProfile:
button.onClick( v -> myEventSystem.fireEvent(new MyEvent()) )
Class ContentStats:
myEventSystem.subscribeToEvent( evt -> changeColor(evt.color) )
private void changeColor( int color ) { ... }

Now UserProfile and ContentStats define their own logic and requirements, self-maintaining and nothing is exposed. UserProfile only knows that its job is to fire “MyEvent” and it doesn’t care who’s listening.

ContentStats’s job is to respond to “MyEvent” and it doesn’t care who fired it. We can now plug ContentStats into any other place in our app with no refactoring. If that place in the app doesn’t fire a MyEvent, no harm done. If it does, the code pick it up.

So now we’re focusing on requirements for each component and not tight coupling and pains of refactoring.

(C) A parent element in this tree should have very little knowledge of its children’s implementation.

I sort of answered this in (B). Same as siblings should be decoupled, so should parent and child. However a parent is responsible for providing the data a view needs. So top down communication is fine. A child may demand food from its parent in order to exist. However, a parent should not demand food from the child. That’s really not cool.

(D) A parent should consider dispatching events to its children

For example, often we see a parent subscribe to an event and then tell its child views what to do. If you’re a parent with one or two kids thats fine. Imagine if you have 37 kids, can you tell them all what to do?

Your kids are getting more mature, let them make their own decisions. If you force the parent to make all the decisions, that doesn’t scale. Another benefit of letting a child take care of its own decisions is that logic goes with the component when its reused…

(E) A Child’s reliance on a parent should be limited to child creation.

State

DO NOT:

ContentViewPager:
startPageNum = 0
constructor( context )
myMainActivity = (MyMainActivity)getContext()
startPageNum = myMainActivity.getStartPageNumber()

This forces tight coupling (dependence) on the specific implementation details of MyMainActivity. It means reusing ContentViewPager in another activity is impossible unless we factor-out the getStartPageNumber into an interface. What a mess.

DO:

ContentViewPager:
startPageNum = 0
constructor( context )
setVisibility( gone )
public void init( startPageNum )
this.startPageNum = startPageNum
setVisibility( visible )

No reliance on a specific Activity. No casting. No bothering with an Activity’s implementation when reusing this code elsewhere.

Even better, by not relying on the Activity, we can now nest this View under another View. Its position in the view tree is no longer tied to the Activity, we can place it wherever makes sense.

Another advantage to this pattern is persisting state. Getting the startPageNumber dynamically means that number could change — we should not care about the implementation details about how the parent got that startPageNumber, all we care about is what we should start with.

Less concern = better code.

In the spirit of less concern. Taking data from the parent during initialization is better for sure. However, it’s still a reliance on the parent. Sometimes this is a coupling you want, depending on what the parent is naturally concerned with. There are even better ways to obtain this data — such as SavedInstanceState abstractions, or ContextualSharedPreferences, but thats probably too deep for now.

Communicating

We often see a child view talking back to its parent. That’s not how I was raised. For example, a custom View casting its context to an Activity so it can start another activity. Or worse, the view casting the context to a specific Activity implementation for more tight coupling interaction.

ContentViewPager:
myMainActivity = (MyMainActivity)getContext()
onPageChange( n -> myMainActivity.notifyPageChanged(n) )MyMainActivity:
public void notifyPageChanged( n )
userComments = findView( user_comments )
userComments.notifyPageChanged(n)
UserComments:
public void notifyPageChanged( n )
// update my content

That’s a lot of ugly wiring and a lot to refactor when reusing [UserComments]. We are forced to expose the internals of ContentViewPager and UserComments, we’re also forcing MyMainActivity to be concerned any time it changes.

When we reuse these parts in another activity, we must then also concern that activity. It’s a mess. Instead, same pattern we’ve seen before in (B) above. A parent and child should communicate as independent elements just like siblings do.

Class ContentViewPager:
onPageChange( n -> myEventSystem.fireMyEvent(new MyEvent(n)) )
Class UserComments:
myEventSystem.subscribeToEvent( evt -> notifyPageChanged(evt.n) )
private void notifyPageChanged( n )
// update my content

Notice how UserComments.notifyPageChanged is now encapsulated — hiding implementation is huge.

Notice how UserComments and ContentViewPager do not know about each other? Huge.

Notice there is no mention of any Activity, let alone MyMainActivity — Huge.

(F) Elements should be self-contained / reusable / pluggable (smaller)

As the complexity of your app grows, smaller composeable parts scale.

  • Easier to understand at a glance.
  • Easier to protect with good contracts (good API and Docs and Lints)
  • Easier to enforce contracts with Tests
  • Stable because they change less often. If your entire app was written in one class and everyone on the team was working in that class — good luck. However, if its broken into smaller parts, many parts remain unchanged as they serve their purpose well.
  • Reuse leads to hardening of code via more real-world testing.

Open Questions

If siblings don’t know about each other, how do they get the same myEventSystem?

Good eye! Many won’t catch that. In my examples I’m using Dependency Injection. However that is not necessary. The requirements are that you can obtain the same instance in two different places within the code. An old fashioned Singleton can do this (but that has issues of its own). I recommend reading up on Dependency Injection (DI), or Service Locators, or Inversion of Control (IoC).

If that sounds daunting, then let me say this. For now, start with a simple Singleton that is a factory that provides a MyEventSystem by context. That will get you started. You will outgrow this but it’s fine to start and it lets you declutter your code.

public static class MyEventSystemFactory {
private static final WeakHashMap<Context, MyEventSystem> cache ...
@MainThread
public static MyEventSystem get(@NonNull Context context) {
MyEventSystem out = cache.get(context);
if ( out == null ) {
out = new MyEventSystem();
cache.put(context, out);
}
return out;
}
}class MyCustomView:
private final @NonNull MyEventSystem myEventSystem;
public constructor( context )
myEventSystem = MyEventSystemFactory.get(context)
protected void onAttach()
myEventSystem.subscribe( ... )
protected void onDetatch()
myEventSystem.unsubscribe( ... )

The code above will NO DOUBT receive scrutiny.

“You shouldn’t use a WeakHashMap as a cache” — I know I know. etc etc.

The point is to get you started. This factory can (and should) be upgraded at any time in the future with little cost, but in the mean time you can enjoy the fruits.

I’ll try to post a GitHub project that resembles some real implementation later.

However, if you’re an Android Developer, take a look at Dagger (common) or Fuel (My DI framework). Read this: Scope — Dependency Injection.

What is this fireEvent & subscribeToEvent stuff?

Not EventBus. It’s very easy to roll your own, there’s also plenty of PubSub models out there. The key ingredient is managed singletons (dependency injection) to make sure each part is obtaining the right event manager.

Summary / Hopes / Dreams

First and foremost, really care about separation of concern and everything will fall into place — we wall want it, we just don’t always know how to achieve it. Hopefully this post helped provide some tools.

Want more?

Read: Scope — Dependency Injection

--

--