Object-oriented design patterns Object-oriented design patterns

Object-oriented design patterns

Object-oriented design patterns solve many common software design problems, as follows, that architects come across every day:

  • Finding appropriate objects
  • Determining object granularity
  • Specifying object interfaces
  • Specifying object implementations
  • Programming to an interface, not an implementation
  • Putting the reuse mechanism to work

We will touch upon some of the common problems and how design patterns solve the mentioned glitches in this section and cover OO design patterns in detail.

We can categorize the patterns into three types: creational, structural, and behavioral. Refer to the table at the end of this chapter, which depicts the patterns and its categories as a simple reference before we move ahead with the details.

Creational design patterns

The creational patterns intend to advocate a better way of creating objects or classes, and its primary focuses are as follows:

  • Abstracting the class instantiation process
  • Defining ways to create, compose, and represent objects and hide the implementation details from the involving system
  • Emphasizing avoiding hard code of a fixed set of behaviors and defining a smaller set of core behaviors instead, which can compose into any number of (complex) sets

Creational design patterns have two basic characteristics: one is that they encapsulate knowledge about which concrete class the system use, and the second is that they hide how the instances of these classes are created and put together.

The class creational pattern uses inheritance for instantiation, whereas object creations delegates it to another object.

The following section deals with each pattern, its general structure, and sample implementation diagram in most of the cases.

Factory method (virtual constructor)

This pattern suggests to let the subclasses instantiate the needed classes. The factory method defines an interface, but the instantiation is done by subclasses:

The preceding structure depicts a factory method, and an application uses a factory to create subtypes with an interface.

The benefits of using this are as listed:

  • Loose coupling: Separates application from the classes and subclasses
  • Customization hooks: The factory method gives subclasses a hook for providing an extended version of an object

The impact of using this is that it creates parallel class hierarchies (mirroring each other’s structures), so we need to structure in the best possible ways using intelligent children pattern or Defer identification of state variables pattern.

Abstract factory (kit)

Abstract factory pattern is intended to provide an interface if we want to create families of related or dependent objects, but without explicitly specifying their concrete classes:

The preceding class diagram depicts the AbstractFactory class structure and a real-time implementation of an abstract factory pattern for an application that combines a different set of (heterogeneous) products from two different groups (<<Bank>> and <<Loan>>).

The benefits of this are the following:

  • Isolating concrete classes
  • Making exchanging product families easy
  • Promoting consistency among products

Impact is such as; supporting new kinds of the product is difficult.

Builder

The builder is intended to separate the construction of a complex object from its representation so that the same construction process can create different representations. In other words, use this pattern to simplify the construction of complex object with simple objects in a step-by-step manner:

The class diagram depicts a typical builder pattern structure and a sample implementation classes for the Builderpattern. The Builder (TextConverter) is an abstract Interface that creates parts of a product page. The Concrete Builder (AsciiConvTexConv) constructs and assembles parts by interface implementation, the Director (Reader) constructs an object with the builder interface, and the Products (AsciiTxtText) are under construction complex objects.

The benefits are as listed:

  • Allows changing the internal representation and defines new kind of builder
  • Isolates code for construction and representation
  • Provides finer control over the construction process

Impacts are as listed:

  • Leads to creating a separate concrete builder for each type of product
  • Leads to mutable Builder classes

Prototype

Prototype pattern suggests copying or cloning the existing object and customizing it if needed rather than creating a new object. Choose this pattern when a system should be independent of its products creation, compose, and representation:

We can create a copy of PublicProfile (limited information) or FullProfile at runtime. Those two classes share a few combination of states, so it is good that we design as a prototype.

Let’s take a look at its benefits:

  • Adding and removing products at runtime
  • Specifying new objects by varying values and structures
  • Reduced subclasses
  • Dynamic class configuration to an application

The impact is, each subclass must implement clone operation, and it is not possible to clone circular reference classes.

Singleton

This pattern suggests that you create one and only one instance and provide a global point of access to the created object:

The DB connection in the preceding diagram is intended to be a singleton and provides a getter for its only object.

Here are its benefits:

  • Controlled access to a sole instance
  • Reduced namespace
  • Flexibility to refinement of operations and representations
  • More flexible than class operations

Impacts are as follows:

  • Carry states for the whole lifetime of the application, creating additional overhead for unit tests
  • Some level of violation of single responsibility principle
  • By using singleton as a global instance, it hides the dependencies of the application; rather, it should get exposed through interfaces

Structural design patterns

