Software Design Patterns

Florian Rappl, Fakultät für Physik, Universität Regensburg

Software Design Patterns

Introduction to modern software architecture

software architecture

SOLID principles

Introduction

  • SOLID is a mnemonic acronym introduced in the early 2000s
  • Basically this contains the five basic principles of object-oriented programming and design
  • These principles are meant to guide programmers into better designs
  • Mastering SOLID is considered one of the most important abilities
  • Applying the techniques presented in this chapter should result in better programs

The five categories

  • SOLID is composed of
    • SRP (Single Responsibility Principle)
    • OCP (Open Closed Principle)
    • LSP (Liskov Substitution Principle)
    • ISP (Interface Segregation Principle)
    • DIP (Dependency Inversion Principle)
  • To put it into one sentence: Classes should be decoupled, reusable, closed yet extendable and only responsible for a single task

Single responsibility principle

  • This principle can be boiled down to the following:

A class should have only a single responsibility.

  • Why is that important? Reducing complexity! This reduces errors
  • Also this makes the class more pluggable
  • And finally the implementation is probably more dedicated and lightweight (plus easier to test)

Open / closed principle

Software entities... should be open for extension, but closed for modification.

  • This means that classes should be open for extension e.g. in form of inheritance
  • However, the core purpose of the class should be closed for modification
  • We are not able to modify the class in such a way, that the original purpose is no longer fulfilled
  • We have seen patterns, e.g. Strategy pattern, which embrace this

Liskov substitution principle

  • Making the closed part of OCP even stronger:

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

  • This is also stated in the design by contract philosophy
  • Basically only new methods should be introduced by subtyping
  • By replacing a method one should not change the core behavior

Interface segregation principle

Many client-specific interfaces are better than one general-purpose interface.

  • Basically we are interested in composing the right interfaces
  • We are not interested in leaving many methods unimplemented
  • As with SRP, interfaces should be as atomic as possible
  • Intention to create a barrier for preventing dependencies

Dependency inversion principle

Depend upon Abstractions. Do not depend upon concretions.

  • This is actually one of the most crucial points in OOP
  • It inverts the way someone may think about OO design by dictating that both high- and low-level objects must depend on the same abstraction
  • Depending on concrete implementations yields strong coupling
  • Changing the implementation might cause bugs in the dependencies
  • Dependency injection is a method build upon this principle

