Object-oriented Programming (OOP)

Design Patterns

Patterns in software systems turn up at different abstraction levels:

  • Idioms: Usage of programming language features (depend on language)
  • Design patterns: Teams of interacting objects and classes
  • Architecture patterns: Building blocks of software architectures

Idioms

Idioms are common applications of programming language features and usually depend on a specific programming language. Often they are a workaround for missing language features. Some idioms are thus not necessary with certain languages or may become obsolete with new language features.

Idioms are also referred to as "low-level design patterns" (also called "implementation patterns"). Experienced developers usually have a rich repertoire of idioms.

Idiom - Execute Around Method Pattern

Sometimes boilerplate code must be executed before and after an operation. (e.g. database operations within a transaction). Boilerplate code refers to code that is always the same, only the operation is variable. It decreases the readability by distracting from the operation and it does not adhere to DRY principle.

In the following example, there is a lot of boilerplate code that is always going to be the same when an operation has to be executed within a database transaction.

Transaction = tr. persitenceManager.createTransaction();
tr.begin();
try {
  // operation
  tr.commit();
} catch (Exception e) {
  tr.rollback();
} finally {
  // cleanup
}

The boilerplate code can be factorized and the operation can be specified as a lambda expression:

@FunctionalInterface
public interface TransactionalOperation {
  void execute();
}
public static void withTransaction(TransactionalOperation op) {
  Transaction tr = persistenceManager.createTransaction();
  tr.begin();
  try {
    op.execute() // the actual operation is performed based on the lambda
    tr.commit();
  } catch (Exception e) {
    tr.rollback();
  } finally {
    // cleanup
  }
}  
// client can invoke it and specify a lambda
withTransaction( () -> { /* operation */ })

Idiom - Conditional Compilation

Conditional compilation as in C++ is not possible in Java. It can be used to prevent code that is required during development / testing from being executed in production. In Java the optimization of the compilers can be leveraged to achieve this:

public class Debug {
  public static final boolean isEnabled = true;
}
public class Log {
  public static void debug(String s, Throwable t) { ... }
}
// ...
if (Debug.isEnabled) Log.debug("entered xyz", new Throwable());

Idiom - Bouncer

The bouncer idiom can be used to check a condition (e.g. if true, do nothing and else throw exception).

void checkNotNull(String name, Object obj) {
  if (obj == null)
    throw new IllegalArgumentException(name + " is null");
}
// ...
public void doStuff(String name, Object value) {
  checkNotNull("name", name);
  checkNotNull("value", value);
  // do stuff
}

Object Oriented Design

Object-oriented concepts are a powerful design tool but it requires appropriate skills to leverage this tool. Good design is hardly feasible on paper (i.e. the implementation / prototype "verifies" the design) and good design can hardly be planned but evolves over time. It requires experience, courage (and time) for changing design / implementation, test support and a development environment with fast turn-around times.

Class Design

Visibility

The visibility of classes should be minimized. Classes can be either public or package-private. It should first be tried with package-private. If it is just used by a single class make it a private nested class.

Additionally, the visibility of members should be minimized. Private should be the default for members. If access is needed, package-private should be default. Non-final instance fields should never be public. The proper use of access modifiers is essential for information hiding.

Since inheritance violates encapsulation, a class should be designed and documented for inheritance or prohibit it (with the final keyword). The mutability should be minimized: The final keyword can be used for methods that must not be overridden within non-final classes. Methods that are meant to be overridable should be marked with protected.

General Class Design

A class can either have value or object semantics. It should be independent from the context to facilitate testing (-> use abstract coupling). Abstract classes can provide default implementations however, they should not be used for type definition (-> use interface).

A class should only have a single responsibility -> SOLID principles. A delegating class is a class that combines the features of several classes. It should be more powerful (more than the sum of its parts) and provide a simpler API (less than the sum of its parts).

Base classes should have a simple and expressive name. Important classes should be one-word names. The names of subclasses need to communicate two aspects: Likeness, Difference.

Whenever possible, try to specify a type which this class conforms to: subtyping. And in general, favor composition over inheritance. ("extends" i.e. subclassing should not be used for code sharing).

Class Fields

  • declare named constants with static final
  • use classes rather than interfaces for constants (static import renders qualifying obsolete)

