Design Patterns

Good software is always a well-designed software. Software engineering takes good thinking, reviewing past solutions and paying attention to the difficulties that we had to go through when making changes, refactoring, testing our solutions.
But as we accumulate experience, we inevitably ask ourselves: do we need to reinvent the design all the time?
No, of course – thousands of developers had been thinking about the same repeat challenges, and this is how the Design Patterns were born.

In 1994 a book called Design Patterns: Elements of Reusable Object-Oriented Software was published. It was written by the Gang of Four: Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides and they described 23 classic software design patterns.

Every good software engineer should acquaint himself with the most frequently used ones and the motivation behind them.
These are some of the patterns:

  • Singleton – an object that can be instantialted only once and provides global access to this instance
  • Decorator – a functionality wrapper that preserves the same interface
  • Command – encapsulate data with action and pass it along
  • Facade – an object that hides the complexity of using another object, library or a system
  • Factory – an interface to object creation that lets subclasses decide which object implementation will be created; an example would be Spring’s DataSourceFactory
  • Observer – define a subscription mechanism for objects (observers) interested in your events
  • Composite – treat a set of objects and each separate object via the same interface; used typically as a tree of objects – like GUI widgets

Let’s take a closer look at the Decorator.

Decorator

This pattern combines other component’s functionality in a wrapper, while reserving that component’s interface.
We see this pattern often in our life, for example decorators in Python are exactly that.

@func2
def func(): pass

which is roughly equivalent to

def func(): pass
func = func2(func)

This pattern allows you to do your own per-processing and post-processing and calling func() in between.
The benefit is that the wrapper is decoupled from the child and has no concern about how exactly the child function is implemented.
For example, you may want to do some logging, check permissions, encrypt data in the wrapper – before passing it to the child.
And after calling a child functoin you may want to do some logging again and encrypt the data before returning it to the caller.
Note that we don’t have to touch the child function for that.

For example we can write a decorator that checks if the current user has “editor” permission before we proceed with the update_db_row() call:

def editor_level_required(func):
    @wraps(func)
    def decorated_view(*args, **kwargs):
        if current_user.is_readonly():
            current_app.logger.info(f'{request.full_path} forbidden, attempt from IP={request.remote_addr}, user={current_user.username}')
            return abort(403)
        return func(*args, **kwargs) # call update_db_row() below
    return decorated_view


@editor_level_required
def update_db_row(id):
    # write to DB
	...

In Java we see this pattern used in Servlet Filters, since Java Servlet specification version 2.3 (see https://www.oracle.com/technetwork/java/filters-137243.html)


Command pattern

This pattern is used to encapsulate data and action together, the data is typically passed to the constructor of the Command object, and the action to be performed meets the same API regardless of where it will be performed.
Benefits? You can serialize the Command, store it or send it over the wire and have it executed, replayed or rolled back. In fact, the ubiquitous Undo functionality uses Command pattern.

We also see this design pattern everywhere all around us in life:

  • regardless of your E-mail client, you know how to send email – you click Send button. The Command Pattern behind it is: Data = {from, to, subject, message body}, action = Send
  • regardless of a restaurant, your place lunch order with a waitress in the same manner. The Command Pattern behind it is: Data = {1 spring salad, 1 sandwich, 1 glass of wine}, action = Order
  • at the gas station attendant fills up the gas for you. The Command Pattern behind it is: Data = {type: Diesel, volume: 40L}, action = Fill

Let’s look at a code example.

For example, you have an object that needs to be persisted, and you don’t want to know or care where exactly it will be stored – in file system, Oracle DB, or AWS S3.

Your command will look like this:

public abstract class StoreCommand {

    protected Data d;

    public StoreCommand(Data d){
        this.d = d;
    }

    abstract void storeData();

}

And your app may want to storeData() in a variaty of destinations – the list may change in the future, but for now just three:

public class Main {

    public static void main(String[] args) {        
        Data d = new Data("January", 0.80);

        StoreCommand fscommand = new StoreCommandFileSystem(d);
        StoreCommand s3command = new StoreCommandS3(d);
        StoreCommand dbcommand = new StoreCommandRdbms(d);

        fscommand.storeData();
        s3command.storeData();
        dbcommand.storeData();
    }

}

The code looks very clean as all we need to know is that we create a command, call storeData() and the implementation does the rest.

  • The command would be implemented for the 3 destinations like this:
public class StoreCommandFileSystem extends StoreCommand {

    public StoreCommandFileSystem(Data d){
        super(d);
    }

    void storeData() {
        System.out.println(" FS -> Writing data " + d.toString() + " to file...");
    }

}
public class StoreCommandRdbms extends StoreCommand {

    public StoreCommandRdbms(Data d){
        super(d);
    }

    void storeData() {
        System.out.println(" RDBMS -> Inserting " + d.toString() + " row...");
    }

}
public class StoreCommandS3 extends StoreCommand {

    public StoreCommandS3(Data d){
        super(d);
    }

    void storeData() {
        System.out.println(" AWS -> Storing data " + d.toString() + " in S3...");
    }

}
// This could be our data object:
public class Data {

    private String s1;
    private double value;

    public Data(String _s1, double _value){
        this.s1 = _s1;
        this.value = _value;
    }

    @Override
    public String toString() {
        return String.format("(%s, %f)", s1, value);
    }

}

And as you execute the app code here’s the output:

 FS -> Writing data (January, 0.800000) to file...
 AWS -> Storing data (January, 0.800000) in S3...
 RDBMS -> Inserting (January, 0.800000) row...