Trade type safety for better architecture

In JavaScript it is very common to call methods on subtypes of your class hierarchy. There are no real interfaces and developers have no qualms about calling methods of objects of unknown types like Java developers do. The reason for this is type safety and understanding what SOLID principles are. The definition of type safety is when you can guarantee that no class cast exception will occur.

Today I will present you with an example of how to profit from trading explicitly typed methods for more flexibility, readability, maintainability and extendibility.

Imagine you have a class constellation like this:

simple class diagram

Here you have a class View that uses other classes like FocusManager, AccessPermitter to solve different requirements of the view. In some cases, they call up other classes like VisitorRegistry and SystemMonitor. Most of the time the classes update something that is related to the View's state. Examples are classes like SystemEventManager and GlobalMessenger.

There have 4 different categories:

  • Subject modifier: SystemEventManager, GlobalMessenger
  • Subject: View
  • Subject helper: FocusManager, AccessPermitter
  • Subject helper dependencies: VisitorRegistry, SystemMonitor

The probabilities are good that the item class count of each category will grow as soon as your application extends. The components are heavily coupled because subject modifier call methods from subject helper dependencies when something is changed that is related to its remit.

So let’s take this example case:
SystemEventManager is invoked by a class and sets on every view’s AccessPermitter the access rights to “no-access”. In this case the AccessPermitter calls up the SystemMonitor, the VisitorRegistry is called if the FocusManager has already called up a method in advance. Dependency calls can be very complex and may relate to various criteria. If the class count of each category increases then the classes are strongly coupled and method calls for each dependency grows very quickly:

each subject modifier * each subject helper * each subject helper dependency

If you keep the classes small and clean, you will reach a point when you have to add extra classes that handles your dependency call criteria behaviour. But there is a solution that makes use of the observer pattern. I call it the criteria observer pattern but I bet something similar has already been defined.

So how can we change the call structure of the given classes?
Let’s extend the View class from CriteriaHolder. CriteriaHolder consists of a property registry and a setter for a given range of properties.

public class CriteriaHolder {  
    ClassProperties properties = new ClassProperties();
    Map<CriteriaObserver, Map<Properties, Object>> observers = new HashMap<CriteriaObserver, Map<Properties, Object>>();

    public void set(Properties prop, Object value) {
        properties.set(prop, value);
        notifyObserver();
    }

    private void notifyObserver() {
        Set<CriteriaObserver> criteriaObservers = observers.keySet();
        for(CriteriaObserver observer : criteriaObservers) {
            //if all properties are fulfilled then the observer is called with true; otherwise it is false
            observer.updateConjunctionResult(calculateCriteriaConjunctionResult(observers.get(observer)));
        }
    }

    private boolean calculateCriteriaConjunctionResult(Map<Properties, Object> propertiesOfInterest) {
        boolean conjunctionResult = true;

        Set<Properties> properties = propertiesOfInterest.keySet();
        for(Properties poi : properties) {
            Object criteriaHolderProperty = this.properties.get(poi);
            Object observerProperty = propertiesOfInterest.get(poi);

            //this is the result of losing type safety
            conjunctionResult = conjunctionResult && criteriaHolderProperty.equals(observerProperty);
        }
        return conjunctionResult;
    }

    public void register(CriteriaObserver obs) {
        observers.put(obs, obs.getConjunctionProperties());
    }
}

They are updated from the subject modifier. In addition subject helper dependencies implement an interface called CriteriaObserver.

public interface CriteriaObserver {  
    void updateConjunctionResult(boolean fulfilled);
    Map<Properties, Object> getConjunctionProperties();
}

CriteriaObserver can be registered to the CriteriaHolder. There is also a method called getConjunctionProperties. Each CriteriaObserver returns a map of criteria that need to be fulfilled. The subject helper class loose logic that decides about when to call up a helper dependency.

Refactored class diagram using the observer pattern

There are new categories:

  • Subject modifier (updates CriteriaHolder properties): SystemEventManager, GlobalMessenger
  • Subject -> CriteriaHolder: View
  • Subject helper: FocusManager, AccessPermitter
  • Subject helper dependencies -> Criteria observer: VisitorRegistry, SystemMonitor

The call count improves from n^3 to n. You can optimize unneeded calls in your criteriaHolder checking when a conjunction result changes for an observer. In this case you call its update method with the the new boolean result.

Each subject modifier updates the CriteriaHolder + results of CriteriaObserver update calls.
But you gain a lot of readability. You have refactored your properties so they are related to their subject. Before this change you called upon specific subject helper methods. But in reality some subject states changed, as what the code now tells you. This behaviour was hidden beforehand.
You also profit from extendibility. It’s very probable that there will be more classes in different categories. So if you want to add a new criteria observer you can simply do it. You define the conjunction criteria in its getConjunctionProperties method. The class itself or another class registers the new CriteriaObserver class to the CriteriaHolder and you are good to go. The criteria that decides when an observer gets called up are located in the CriteriaObserver and not in the class that calls the subject helper dependency.

So if we check a concrete Java implementation we trade explicit method calls with typed parameters to a map of enum and object values. Given the crucial advantages here this price is worth paying.

Many developers know about the option of tradining complexity for type safety but I know many applications that still contain class dependencies like those presented in this example. It just feels a bit more difficult in Java than it does in JavaScript because Java is a strongly typed language. And yes by definition we don’t lose type safety. But the headline was eye catching and so I hope that you guys don't get me wrong!

References

Journerist

Read more posts by this author.