Instance Fields

  • mutable fields should never be public
  • provide accessors (but only if really needed, refrain from eager accessor generation)
  • base classes should have as little state as possible (because it's difficult to override state)
  • use abstract state to defer representation decisions (i.e. subtypes can decide how to represent values)
  • if several instance fields share a prefix -> delegate to helper object (may be a member class)

Constructor

  • constructor is limited because it can only return own type
  • consider (static) factory method as an alternative
  • can provide a more descriptive name
  • cannot be adapted in subclasses
  • does not need to create an object with each call
  • may return a subtype of the requested type
  • builder (with fluent API) provides flexibility and expresiveness
  • only final methods of the class should be called in a constructor

Methods

  • the name should refer to what the method does, not how it does it
  • too many parameters indicate wrong responsibilities
  • use parameter object for recurring parameter combination
  • use overloading judiciously
  • small methods are easier to to comprehend and override
  • methods should only do a single task
  • tell, do not ask (Law of Demeter)
  • caller should not be forced to use chained getters
  • encapsulate object structure
  • do not mix abstraction levels
  • use final to prohibit overriding
  • input parameters:
  • type should be as abstract as possible
  • a too abstract type would enforce instanceof and downcast in method body
  • output parameters:
  • type should be as specific as possible
  • client should not need to use instanceof and downcast

SOLID Design Principles

  • SRP - Single Responsibility Principle: Changes to a class should have only a single reason
  • OCP - Open-Closed Principle: Classes are closed but still extensible
  • LSP - Liskov Substitution Principle: A derived class must entirely fulfill the contract of its base class
  • ISP - Interface Segregation Principle: Clients should not depend on interfaces which they don't need
  • DIP - Dependency Inversion Principle: Concrete classes depend on abstract classes and not vice versa

Single Responsibility Principle

If a class implements many requirements there is a high probability of a need for change (if a requirement changes). These changes can have unwanted side effects. It is thus recommended that a class is responsible only for a single requirement and thus changes to it should have only a single reason. The single responsibility principle leads to high cohesion in the codebase.

If the classes cannot be broken up use Interface Segregation principle.

The class rectangle has responsibilities from two different areas: Computational Application (wants to perform computation) and Graphical application (wants do draw the rectangle). This violates the SRP because the applications might be affected if a requirement changes that affects the rectangle class. A better approach is to decouple these two responsibilities. The class Box delegates the geometric computations to the class Rectangle.

Open-Closed Principle

The implementation unit (class, module, method, etc.) can be extended without modifying it. The implementation can be adapted to a new context. Access to source code is not required for the extension. This principle makes the units resilient to changes in their environment.

// Counterexample
public draw(Shape shape) {
  if (shape instanceof Box) {
    drawBox(shape)
  } else if (shape instanceof Circle) {
    drawCircle(shape)
  }
}


Liskov Substitution Principle

Derived classes must entirely fulfill the contract of their base classes. (i.e. subtypes rather than subclasses). Creating and using new subclasses does therefore not impact clients. The contract specifies hehavior from a client's point of view. The client must be able to rely on this contract.

Covariance is a counterexample of LSP because the contract cannot be kept at run-time.

Interface Segregation Principle

Clients should not depend on interfaces which they don't need. Large interfaces cause unnecessary dependencies. Usually clients need only specific subsets of a large interface but any changes to a large interface might impact a client.

In the example above the Service class offers methods for all three clients (and thus violates the SRP principle). In the case that it is not possible to split up the class into three distinct ones, it is possible to create client-specific interfaces on which the implementations are based.

The solution consists of defining small interfaces for each client. As a result, the clients only depend on the single interfaces and changes in any other method do not affect them.

Dependency Inversion Principle

High-level components should not depend on low-level components. Abstractions should not depend on concrete details. Changes in lower layers must thus not impact higher layers. High-level components should be reusable in some other context. Consequently, low-level components need to be replaceable.

The image above depicts a usual situation with different layers. The abstract layers are on top and the concrete layers are at the bottom. The problem with this architecture is that changes in the lower levels might affect the layers above.

This approach is much better since it introduces abstractions (i.e. interfaces). Now the layers only depend on the interface of the lower levels. As a result, changes in the business logic do not affect the higher levels. Furthermore, alternative implementations can be provided as long as they adhere to the interface.

Law of Demeter

The law of demeter promotes loose coupling. A method m of object o may only invoke methods of the following objects:

  • o itself
  • m's parameters
  • any objects created / instantiated within m
  • o's direct component objects
  • global variables, accessible by o in the scope of m

In other terms:

  • do not use objects returned by methods (e.g. getters on these objects)
  • do not use multiple "." in Java (e.g. currentShipment.getItems()[i].getManufacturer().getAddress())

Design Patterns

Design patterns convey proven design structures and their intention. They provide abstraction and reusability of good object-oriented design. The are used to name and catalogue repeatedly occurring design structures. Thus they create a common vocabulary and communication medium. A design pattern consist of a description of interacting objects and classes which solve a problem in a specific context.

There are three different kinds of design patterns:

  • Creational patterns
  • Abstract Factory: Families of product objects
  • Builder: Creation of a complex object (graph)
  • Factory Method: Subclass of object that is instantiated
  • Prototype: Class of object that is instantiated
  • Singleton: The sole instance of a class
  • Structural Patterns
  • Adapter: Interface to an object
  • Bridge: Implementation of an object
  • Decorator: Responsibilities of an object without subclassing
  • Facade: Interface to a subsystem
  • Flyweight: Storage costs of objects
  • Composite: Structure and composition of an object
  • Proxy: Access to an object and its location
  • Behavioral Patterns
  • Command: Time and specifics of an operation
  • Observer: Number of dependent objects and how they are updated
  • Visitor: applying operations on objects without changing their class
  • Interpreter: Grammar and interpretation of a language
  • Iterator: Accessing and traversing the elements of an aggregate
  • Memento: Extent and time of storing primitive information externally
  • Template Method: Steps of an algorithm
  • Strategy: Algorithm
  • Mediator: Interacting objects and the way of interaction
  • State: Object state
  • Chain of Responsibility: Object which can respond to a message

Creational Patterns

Factory Method

The intent is to define a class interface with operations for creating an object. Subclasses decide what objects are being created. The creation process is delegated to a subclass. The general idea is to abstract the creation process.

  • abstract Product: defines class of object to be created by factory method
  • ConcreteProduct: implements product interface
  • abstract Creator: declares factory method, may also call factory method
  • ConcreteCreator: overrides factory method to create and return concrete product

Singleton

Ensures that there is only a single instance of a class. Some classes may only have a single instance (e.g. an interface to the operating system). A global variable is not sufficient (does not communicate the intention and does not prevent the creation of additional instances). The singleton class encapsulates its sole instance.

public class Singleton {
  private static Singleton s = new Singleton();
  public static Singleton getSingleton() { return s };
}
// second possibility:
public enum Singleton {
  INSTANCE;
  private Singleton() {};
}
  • Should be used sparingly: globals are "evil" and singletons are a kind of (hard-coded) global
  • singletons should be thread-safe (for most scenarios)

Abstract Factory

The intent is to provide an interface for creating families of related or dependent objects without specifying their concrete classes. The system should be independent of how its products are created. The implementation of products itself should be hidden. The system should be configurable with different product families. Within a product family, consistency is required.

public class WidgetFactory {
  public static Button createButton() {
    if(onMacOS) {
      return new MacButton();
    else if (onWindows) {
      return new WindowsButton();
    }    
  }
  public static Checkbox createCheckbox() { ... }
}
  • AbstractFactory: declares an interface for operations that create abstract product objects
  • ConcreteFactory: implements operations to create concrete product objects
  • AbstractProduct: declares an interface for a type of product object
  • ConcreteProduct: defines a product object to be created, implements AbstractProduct interface
  • Client: uses only interfaces AbstractFactory and AbstractProduct

Usually, only one instance per ConcreteFactory is created (-> Singleton). Sometimes there is only a single instance of a ConcreteFactory at run-time. The products are created with factory methods.

It isolates concrete classes and thus clients do not have any dependency on concrete classes. Replacing product families gets easy (change concrete factory). Adding new product families is easy. It also promotes consistency among products. BUT: Introducing new products is cumbersome.

Builder (Variation)

public class Person {
  public enum Gender {FEMALE, MALE};
  private Gender gender;
  private String firstName;
  private String lastName;
  private String profession; //optional 
  private LocalDate dateOfBirth; //optional    

  public static class Builder {
    private Gender gender;
    private String firstName;
    private String lastName;
    private String profession;
    private LocalDate dateOfBirth;
    
    public Builder(String firstName, String lastName, Gender gender) {
      // all mandatory fields
      this.firstName = firstName;
      this.lastName = lastName;
      this.gender = gender
    }
    
    // important: returns Builder itself

    public Builder profession(String profession) {
      this.profession = profession;return this;
    }
    public Builder dateOfBirth(LocalDate dateOfBirth) {
      this.dateOfBirth = dateOfBirth;
      return this;
    }
    public Person build() {
      // perform validations (e.g. that state is valid)return new Person(this)
    }
  }

  private Person(Builder builder) {
    firstname = builder.firstName;
    lastName = builder.lastName;
    ....
  }
}

// usage
Person person = new Person.Builder("Ada","Lovelace",Person.Gender.FEMALE)
  .profession("mathematician")
  .dateOfBirth(LocalDate.of(1815, 12,10))
  .build();

Structural Patterns

Adapter (Wrapper)

The intent is to convert the interface of a class into an interface expected by clients. Often existing classes cannot be changed, thus if we want to use them we need to adapt their interfaces. The adapter allows integrating a class into a new type hierarchy without an interface. It helps to make independently developed class libraries work together. We can use it whenever we want to use an existing class but its interface is not compatible.

interface IShape {
  void draw();
}
public class ShapeView {
  void addShape(IShape shape) { ... }
}
public class Box implements IShape {
  @Overridepublic void draw() { ... }
}
// we cannot change this class
public class Icon {
  void drawImage();
}
// we create an adapter that delegates the operation to the
// original Icon class
public class IconAdapter implements IShape {
  private Icon icon;
  @Override public void draw() {
    icon.drawImage();
  }
}


Bridge

The intent is to decouple an abstraction from its implementation such that both can vary independently. If an abstraction is implemented using inheritance it creates a strong coupling between abstraction and implementation. It is like a more sophisticated adapter where the implementation (-> the implementor) is also defined as an abstract class.

// abstraction
public class Button {
  private ButtonPeer button;
  public void setLabel(String s) {
    button.setLabel(s);
  }
  public void processEvent() {...}
} 
// refined abstraction
public class MyButton extends Button {
  public void processEvent() {...}
}
// implementor
public abstract class ButtonPeer {
  public abstract void setLabel(String s);
}
// concrete implementor
public class MacButtonPeer extends ButtonPeer {
  public void setLabel(String s) { ... }
}
  • Abstraction: defines the interface of the abstraction and contains a reference to an implementor object
  • RefinedAbstraction: extends the interface of abstraction
  • Implementor: defines interface for implementor classes, provides primitive operations for abstraction
  • ConcreteImplementor: implements implementor interface

Decorator

The intent is to attach additional responsibilities to an object dynamically (during runtime). It is a flexible alternative to subclassing. It can be used to change the functionality of classes (and also remove functionality). The decorator only changes the "skin" not the "guts". It can add / remove functionality but not change how something is performed. (Use strategy instead.)

public abstract class Component {
  public abstract void operation() { ... }
}
public class ConcreteComponent extends Component {
  public void operation() { ... }
}
// is optional - only makes sense if there are lots of different subclasses
public abstract class Decorator extends Component {
  private Component component;
  public void operation() {
   component.operation();
  }
}
public class ConcreteDecorator extends Decorator {
  private Component component;
  private Object additionalState;
  public void operation() {
    super.operation();
    additionalOperation();
  }
}
  • Component: defines interface for dynamically extensible objects
  • ConcreteComponent: defines an object which can be dynamically extended
  • Decorator: maintains reference to Component object and defines interface conforming to component
  • ConcreteDecorator: adds functionality to component object

In comparison to inheritance this provides more flexibility. The functionality can be added "pay-as-you-go" in the leaves instead of the root. However, the decorator can only access the public API of the decorated component whereas a subclass has access to public and protected API.

Composite

The intent is to treat individual objects and compositions the same way (e.g., files and folders should both be duplicated using duplicate()). This usually applies to objects that are composed into tree structures. Whenever objects are represented hierarchically (object consists of set of objects or object is contained in some other object) this pattern can be applied. The client should be able to ignore the difference between a single object and a set of objects.

public abstract class Component {
  public abstract void operation() { ... }
  public abstract void addComponent() { ... }
  public abstract void removeChild() { ... }
  public abstract void getChild() { ... }
}
public class Leaf extends Component {
  public void operation() { ... }
  public abstract void addComponent() { ... }
  public abstract void removeChild() { ... }
  public abstract void getChild() { ... }
}
public class Composite extends Component {
  private List<Component> children;
  public void operation() {
    for(Component g : children) {
      g.operation();
    }
  };
  public void addComponent() { ... };
  public void removeComponent() { ... };
  public Component getChild() { ... };
}
  • Component: declares interface for objects in the composition and implements default behavior. declares interfaces for accessing child/parent objects
  • Leaf: represents leaf objects in the composition; defines behavior for primitive objects
  • Composite: defines behavior for components with child objects; stores child objects; implements child-related operations
  • Client: manipulates objects in the composition through the component interface

Complex object structures can be built dynamically and recursively. The clients do not need to differentiate between simple and complex objects. It facilitates adding new kinds of objects which work with existing clients. However, restricting object structure is not easy (only possible with run-time checks).

Behavioral Patterns

These patterns describe algorithms and how they are assigned to objects. They contain collaboration patterns that govern how objects interact.

Observer

The intent is to define a 1..n dependency among objects. If the state of one object is changed, all the dependent objects are notified so they can update. It can be applied if there are two aspects (e.g. model and view) and one aspect depends on the other one. Also if there are changes that require notifying some objects and the number of objects is unknown. It avoids tight coupling of objects.

public abstract class Subject {
  private List<Observer> observers; 
  // knows its observers

  public void attach(Observer o) { ... }public void detach(Observer o) { ... }
  public void notifyObservers() {
    for(Observer o : observers) {
      o.update();
    }
  }
} 
public class ConcreteSubject extends Subject {
  private Object subjectState;
  public Object getState() { return subjectState; }
  public void setState(Object o) { ... }
}
// abstraction for the interface for the observers
public abstract class Observer {
  public abstract void update() { ... }
}

public class ConcreteObserver extends Observer {
  private Object observerState; 
  private Subject subject; // concrete observer knows the subject
  public void update() {
    observerState = subject.getState();
  }
  // a different implementation where the observer might be observing multiple
 // subjects and it is passed in the update
  public void update(Subject s, Object args) { ... }
}
  • Subject: knows its observers (arbitrary number); provides interface for attaching / detaching observers
  • Observer: defines updating interface
  • ConcreteSubject: stores state of interest to ConcreteObserver
  • ConcreteObserver: maintains reference to a ConcreteSubject; implements updating interface

It allows abstract coupling between subject and observer and supports broadcast communication. However, there might be unexpected updates that are hard to debug. Also circular updates can be a problem (update causes another update) if subject and observer classes are combined.

There are different implementations available where the update might be triggered by the client (allows for bundling / batching updates) or by the subject (guarantees updates).

Strategy (Policy)

The intent is to define a family of algorithms, encapsulate each one and make them interchangeable. The strategy pattern lets the algorithm vary independently from clients that use it. It can be applied if there are several classes that differ only in their behavior or if there are several variants of an algorithm needed. It can also be used to replace a case / switch statement where a class decides on the behavior to use depending on the context.

public class Context {
  private IStrategy strategy;
  public void setStrategy(IStrategy strategy) {
    this.strategy = strategy;
  }
  public void execute() {
    strategy.executeAlgorithm(); // execution is delegated to strategy
  }
}
public interface IStrategy {
  default void executeAlgorithm() { 
    // implementation of algorithm
  }
}
public class MyStrategy implements IStrategy {
  public void executeAlgorithm() {
   // different implementation of algorithm
  }
}
// usage
Context context = new Context();
context.setStrategy(new MyStrategy())
context.execute() // will delegate to strategy and call executeAlgorithm()

There is also a lambda version of this implementation:

@FunctionalInterface
public interface IStrategy {
  void executeStrategy(); // functional interface can only have 1 method
}
public class Context {
  private IStrategy strategy;
  public void setStrategy(IStrategy strategy) {
    this.strategy = strategy;
  }
  public void execute() {
    strategy.executeStrategy();
  } 
}
// usage
Context context = new Context();
// as long as there are the same amount of parameters and the types
// of the parameters match to the functional interface it will work
context.setStrategy( () -> System.out.println("execute strategy"));
context.execute();
  • Strategy: declares interface common to all supported algorithms; Context uses this interface
  • ConcreteStrategy: implements algorithm using the Strategy interface
  • Context: is configured with a ConcreteStrategy; maintains reference to Strategy object; may define an interface for the Strategy object to access its data

It is an alternative to subclassing when there are families of related algorithms (e.g. optimizing time vs. memory). It eliminates the need for case / switch statements. However, it adds communication overhead between Context and Strategy and increases the number of objects.

Template Method

The intent is to create a group of subclasses that have to execute a similar group of methods.

public abstract class Importer {
  private abstract void initialize();
  private abstract void startImport();
  private abstract void afterImport();
  public final void import() {
    initialize();
    startImport();
    afterImport();
  } 
} 
public class MyImporter extends Importer {
  @Override
  private void initialize() {
    System.out.println("Initialize");
  }
  @Override
  private void startImport() {
    System.out.println("Starting Import");
  }
  @Override
   private void afterImport() {
     System.out.println("Import done.")
   }
}