The structural patterns provide guidelines to compose classes and objects to form a larger structure in accordance with the OO design principles.

The structural class pattern uses inheritance to compose interfaces or implementations, and structural object patterns advocate ways to compose objects and realize the new functionality.

Some focus areas of Structural design pattern are as follows:

  • Providing a uniform abstraction of different interfaces (Adapter)
  • Changing the composition at runtime and providing flexibility of object composition; otherwise, it is impossible with static class composition
  • Ensuring efficiency and consistency by sharing objects
  • Adding object responsibility dynamically

The following section describes each structural pattern with standard structure and sample implementation structure as a diagram as well.

Adapter class (wrapper)

Convert one interface of a class into another interface that the client wanted. In other words, the adapter makes heterogeneous classes work together:

The preceding class diagram depicts an adapter called OnlineLinkedAccounts that adopts a savings account’s details and a target interface called credit card details, and combine the results to show both account numbers.

Adapter (object)

An adapter object relies on object composition, and when we need to use several of the existing subclasses, we can use object adapter to adapt the interface of the parent class:

The preceding diagram depicts the formal structure of an Adapter.

These are the benefits:

  • Saves time during development and testing by emulating a similar behavior of different parts of the application
  • Provides easy extensions for new features with similar behaviors
  • Allows a single adapter works with many adaptees (adapter object)

Impacts are as follows:

  • Leads to needlessly duplicated codes between classes (less usage of inherited classes’ functionalities)
  • May lead to nested adaptions to reach for intended types that are in longer chains
  • Make it more difficult to override adaptee behavior (adapter object)

Bridge (handle/body)

Bridge pattern intent is to decouple the abstraction from its implementation, so abstraction and implementation are independent (not bound at compile time, so no impact to the client):

The benefits are as mentioned:

  • Decoupling interfaces from the implementation
  • Configuring the implementation of an abstraction at runtime
  • Elimination of compile-time dependency
  • Improved extensibility
  • Hiding implementation details from the client

The impact is, introducing some level of complexity.

Composite

Composite objects let clients treat individual objects and composition of objects uniformly. Composite represents the hierarchies of objects as tree structures.

The preceding diagram depicts the standard structure of the Composite pattern and an implementation of a part-whole hierarchy (employee part of agent, Accountant, and teller), and to the Client, all objects are Composite and structured uniformly.

These are the benefits:

  • It simplifies the client code by hiding the complex communications (leaf or composite component)
  • It is easier to add new components, and client does not need a change when new components get added

The impact is such that it makes the design overly general and open as there are no restrictions to add any new components to composite classes.

Decorator

The decorator pattern attaches additional responsibilities to an object dynamically. It provides an alternative way (by composition) to subclass and to extend the functionality of an object at runtime.

This pattern creates a decorator class by wrapping the original class to provide additional functionalities without impact to the signature of methods.

Observe the preceding diagram as it depicts invoice functionalities extended by composition dynamically (runtime).

Let’s list the benefits:

  • It reduces time for upgrades
  • It simplifies enhancing the functionalities from the targeted classes and incorporates behavior into objects (changes class responsibilities, not the interface)

Impacts are as follows:

  • It tends to introduce more look-alike objects
  • It leads to debugging difficulties as it is adding functionality at runtime

Façade

Façade suggests providing a high-level interface that unifies set of interfaces of subsystems, so it simplifies the subsystem usage.

A sample implementation of a service façade as in the preceding diagram, the session subsystem are unified with session façade (local and remote).

Let’s look at the benefits:

  • It promotes loose coupling (between clients and subsystems)
  • It hides complexities of the subsystem from the clients

The impact is such that it may lead to façade to check whether the subsystem structure changes.

Flyweight

Flyweight suggests using the shared support of a vast number of fine-grained objects. We can use the Flyweightpattern to reduce the number of objects created (by sharing) and thereby reduce the memory footprint and improve the performance.

The preceding diagram depicts the general structure of the Flyweight pattern and a sample implementation. Consider a massive object that is shared across printer and a screen; Flyweight is a good option and can be cached as well (say for printing multiple copies).

Here are the benefits:

  • It leads to good performance due to reduction in the total number of instances (by shared objects)
  • It makes implementation for objects cache easy

The impact is such that it may introduce runtime costs associated with transferring, finding, or computing foreign (extrinsic) state.

Proxy

The proxy pattern suggests providing a placeholder (surrogate) for another object to control and get access to it. It is the best fit for lazy loading of objects (defer the creation and initialization until we need to use it).