Abstraction

  • Obviously abstraction is important - most patterns introduced some
  • It helps to identify the important aspects and ignore the details
  • Supports separation of concerns by divide & conquer vertically
  • Abstractions dominate computing
    • Design Models (ER, UML etc )
    • Programming Languages (C#, C++, Java, etc.)
  • This is control abstraction, but there is also data abstraction

Why the separation?

  • We cannot deal with all aspects of a problem simultaneously
  • To conquer complexity, we need to separate issues and tasks
    • Separate functionality from efficiency
    • Separate requirements specification from design
    • Separate responsibilities
  • Today's applications involve interoperability of
    • Client/Server, Legacy system, COTS (3rd party), databases, etc.
    • Multiple programming languages (C#, C++, Java, etc.)
    • Heterogeneous hardware/OS platforms

Extending classes

  • The OCP is a very important principle
  • A class should be open for extension, but closed for modification
  • In other words, (in an ideal world...) we should never need to change existing code or classes
  • Exception: Bug-fixing and maintainance
  • All new functionality can be added by adding new subclasses and overriding methods
  • Alternatively by reusing existing code through delegation

SRP example

SRP.jpg (Image courtesy of Derick Bailey.)

Where SRP should be applied

public class ServiceStation
{
    public void OpenGate()
    {
        /* ... */
    }
 
    public void DoService(Vehicle vehicle)
    {
        /* ... */
    }
 
    public void CloseGate()
    {
        /* ... */
    }
}
public class ServiceStation {
    public void openGate() {
        /* ... */
    }
 
    public void doService(Vehicle vehicle) {
        /* ... */
    }
 
    public void closeGate() {
        /* ... */
    }
}
class ServiceStation {
    public:
    void OpenGate() {
        /* ... */
    }
 
    void DoService(Vehicle vehicle) {
        /* ... */
    }
 
    void CloseGate() {
        /* ... */
    }
}

Why SRP makes sense here

  • The class has two responsibilites:
    1. Performing the service
    2. Controlling the gate
  • However, the name suggests only one responsibility
  • Maintenance is much easier if we just deal with a set of methods that belong to the same group
  • Hence we should outsource the methods for controlling the gate
  • In order to keep the coupling at a minimum we should use an interface

With SRP in action

public interface IGateUtility
{
    void OpenGate();
    void CloseGate();
}
 
public class ServiceStationUtility : IGateUtility
{
    public void OpenGate()
    {
        /* ... */
    }
 
    public void CloseGate()
    {
        /* ... */
    }
}

public class ServiceStation
{
    IGateUtility _gateUtility;
 
    public ServiceStation(IGateUtility gateUtility)
    {
        this._gateUtility = gateUtility;
    }
    public void OpenForService()
    {
        _gateUtility.OpenGate();
    }
 
    public void DoService()
    {
        /* ... */
    }
 
    public void CloseForDay()
    {
        _gateUtility.CloseGate();
    }
}
public interface GateUtility {
    void openGate();
    void closeGate();
}
 
public class ServiceStationUtility implements GateUtility {
    public void openGate() { 
        /* ... */
    }
 
    public void closeGate() {
        /* ... */
    }
}

public class ServiceStation {
    GateUtility _gateUtility;
 
    public ServiceStation(GateUtility gateUtility) {
        this._gateUtility = gateUtility;
    }

    public void openForService() {
        _gateUtility.openGate();
    }
 
    public void doService() {
        /* ... */
    }
 
    public void closeForDay() {
        _gateUtility.closeGate();
    }
}
class GateUtility {
    public:
    virtual void OpenGate() = 0;
    virtual void CloseGate() = 0;
};
 
class ServiceStationUtility : public GateUtility {
    public:
    void OpenGate() { 
        /* ... */
    }
 
    void CloseGate() {
        /* ... */
    }
};

public class ServiceStation {
    private:
    GateUtility* _gateUtility;
 
    public:
    ServiceStation(GateUtility* gateUtility) : _gateUtility(gateUtility) {
    }

    void OpenForService() {
        _gateUtility->OpenGate();
    }
 
    void DoService() {
        /* ... */
    }
 
    void DloseForDay() {
        _gateUtility->CloseGate();
    }
};

OCP example

OCP.jpg (Image courtesy of Derick Bailey.)

Where OCP should be applied

public class MileageCalculator
{
    IEnumerable<Car> _cars;
    
    public MileageCalculator(IEnumerable<Car> cars) 
    {
        this._cars = cars; 
    }
 
    public void CalculateMileage()
    {
        foreach (var car in _cars)
        {
            if (car.Name == "Audi")
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "10M");
            else if (car.Name == "Mercedes")
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "20M");
        }
    }
}
public class MileageCalculator {
    Enumerable<Car> _cars;

    public MileageCalculator(Enumerable<Car> cars) {
        this._cars = cars; 
    }
 
    public void calculateMileage() {
        for (Car car : _cars) {
            if (car.getName() == "Audi")
                out.println("Mileage of the car " + car.getName() + " is 10M");
            else if (car.getName() == "Mercedes")
                out.println("Mileage of the car " + car.getName() + " is 20M");
        }
    }
}
class MileageCalculator {
    private:
    list<Car*>& _cars;

    public:
    MileageCalculator(list<Car*>& cars) : _cars(cars) {
    }
 
    void CalculateMileage() {
        for (auto car = _cars.begin(); car != _cars.end(); ++car) {
            if ((*car)->getName() == "Audi")
                cout << "Mileage of the car " << (*car)->getName() << " is 10M" << endl;
            else if ((*car)->getName() == "Mercedes")
                cout << "Mileage of the car " << (*car)->getName() << " is 20M" << endl;
        }
    }
};

Why OCP makes sense here

  • The class represents a typical mess for maintenance and extension
  • For two names the method seems OK, however, consider many more
  • Additionally every new name will result in changing the method
  • While the closed principle is violated, the open principle is also violated
  • The mileage is provided as a hardcoded string
  • There is also no possibility to give special cars special mileages

With OCP in action

public interface ICar 
{ 
    string Name { get; set; }
    string GetMileage();
}
 
