The Context Switcher Pattern

What is your relationship to encapsulation? Do you belong to the group who always start with setting all of the class's internal attributes to private, but who often end up needing to add getters because they couldn’t see a better solution? Or perhaps you belong to the group that has given up on encapsulation and who let their IDEs generate all the getters when the class is created? Maybe you belong to the third group who do everything in their power to ensure they maintain encapsulation; even going so far as to use instrumentation “magic” or code generation (or possibly both) to avoid adding those unwanted getters.

Most programmers agree that encapsulation is a good thing. It allows you to protect internal representation and control which methods are exposed to the outside world. If, for example, you have created the class Car, you have created something that you can relate to in a natural way which will make the code more readable and understandable, easier to work with and become a natural place to put all behavior that is related to the concept car. All related code will be gathered in one place which is an important principle in programming and helps to prevent unwanted dependencies that otherwise can easily occur when behavior is spread out in various places in the code.

Have you ever wondered in what situations you are forced to violate encapsulation? Let us say that you create the class Car and add a couple of nice methods that are needed in the business layer, for example isBig(). You also need to add the getter getName() but you can't see what harm that could cause. For the first it returns an immutable type (String) that can't change the inner state of Car via that reference. And second, you don’t think anyone will do anything else than reading it. What can happen is that code outside Car operates on name in other ways than just reading it. If that happens, it is behaviour, and moving behaviour out from the object is a bad thing and is not what object orientation is all about. Moving code outside the car can also lead to code duplication which is not what we want to happen.

You think you have met those two criteria and it makes you feel both happy and proud. All other attributes, such as primary key to the car table in the database, are still set as private which is a good thing since these are details that the business layer does not care about. But it happens that someone asks you to add an export function for an old banking system. The file that is exported should follow a text format with fixed positions where every position holds an attribute of a car.

This is the moment where it happens - you are forced to violate encapsulation! Why do you need to do that? Because you have switched context. In the new context, we are suddenly not at all interested in the methods used in the business layer, such as isBig(), but on the other hand have urgent needs to access the internal class representation. We swallow our pride and add a couple of getters who we then use in our class CarBankExporter. We have now introduced the risk that behaviour will move outside Car and at the same time managed to pollute the cars API. Does this feels familiar? The next question is, is there any good solution to this problem?

What if we could create the instance car of the type Car and then context-switch this instance to another class instance with preserved internal representation, but with access to a new set of methods, tailor-made for our new context (export cars to a file)? Or if we could create an instance of the new "handle export" class and then context-switch to the version of Car to be used in the business layer. This helps us maintain the object oriented approach where we move behavior into the object rather than as in the case CarBankExporter where we did the opposite by moving the object to the behavior.

The latter is an example of procedural programming, and corresponds to calling a function with a Car struct in C. The solution to our problems is called context switching and is handled by the pattern Context Switcher.

The best way to explain something is often by example. Assume that we have our class Car, which is used in our business layer, and that we both need to export cars to file and store cars in a database. Let's take a look how the code for such a scenario could look like in Java (the source is hosted at GitHub), class Main:
package nu.tengstrand.contextswitcher;

import nu.tengstrand.contextswitcher.car.CarFactory;
import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

public class Main {

    /**
     * This is an example of the pattern Context Switcher.
     *
     * Author: Joakim Tengstrand, august 2011.
     */
    public static void main(String[] args) {
        // 1. Create an instance of Car and run some business logic.
        //    Only the method isBig(), that think cars >= 400 cm are big,
        //    is exposed from this context.
        Car car = CarFactory.createCar(479, "Volvo");
        System.out.println("The car: " + car);
        printIsBig(car);

        // 2. Switch context to "car in database" and save to database.
        //    Only the method save() is exposed from this context.
        DbPersister dbPersister = new DbPersister();
        CarInDb carInDb = car.asCarInDb(dbPersister);
        carInDb.save();

        // 3. Change context to "as row in file" and append to file.
        //    Only the method appendToFile() is exposed from this context.
        FileWriter fileWriter = new FileWriter("Car.txt");
        CarAsRowInFile carToBeStoredInFile = carInDb.asRowInFile();
        carToBeStoredInFile.appendToFile(fileWriter);

        // 4. Read a car from file and change context to "business layer".
        String rowInFile = new FileReader("Car.txt").readNextRowFromFile();
        CarAsRowInFile carFromFile = CarFactory.createCarAsRowInFile(rowInFile);
        System.out.println("Row in file context: " + carFromFile);
        Car businessLayerCarFromFile = carFromFile.asCar();
        System.out.println("Business layer context: " + businessLayerCarFromFile);
        printIsBig(businessLayerCarFromFile);
    }