The preceding diagram shows a sample implementation of a proxy pattern for a payment class, and the payment can be either by check or by pay order. However, the actual access would be to DebitAccount object, so PayOrderProxy and CheckProxy are both surrogates for Debit Account.

The following are the benefits:

  • It introduces the right level of indirections when accessing an object (abstraction of an object that resides in a different space)
  • Creating objects on demand
  • Copy-on-write (may reduce the copying of heavy objects if not modified)

The impact is such that it can make some implementations less efficient due to indirections.

Behavioral patterns

Behavioral patterns provide guidelines on assigning responsibilities between objects. It does help with ways to implement algorithms and with communication between classes and objects.

Behavioral pattern focuses on the following characteristics:

  • Communication between objects and classes
  • Characterizing the complex control flow; flow of control in software programming (otherwise, it is hard to follow at runtime)
  • Enforcing object composition rather than inheritance
  • Loose coupling between the peer objects, and at the same time, they know each other (by indirections)
  • Encapsulating the behavior in an object and delegating request to it

There are various design patterns available to enforce the above said behavioral focusses and characteristics. We will see details of those behavioral patterns in this section . We also provided a sample implementation structure as a diagram for some of the patterns.

Chain of responsibility

This pattern suggests avoiding coupling the client object (sender of requests) with the receiver object by enabling objects (more than one) to handle the request.

The preceding diagram depicts the typical structure of the chain of responsibility; the handler is the interface to define the requests and optionally express the successors along with concrete handlers that can handle the requests and forwards the same if needed.

Here’s a list of the benefits:

  • Reduced coupling (objects do not know which other objects handle the requests)
  • Additional flexibilities in responsibilities assignments (of objects)

The impact is, no handshakes between the request handlers, so no guarantee of handling the request by other objects, and it may fall off from the chain unnoticed.

Command (action/transaction)

This pattern suggests encapsulation of requests as an object, parameterizing clients with different requests; it can placed over message queues, can be logged, and supports undo operations.

The preceding diagram depicts the structure of a command pattern and a sample implementation for a stockbroker application classes. <<StockOrder>> interface is a Command, and Stock concrete class creates requests. Buy and Sellare concrete classes implementing the <<StockOrder>>. The StockBroker is an invoker, and its objects execute specific commands depending on the type that it receives.

Here are the benefits:

  • Encapsulation of object facilitates the changing of requests partially (by changing a single command) and no impacts to the rest of the flow
  • Separates the invoking object from the actual action performing object
  • Easy to add new commands without any impact to the existing classes

The impact is, the number of classes and objects increases over time or depends on the number of commands (concrete command implementations).

Interpreter

This pattern suggests defining grammar along with an interpreter that uses representations so that the system can interpret any given sentences of a language.

Abstract expression or regular expression declares interpret operation, terminal expressions or literal expressions implements symbols in the grammar, and non-terminal expressions (alternate, sequence, repetition) has nonterminal symbols in the grammar.

Let’s look at the benefits:

  • It is easy to change and extend the grammar
  • Implementing the grammar is easy as well
  • Helps introduce new ways to interpret expressions
  • Impacts
  • Introduces maintenance overhead for complex grammars

Iterator (cursor)

This pattern suggests providing an approach to sequentially access the elements of an aggregate object and, at the same time, hide the underlying implementations.

The preceding diagram depicts the structure of the iteration pattern in which the iterator interface defines traversing methods, and the concrete iterator implements the interface. Aggregate defines an interface for creating an iterator object, while a Concrete aggregate implements the aggregate interface to create an object.

Here are the benefits:

  • It supports variations in the aggregate traversals
  • Iterators simplify the aggregate interfaces
  • It may have null iterators and helps handle boundary conditions better
  • Impacts
  • It may introduce additional maintenance cost (dynamic allocation of polymorphic iterators)
  • It may have privileged access and thus introduces complexities to define new traversal methods in iterators

Mediator

The Mediator pattern advocates defining ways of interactions between encapsulated objects without depending on each other by explicit reference.

The preceding diagram is a typical structure of the Mediator pattern, where Mediator or dialog director defines an interface to communicate with other colleague objects; concrete mediator implements cooperative behavior by coordinating colleague objects.

Let’s look at the benefits:

  • Limits subclassing (by localizing behavior and restricting the distribution of behaviors to several other objects)
  • Enforcing decoupling between colleagues objects
  • Simplifying object protocols (replaces many-to-many interactions to one-to-one)
  • Providing clear clarification on how objects should interact