public class Audi : ICar
{
    public string Name { get; set; }
 
    public string GetMileage()
    {
        return "10M";
    }
}
 
public class Mercedes : ICar
{
    public string Name { get; set; }
 
    public string GetMileage()
    {
        return "20M";
    }
}
 
public class CarController
{
    List<ICar> cars; 

    public CarController()
    {
        cars = new List<ICar>();
        cars.Add(new Audi());
        cars.Add(new Mercedes());
    }
 
    public string GetCarMileage(string name)
    {
        if (!cars.Any(car => car.Name == name))
            return null;

        return cars.First(car => car.Name == name).GetMileage();
    }
}

public class MileageCalculator
{
    IEnumerable<Car> _cars;
    CarController _ctrl;

    public MileageCalculator(IEnumerable<Car> cars) 
    {
        this._cars = cars; 
        this._ctrl = new CarController();
    }
 
    public void CalculateMileage()
    {
        foreach (var car in _cars)
        {
            var mileage = _ctrl.GetCarMileage(car.Name);

            if (mileage != null)
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, mileage);
        }
    }
}
public interface MyCar { 
    string getName();
    void setName(string value);
    string getMileage();
}
 
public class Audi implements MyCar {
    string name;

    public string getName() {
        return name;
    }

    public void setName(string value) {
        name = value;
    }
 
    public string getMileage() {
        return "10M";
    }
}
 
public class Mercedes implements MyCar {
    string name;

    public string getName() {
        return name;
    }

    public void setName(string value) {
        name = value;
    }
 
    public string getMileage() {
        return "20M";
    }
}
 
public class CarController {
    List<MyCar> cars; 

    public CarController() {
        cars = new ArrayList<MyCar>();
        cars.add(new Audi());
        cars.add(new Mercedes());
    }
 
    public string getCarMileage(string name) {
        for (MyCar car : cars) {
            if (car.getName() == name)
                return car.getMileage();
        }

        return null;
    }
}

public class MileageCalculator {
    Enumerable<Car> _cars;
    CarController _ctrl;

    public MileageCalculator(Enumerable<Car> cars) {
        this._cars = cars; 
        this._ctrl = new CarController();
    }
 
    public void calculateMileage() {
        for (Car car : _cars) {
            string mileage = _ctrl.getCarMileage(car.getName());

            if (mileage != null)
                out.println("Mileage of the car " + car.getName() + " is " + mileage);
        }
    }
}
class MyCar { 
    public:
    virtual string getName() = 0;
    virtual void setName(string value) = 0;
    virtual string getMileage() = 0;
};

class Audi : public MyCar {
    private:
    string name;

    public:
    string getName() { 
        return name; 
    }

    void setName(string value) {
        name = value;
    }

    string getMileage() {
        return "10M";
    }
};
 
class Mercedes : public MyCar {
    private:
    string name;

    public:
    string getName() { 
        return name; 
    }

    void setName(string value) {
        name = value;
    }

    string getMileage() {
        return "20M";
    }
};

class CarController {
    private:
    list<MyCar*> cars; 

    public:
    CarController() {
        cars.push_back(new Audi());
        cars.push_back(new Mercedes());
    }
 
    string GetCarMileage(string name) {
        for (auto car = cars.begin(); car != cars.end(); ++car) {
            if ((*car)->getName() == name)
                return (*car)->getMileage();
        }

        return "";
    }
};

class MileageCalculator {
    private:
    list<Car*>& _cars;
    CarController _ctrl;

    public:
    MileageCalculator(list<Car*>& cars) : _cars(cars) {
    }
 
    void CalculateMileage() {
        for (auto car = _cars.begin(); car != _cars.end(); ++car) {
            string mileage = _ctrl.GetCarMileage((*car)->getName());

            if (!mileage.empty())
                cout << "Mileage of the car " << (*car)->getName() << " is " << mileage << endl;
        }
    }
};

LSP example

LSP.jpg (Image courtesy of Derick Bailey.)

Where LSP should be applied

public class Apple
{
    public virtual string GetColor()
    {
        return "Red";
    }
}

public class Orange : Apple
{
    public override string GetColor()
    {
        return "Orange";
    }
}