    private static void printIsBig(Car car) {
        String not = car.isBig() ? "" : "not ";
        System.out.println("This car is " + not + "big!");
    }
}
The output looks like this:
The car: Car{lengthInCentimeters=479, name=Volvo}
This car is big!
'CarInDb{lengthInCentimeters=479, name=Volvo, primaryKey=12}' was saved to the database!
'0479,Volvo' was appended to file 'Car.txt'
Row in file context: CarAsRowInFile{"0384,Fiat"}
Business layer context: Car{lengthInCentimeters=384, name=Fiat}
This car is not big!
Our three classes now expose only the methods that are meaningful depending on what context they are in and we haven’t violated encapsulation. Voilà! Problem solved!

How to use the pattern

For this pattern to be meaningful, it should be applied to an object-oriented language. Java is used in this example but any other object oriented language will do. It should also have support for the keywords private (or similar) to ensure encapsulation.

I have tried to keep the sample code, available for download here, as simple and readable as possible. It can sometimes miss validations, annotations as @Override and the like. We will concentrate on the class Car. The other two representations CarInDb and CarAsRowInFile is created using the same pattern.

A note about the example. Using a factory with static methods, works well in certain situations. However, in larger enterprise systems, it is likely you will want to use dependency injection to provide the code with references to factories or repositories which I will write about in a later blog post.

You should find the following packages and classes:


The UML diagram looks like this:


Start by creating the class CarInternals:
package nu.tengstrand.contextswitcher.car;

/**
 * Put the internal representation of a car, that is shared between
 * different representations, in this class!
 */
public class CarInternals {
    // We don't want to lose track of the PK when switching context
    // so we need to put it here together with the other shared attributes.
    public Integer primaryKey;
    public int lengthInCentimeters;
    public String name;

    public CarInternals(int lengthInCentimeters, String name) {
        if (lengthInCentimeters < 0 || lengthInCentimeters > 999) {
            throw new IllegalArgumentException("Illegal length: " + lengthInCentimeters);
        }
        this.lengthInCentimeters = lengthInCentimeters;
        this.name = name;
    }

    public String toString() {
        return "lengthInCentimeters=" + lengthInCentimeters + ", name=" + name;
    }
}
This is where you store all the attributes shared by the three different car representations. Remember to set them as public. It may feel a bit strange, since our goal is to protect our internal representation, but stay cool the internal representation will not be exposed!
Create an ordinary public constructor in a way that you would, if it was part of the class Car without using this pattern. Add the method toString where you list the attributes.

Create the class CarContextSwitcher:
package nu.tengstrand.contextswitcher.car;

import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

import java.util.HashMap;
import java.util.Map;

/**
 * This class is responsible for switching between cars that are tailor-made for a specific context.
 */
public class CarContextSwitcher {
    private final CarInternals internals;

    // Context swithable car representations
    private Car car;
    private CarAsRowInFile carAsRowInFile;
    private Map<DbPersister,CarInDb> carInDbs = new HashMap<DbPersister,CarInDb>();

    public CarContextSwitcher(CarInternals carInternals) {
        internals = carInternals;
    }

    public Car asCar() {
        if (car == null) {
            car = new Car(internals, this);
        }
        return car;
    }

    /**
     * The implementation of DbPersister may vary as we have to consider.
     */
    public CarInDb asCarInDb(DbPersister dbPersister) {
        if (carInDbs.containsKey(dbPersister)) {
            return carInDbs.get(dbPersister);
        }
        CarInDb carInDb = new CarInDb(internals, this, dbPersister);
        carInDbs.put(dbPersister, carInDb);

        return carInDb;
    }