Impacts is centralized control, leading to more complex and monolithic systems.

Memento

This pattern suggests capturing and externalizing an object’s internal state without violating encapsulation principles; so, we can restore the captured object.

The preceding diagram depicts the structure of the memento pattern and a sample implementation for a calculator application. The Caretaker interface helps restore the previous operation that’s handled in the <<Calculator>>concrete class.

These are the benefits:

  • It preserves encapsulation boundaries by exposing information limited to the originator
  • It simplifies the originator

Impacts are as follows:

  • Memento implementation might be expensive, as it needs to copy large amounts of data to store into the memento
  • It may be difficult to implement (through some programming languages) and ensure that only the originator is accessing the memento’s state
  • It might incur hidden storage and maintenance costs at the caretaker implementations

Observer (dependents/publish/subscribe)

The Observer pattern suggests that when one object changes the state, it notifies its dependents and updates automatically. When implementation is in need of one-to-many dependencies, you would want to use this pattern.

The preceding diagram depicts Observer pattern structure and a sample implementation of the same for a publications app; whenever an event occurs, subscribers need to be informed. The subscribers have a different mode of publishing (SMS, print, and emailing) and may need to support new modes as well in the future, so the best fit is Observer, as we just saw.

Let’s go through its benefits:

  • Enables easy broadcast of communication
  • Supports loose coupling between objects as it’s capable of sending data to other objects without any change in the subject
  • Abstract coupling between subject and observer (changes in the observer do not impact subject)
  • Can add or remove Observers any time

Impacts are as follows:

  • Accidental or unintended updates impact the system heavily as it cascades to the observer down the layers
  • May lead to performance issues
  • Independent notifications may result in inconsistent state or behavior (no handshakes)

State (objects for states)

These allow an object to alter its behavior when its internal state changes, and it appears as the class changes.

Use state pattern when an object’s behavior depends on its state and change at runtime depends on that state.

The diagram depicts both structure of State pattern and a sample implementation; Context class carries states, and Off and On classes implement State interface so that context can use the action on each concrete class’s off/on.

Listed are the benefits:

  • Suggest localizes state-specific behavior and partitions behavior for different states (new states and transitions can be added easily by subclass definitions)
  • Makes state transitions explicit
  • State objects are shareable

The impact is, it may make adding a new concrete element difficult.

Strategy (policy)

Strategy pattern, also known as policy, defines a family or set of algorithms, encapsulates each one, and make them interchangeable. Strategy lets the algorithm vary independently of the clients that use it. When a group of classes differs only on their behavior, it is better to isolate the algorithms in separate classes and provide the ability to choose different algorithms at runtime.

The preceding diagram shows the strategy structure, and implementation of sorting algorithms (as a family) and depends on the input depends on the volume for sort, then the client can use the intended algorithm from the Concrete strategy sorting classes.

The benefits are as listed:

  • Enables open and closed principle
  • Enables large-scale reusability
  • Eliminates conditional statements (leads to clean code, well-defined responsibilities, easy to test, and so on)

Impacts are as follows:

  • Clients need to be aware of different strategies and how they differ
  • Communication overhead between strategy and context
  • Increased number of objects

The template method

This suggests providing a skeleton of an algorithm in operation, and deferring a few steps to subclasses. The template method lets subclasses redefine a few specific actions of a defined algorithm without changing the algorithm structure.

The following are the benefits:

  • Fundamental technique for code reuse
  • Allows partial implementation of business process while delegating implementation-specific portion to implementation objects (flexible in creating prototypes)
  • Helps implement the Hollywood principle (inverted control structure, Don’t call us, we will call you)

Impacts are as follows:

  • Sequence of flow might lead to confusion
  • High maintenance cost and impacts are high on any changes to the code

Visitor

The visitor pattern represents an operation performed on the objects. It lets us define a new operation without changing the class elements on which it operates. In simple words, we use the visitor class to alter the execution of an algorithm as and when the visitor varies.

>

Here are the benefits:

  • Adding new operations over an object structure is straightforward and easy (by adding a new visitor)
  • Visitor separates unrelated operations and gathers related operations

Impacts are as follows:

  • The visitor class hierarchy can be difficult to maintain when a new concrete element class gets added
  • Implementation often forces to provide public operation that accesses an element’s internal state (leads to compromising its encapsulation)

Leave a Reply

Your email address will not be published. Required fields are marked *