//Possible usage:
Apple apple = new Orange();
Console.WriteLine(apple.GetColor());
public class Apple {
    public virtual string getColor() {
        return "Red";
    }
}

public class Orange extends Apple {
    @Override
    public string getColor() {
        return "Orange";
    }
}

//Possible usage:
Apple apple = new Orange();
out.println(apple.getColor());
class Apple {
    public:
    virtual string GetColor() {
        return "Red";
    }
};

class Orange : public Apple {
    public:
    virtual string GetColor() {
        return "Orange";
    }
};

//Possible usage:
Apple* apple = new Orange();
cout << apple->GetColor() << endl;

Why LSP makes sense here

  • This case appears quite often: One derives from a somehow related, but conceptually different class
  • Problem? A method that accepts an apple could then receive an orange (but some methods may not like oranges and will be surprised by the taste of this "apple" ...)
  • Here we cannot say an orange is an apple, but both are definitely fruits
  • A common base class (which will be abstract in most cases) is missing
  • Always think about related and unrelated, parallel and derived

With LSP in action

public abstract class Fruit
{
    public abstract string GetColor();
}

public class Apple : Fruit
{
    public override string GetColor()
    {
        return "Red";
    }
}

public class Orange : Fruit
{
    public override string GetColor()
    {
        return "Orange";
    }
}
public abstract class Fruit {
    public abstract string getColor();
}

public class Apple extends Fruit {
    @Override
    public string getColor() {
        return "Red";
    }
}

public class Orange extends Fruit {
    @Override
    public override string getColor() {
        return "Orange";
    }
}
class Fruit {
    public:
    virtual string GetColor() = 0;
}

class Apple : public Fruit {
    public:
    virtual string GetColor() {
        return "Red";
    }
}

class Orange : public Fruit {
    public:
    virtual string GetColor() {
        return "Orange";
    }
}

ISP example

ISP.jpg (Image courtesy of Derick Bailey.)

Where ISP should be applied

public interface IOrder
{
    void Purchase();
    void ProcessCreditCard();
}

public class OnlineOrder : IOrder
{
    public void Purchase()
    {
        //Do purchase
    }

    public void ProcessCreditCard()
    {
        //process through credit card
    }
}

public class InpersonOrder : IOrder
{
    public void Purchase()
    {
        //Do purchase
    }

    public void ProcessCreditCard()
    {
        //Not required for inperson purchase
        throw new NotImplementedException();
    }
}
public interface Orderable {
    void purchase();
    void processCreditCard();
}

public class OnlineOrder implements Orderable {
    public void purchase() {
        //Do purchase
    }

    public void processCreditCard() {
        //process through credit card
    }
}

public class InpersonOrder implements Orderable {
    public void purchase() {
        //Do purchase
    }

    public void processCreditCard() {
        //Not required for inperson purchase
        throw new UnsupportedOperationException("Not implemented.");
    }
}
class Order {
    public:
    virtual void Purchase() = 0;
    virtual void ProcessCreditCard() = 0;
};

class OnlineOrder : public Order {
    public:
    void Purchase() {
        //Do purchase
    }

    void ProcessCreditCard() {
        //process through credit card
    }
};

class InpersonOrder : public Order {
    public:
    void Purchase() {
        //Do purchase
    }

    void ProcessCreditCard() {
        //Not required for inperson purchase
        throw "Not implemented.";
    }
};

Why ISP makes sense here

  • Sometimes not-implementing a full interface makes sense
  • However, if we define the interfaces we should define them in such a way that only contains the bare methods
  • In this example the interface is definitely too broad, we need to separate it into two two interfaces
  • Sometimes it also might make sense to mark the second interface as being dependent on the first one (staging the interface by implementing other interfaces)

With ISP in action

public interface IOrder
{
    void Purchase();
}

public interface IOnlineOrder 
{
    void ProcessCreditCard();
}

public class OnlineOrder : IOrder, IOnlineOrder
{
    public void Purchase()
    {
        //Do purchase
    }

    public void ProcessCreditCard()
    {
        //process through credit card
    }
}

public class InpersonOrder : IOrder
{
    public void Purchase()
    {
        //Do purchase
    }
}
public interface Orderable {
    void purchase();
}

public interface OnlineOrderable {
    void processCreditCard();
}