    public CarAsRowInFile asRowInFile() {
        if (carAsRowInFile == null) {
            carAsRowInFile = new CarAsRowInFile(internals, this);
        }
        return carAsRowInFile;
    }

    public String toString() {
        return "CarContextSwitcher{internals=" + internals + "}";
    }
}

Add the attribute internals and make sure it is set to private final. To set it as final will ensure that it is assigned by the constructor and will not be changed. Add the attributes car, carInDb and carAsRowInFile. In the case carInDb we send in dbPersister which can vary, so we need to handle that case using a Map. Add the constructor and assign internals. Add method asCar where you return a Car.

Continue with the class Car:
package nu.tengstrand.contextswitcher.car.business;

import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.CarContextSwitcher;
import nu.tengstrand.contextswitcher.car.CarInternals;
import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

/**
 * Represents a car as the business layer wants to see it.
 */
public class Car {
    private final CarInternals internals;
    private final CarContextSwitcher contextSwitcher;

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the CarFactory!
     */
    public Car(CarInternals carInternals, CarContextSwitcher carContextSwitcher) {
        internals = carInternals;
        contextSwitcher = carContextSwitcher;
    }

    public CarInDb asCarInDb(DbPersister dbPersister) {
        return contextSwitcher.asCarInDb(dbPersister);
    }

    public CarAsRowInFile asRowInFile(FileWriter fileWriter) {
        return contextSwitcher.asRowInFile();
    }

    /**
     * Here is an example of business logic that operates on the internal representation.
     */
    public boolean isBig() {
        return internals.lengthInCentimeters >= 400;
    }

    public String toString() {
        return "Car{" + internals + "}";
    }
}
Here we have our pure business object.
Add the attributes internals and contextSwitcher and make sure they are set to private final.
Add the public constructor with the comment DO NOT USE with the signature shown in the example. Now add "as" methods of the other two context-switchable classes, in this case asCarInDb and asRowInFile. Then add your business methods such as isBig(). This is an ordinary class where the only thing you need to think of is to add this constructor!

Create the class CarFactory:
package nu.tengstrand.contextswitcher.car;

import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

/**
 * Responsible for creating our "context dependent" cars.
 */
public class CarFactory {

    public static Car createCar(int lengthInCentimeter, String name) {
        CarInternals carInternals = new CarInternals(lengthInCentimeter, name);
        return new CarContextSwitcher(carInternals).asCar();
    }

    public static CarInDb createCarInDb(int lengthInCentimeter, String name, DbPersister dbPersister) {
        CarInternals carInternals = new CarInternals(lengthInCentimeter, name);
        return new CarContextSwitcher(carInternals).asCarInDb(dbPersister);
    }

    public static CarAsRowInFile createCarAsRowInFile(String rowInFile) {
        return new CarAsRowInFile(rowInFile);
    }
}
This is the class responsible for creating instances of our context-switchable car classes. Make sure all the create methods are set as public static. The method signatures must include all parameters CarInternals needs plus any other parameters that your class needs. Car is an example that only wants a CarInternals while eg CarInDb also wants a CarDbPersister.

The classes Car and CarInDb is created by context-switch them via CarContextSwitcher. This works well since we can easily create a CarInternals. In the case CarAsRowInFile we don’t want to create a CarInternals due to this responsibility lies in the class itself, and we therefore use the constructor directly.

Now you can continue with adding more car classes specialized for a specific context, and if you follow the example, this means you also need to create CarInDb, CarAsRowInFile and Main!

To make the example complete, here are the other two representations of Car, we begin with CarInDb:
package nu.tengstrand.contextswitcher.car.persistence;

import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.CarContextSwitcher;
import nu.tengstrand.contextswitcher.car.CarInternals;
import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;

/**
 * Represents a car as a record in a table in the database.
 */