public class OnlineOrder implements Orderable, OnlineOrderable {
    public void purchase() {
        //Do purchase
    }

    public void processCreditCard() {
        //process through credit card
    }
}

public class InpersonOrder implements Orderable {
    public void purchase() {
        //Do purchase
    }
}
class Orderable {
    public:
    virtual void Purchase() = 0;
};

class OnlineOrderable {
    virtual void ProcessCreditCard() = 0;
};

class OnlineOrder : public Orderable, public OnlineOrderable {
    public:
    void Purchase() {
        //Do purchase
    }

    void ProcessCreditCard() {
        //process through credit card
    }
};

class InpersonOrder : public Orderable {
    public:
    void Purchase() {
        //Do purchase
    }
};

DIP example

DIP.jpg (Image courtesy of Derick Bailey.)

Where DIP should be applied

class EventLogWriter
{
    public void Write(string message)
    {
        //Write to event log here
    }
}

class AppPoolWatcher
{
    EventLogWriter writer = null;

    public void Notify(string message)
    {
        if (writer == null)
        {
            writer = new EventLogWriter();
        }
        writer.Write(message);
    }
}
class EventLogWriter {
    public void write(string message) {
        //Write to event log here
    }
}

class AppPoolWatcher {
    EventLogWriter writer = null;

    public void notify(string message) {
        if (writer == null) {
            writer = new EventLogWriter();
        }
        writer.write(message);
    }
}
class EventLogWriter {
    public:
    void Write(string message) {
        //Write to event log here
    }
}

class AppPoolWatcher {
    private:
    EventLogWriter* writer;

    public:
    void Notify(string message) {
        if (writer == NULL) {
            writer = new EventLogWriter();
        }
        writer->Write(message);
    }
}

Why DIP makes sense here

  • The class looks quite fine already, however, it is violating DIP
  • The high level module AppPoolWatcher depends on EventLogWriter which is a concrete class and not an abstraction
  • Future requirements might break the code on this position (what about other kinds of loggers?)
  • The solution for fixing this problem is called inversion of control

With DIP in action

public interface INofificationAction
{
    public void ActOnNotification(string message);
}

class EventLogWriter : INofificationAction
{   
    public void ActOnNotification(string message)
    {
        /* ... */
    }
}

class AppPoolWatcher
{
    INofificationAction action = null;

    public AppPoolWatcher(INofificationAction concreteImplementation)
    {
        this.action = concreteImplementation;
    }

    public void Notify(string message)
    {   
        action.ActOnNotification(message);
    }
}
public interface NofificationAction {
    public void actOnNotification(string message);
}

class EventLogWriter implements NofificationAction {   
    public void actOnNotification(string message) {
        /* ... */
    }
}

class AppPoolWatcher {
    NofificationAction action = null;

    public AppPoolWatcher(NofificationAction concreteImplementation) {
        this.action = concreteImplementation;
    }

    public void notify(string message) {   
        action.actOnNotification(message);
    }
}
class NofificationAction {
    public:
    virtual void ActOnNotification(string message) = 0;
};

class EventLogWriter : NofificationAction {   
    public:
    void ActOnNotification(string message) {
        /* ... */
    }
};

class AppPoolWatcher {
    NofificationAction* action = null;

    public AppPoolWatcher(NofificationAction* concreteImplementation) : action(concreteImplementation) {
    }

    public void Notify(string message) {   
        action->ActOnNotification(message);
    }
};

Dependency Injection

  • In the last example we prepared everything for dependency injection, which might appear as
    1. Constructor injection (see last example)
    2. Method injection
    3. Property injection
  • DI is mainly for injecting the concrete implementation into a class that is using abstraction i.e. interface inside
  • Moving out the concrete implementation and providing a way for externally providing concrete implementations is the idea of DI

Strong coupling

ioc_without.png

Inversion of control

ioc_with.png

Literature

  • Riel, Arthur J (1996). Object-Oriented Design Heuristics.
  • Plauger, P.J. (1993). Programming on Purpose: Essays on Software Design.
  • Larman, Craig (2001). Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and the Unified Process.
  • Martin, Robert C. (2002). Agile Software Development, Principles, Patterns, and Practices.
  • DeMarco, Tom. (1979). Structured Analysis and System Specification.