public class CarInDb {
    private final CarInternals internals;
    private final CarContextSwitcher contextSwitcher;
    private final DbPersister dbPersister;

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the CarFactory!
     */
    public CarInDb(CarInternals carInternals, CarContextSwitcher carContextSwitcher, DbPersister dbPersister) {
        internals = carInternals;
        contextSwitcher = carContextSwitcher;
        this.dbPersister = dbPersister;
    }

    public Car asCar() {
        return contextSwitcher.asCar();
    }

    public CarAsRowInFile asRowInFile() {
        return contextSwitcher.asRowInFile();
    }

    public void save() {
        internals.primaryKey = dbPersister.save(internals.primaryKey, contextSwitcher);
        System.out.println("  '" + this + "' was saved to the database!");
    }

    public String toString() {
        return "CarInDb{" + internals + ", primaryKey=" + internals.primaryKey + "}";
    }
}

And here comes the last class CarAsRowInFile:
package nu.tengstrand.contextswitcher.car.banking;

import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.CarContextSwitcher;
import nu.tengstrand.contextswitcher.car.CarInternals;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

/**
 * Represents a car in the context of reading and writing it to a file.
 * The two constructors needs to keep the two representations,
 * internals and rowInFile, in sync.
 *
 * File format, e.g "0479,Volvo":
 *   0-3 = Length in centimeters
 *   5-  = Name
 */
public class CarAsRowInFile {
    private final CarInternals internals;
    private final CarContextSwitcher contextSwitcher;

    private String rowInFile;

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the factory!
     *
     * @param rowInFile row in a text file.
     */
    public CarAsRowInFile(String rowInFile) {
        this.rowInFile = rowInFile;

        internals = createCarInternals(); // We also need to set the internal representation.
        contextSwitcher = new CarContextSwitcher(internals);
    }

    private CarInternals createCarInternals() {
        int lengthInCentimeters = Integer.parseInt(rowInFile.substring(0,4));
        String name = rowInFile.substring(5);

        return new CarInternals(lengthInCentimeters, name);
    }

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the CarFactory!
     */
    public CarAsRowInFile(CarInternals carInternals, CarContextSwitcher carContextSwitcher) {
        internals = carInternals;
        contextSwitcher = carContextSwitcher;
        setRowInFile(); // We also need to set the "row in file" representation.
    }

    private void setRowInFile() {
        String lengthCm = "000" + internals.lengthInCentimeters;
        rowInFile = lengthCm.substring(8-lengthCm.length()) + "," + internals.name;
    }

    public Car asCar() {
        return contextSwitcher.asCar();
    }

    public CarInDb asCarInDb(DbPersister dbPersister) {
        return contextSwitcher.asCarInDb(dbPersister);
    }

    public void appendToFile(FileWriter fileWriter) {
        fileWriter.appendToFile(rowInFile);
    }

    public String toString() {
        return "CarAsRowInFile{\"" + rowInFile + "\"}";
    }
}
It may be worth mentioning a detail of this class. It differs little from the other two as seen in CarFactory where the other types are sending in the entire internal representation in the parameter list, int lengthInCentimeter, String name, while CarAsRowInFile is created with the signature String rowInFile. This means that a CarAsRowInFile can be created in two ways, either by CarFactory where you send in a row from the file, which is our alternative representation of a car or via context-switching an instance of Car or CarInDb.

With these words I’m wishing you good luck with this pattern! A special thanks to James Trunk who helped me with the English text and provided constructive feedback. I would also like to thank Magnus Mickelsson, who took the time to look through the solution.

2 comments

  1. Interesting!

    An issue I see with this pattern is that it is not extensible to a third-party (since you'd need to both extend the context switcher AND Car itself to provide new context-switch-methods.

    I'd recommend having a look at the TypeClass pattern(1) and also Lenses (2)

    1: http://debasishg.blogspot.com/2010/06/scala-implicits-type-classes-here-i.html
    2: https://gist.github.com/1240480

  2. The context-switchable classes are tightly coupled which is used to avoid making unnecessary validations and format conversions when context-switching between the different representations. If there is a need to context-switch between system boundaries a looser coupling needs to be created such such as a CarAsXml or similar "string-based" format.
    Interesting links, however I need to improve my Scala skills in order to fully understand them!

Post a Comment