Skip to content

biniama/design-patterns-notes-and-code

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Design Patterns

1. Object Oriented Programming Concepts

Interface

a contract that specifies the functionalities/capabilities a class should provide

Encapsulation

hiding the implementation detail

Abstraction

reduce complexity by hiding unnecessary details (e.g. expose only one method and making others private). Promotes less coupling.

Inheritance

a mechanism for reusing code by creating 'is-a' relationship between parent/super and child/sub classes. Promotes DRY

Polymorphism

Poly means 'many', morph means 'form'. Hence, it is the ability of an object to take many forms

UML: Unified Modelling Language - official language to model our system to represent classes and their relationships.
Types of Relationships
  • Inheritance relationship (line with an unfilled rectangle) [source] public class Rectangle extends Shape {}

  • Composition relationship (arrow with a filled diamond)

public class Shape {
    //  Shape class is composed of a Size class
    private Size size;
}
  • Aggregation relationship (arrow with an empty diamond)

  • Dependency relationship (dashed arrow)

public class Shape {
    // Shape class depends on the Document class
    public void render(Document doc) {}
}
Note
Aggregation vs Composition

2. Design Patterns

Design patterns are general solutions to common OO problems. They are time-tested, reusable OO designs that solve many common design problems. It is an approach to thinking about software design that incorporate the experience of developers who have had similar problems. A Design pattern is named solution to a problem in a context.

Goal

To create object-oriented software that is more flexible, maintainable, extensible and reliable.

Design Patterns should be programming language and domain independent. Often, architectures and platforms are built on a set of design patterns. E.g. Java Spring Framework uses Proxy, Adapter, Singleton, Facade and Template Method patterns.

Benefits
  1. Not reinventing the wheel

  2. Building resilient code that can withstand change

  3. Prepare for future additions and change

  4. Serves as shared vocabularies

    1. Patterns allow you to say more with less

    2. Talking at the pattern level allows you to stay 'in the design' longer

    3. Shared vocabularies can turbo-charge your development team - go faster and less misunderstanding

    4. Shared vocabularies encourage more junior developers to get up to speed.

3. Design Principles vs Design Patterns

Design Principles

general guidelines on how to develop quality software and avoid bad object-oriented design. A bad design is one that is too rigid and inflexible, or is hard to reuse. E.g. 'Encapsulate what varies'

Design Patterns

specific design solutions often named at solving common OO problems. E.g. The Strategy pattern, the Iterator pattern, the Factory pattern

Principles are aimed at the low level of how we put objects together, while patterns are aimed at larger problems. Many design patterns are inspired by design principles.

Design principles are general guidelines that you can use with any aspect of your object-oriented design. Principles guide your class structure and the relationships and really to steer you away from rigid, fragile and immobile designs.

Design patterns are aimed at solutions to commonly occurring problems. These are proven solutions that have been found, over time, to solve those problems, rather than abstract guidelines. Design patterns provide actual designs in the form of class diagrams that can then be adapted to your own specific solution.

4. Design pattern categories

In the Gang of Four catalog, patterns are categorized primarily by their purpose. Creational patterns:: relate to creating or instantiating objects. It provides a way to decouple the client, that is the part of the system instantiating and using the objects, from the objects it needs to instantiate. Behavioral patterns:: relate to how classes and objects interact. Structural patterns:: relate to how classes and objects are composed into larger structures.

design pattern categories

5. Creational Patterns

The goal of all Creational Patterns is to give us more flexibility in how we create objects, what kinds of objects get created, and who’s responsible for creating them.

Most of the patterns allow us to abstract away the concrete types a client uses so that the client depends only on the abstraction, that is the interface, rather than the concrete types themselves. This makes adding new types and behaviors a lot easier and error-prone.

5.1. Problems with inheritance

problems with inheritance
  1. Because we are overriding a lot of methods, we are not getting the benefits of inheritance like code reuse

  2. We have code duplicated across classes like no flying and quacking overriding

  3. It is hard to get the whole knowledge about ducks by looking at the super class

  4. Changes can lead to unintended side effects

  5. All behaviour is assigned at compile time. Hence, runtime behaviour change is difficult

  6. Inheritance is powerful but it can lead to inflexible and fragile designs.

5.2. Interfaces for the rescue?

  1. An interface defines the methods an object must have inorder to be considered a particular type

  2. Interfaces allow different classes to share similarities. Not all classes need to have the same behavior, though.

ducks with interfaces
Problems
  1. It destroys code reuse - every duck will implement its own fly() and quack() method

  2. Hence, maintenance becomes a nightmare

  3. It doesn’t allow for runtime changes in behaviour

review of attempts
encapsulte what varies
Design Principle - Encapsulate what Varies.

If some aspect of your code is changing, that's a strong indication that you need to pull out those parts that are changing and to
separate them from the rest of your code.

By separating out the parts of your code that change, you can extend or alter them without affecting the rest of your code.

This principle is fundamental to almost every design pattern.
program to interfaces
Design Principle - Program to an interface, not an implementation
Clients remain unaware of the specific types of objects they use, as long as the objects adhere to the interface that clients expect.
e.g.
    [source] Duck duck = new MallardDuck();

6. GoF Design Patterns

6.1. Strategy Pattern

Type

Behavioural

Definition

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This lets the algorithm vary independently from clients that use it.

strategy pattern class diagram
Figure 1. Strategy pattern class diagram
strategy pattern ducks
Figure 2. Pattern Example - Ducks
strategy pattern phone camera app
Figure 3. Pattern Example - Phone Camera App
favor composition over inheritance
Figure 4. favor_composition_over_inheritance
Design Principle - Favor Composition over Inheritance
Instead of inheriting behavior, composition delegates the behavior to the composed object.
Leads to a more flexible and extensible design.
Allows for changing the behavior during runtime.

6.2. Adapter Pattern

Type

Structural

Definition

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

adapter pattern
Figure 5. Definition
adapter pattern class diagram
Figure 6. Class Diagram
adapter pattern example
Figure 7. Example
adapter pattern example foobarmotorco
Figure 8. Foo Bar Motor Co. Example
Adapters use Composition
  1. The client is composed with the adapter class, and the Adapter is composed with the adaptee.

  2. The adapter sits between the client and the adaptee.

  3. The adapter delegates calls to the adaptee, and returns any needed value.

  4. The advantage of the Adapter Pattern is you can add an adapter easily without having to modify the adaptee at all, and only modify the client to add the adapter.

  5. Useful when working with Vendor classes which we can’t modify.

public class ObjectAdapter extends ClassAdaptingTo {
    private ClassAdaptingFrom fromObject;
public ObjectAdapter(ClassAdaptingFrom fromObject) {
    this.fromObject = fromObject;
}
    // Overidden method
    public void methodInToClass(){
        fromObject.methodInFromClass();
    }
}
Use
  1. When you have incompatible class, create an Adapter and make it 'adapt'

  2. Good to implement Anti-Corruption Layer and Hexagonal/Orion Architecture

Design Principle - Loose Coupling
The observer pattern exemplifies the design principle of loose coupling.
Loosly coupled objects are objects that interact but donot know much about each other.
Helps us to minimize complexity of a scenario.
loose coupling
  1. The Subject/Publisher owns the copy of the data which makes the design cleaner than many objects owning the data.

  2. The Dependents/Subscribers/Observers gets notified when the data changes in the Subject/Publisher.

6.3. Observer pattern

Type

Behavioral

Definition

This pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.

observer pattern class diagram
Figure 9. Class Diagram
observer pattern publisher subscribers
Figure 10. Publisher and Subscribers
Design Principle - The Open-Closed Principle
Classes should be open for extension but closed for modification.
Ensures flexibility, maintainability and robustness because we can add new behavior without the risk of introducing bug in the existing code.

Whenever you want to add a functionality, you should create new classes and test those. Instead of changing existing code.

6.4. Decorator Pattern

Type

Structural

Definition

This pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

decorator pattern
Figure 11. Class Diagram
decorator pattern example
Figure 12. Example
decorator pattern how it works
Figure 13. How it works

Decorator pattern uses Composition in a different way than Strategy pattern.

Inheritance Advantages
  1. Powerful, but it can lead to inflexible designs

  2. All classes inherit the same behavior

Composition Advantages
  1. We can still 'inherit' behavior by composing objects

  2. We can make dynamic runtime decisions

  3. We can add new behavior without altering existing code

  4. We can include behaviors not considered by the creator

  5. The end result often proves fewer bugs and side effects, and flexible designs.

By using composition, we get flexibility in how we add capabilities (e.g. condiments) to our components (e.g. beverages). By using inheritance (a common beverage super type), we get the type structure we need to treat sub classes as the super class (treat coffees and decorated coffees both as beverages. So, we can decorate beverages multiple times and call get description and cost on basic coffees or decorated coffees.

6.5. Iterator Pattern

Type

Behavioural

Definition

This pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

iterator pattern class diagram
Figure 14. Class Diagram
iterator pattern example
Figure 15. Example

Aggregate objects collect Objects. E.g. Arrays, Java Collection classes like ArrayList, List, Set, Map, Dictionary

To iterate over an aggregate object using the Iterator pattern
  1. Ask the object for its iterator

  2. Use the iterator to iterate through the items in the aggregate.

  3. Iteration code now works with any kind of aggregate object.

Using built-in iterators
  • Java offers a built-in Iterator interface, Java.util.Iterator which is an implementation of the Iterator pattern.

public interface Iterator<E> {
    public boolean hasNext(); (1)
    public E next();    (2)
    public void remove();   (3)
  1. returns true if another item exists

  2. returns the next item

  3. removes the last returned item

More about Java.util.Iterator
  • The java.util.Iterator interface acts both as an interface that your own iterator classes can implement as well as the type of the Java collection classes built-in iterators.

  • Classes like ArrayList, Vector and LinkedList all have an iterator method that returns a ready built iterator with a type java.util.Iterator.

  • Java arrays don’t have built-in iterators.

  • For lists, a sub-class of Iterator called ListIterator provides some additional methods.

Java’s Iterable Interface

java.lang.Iterable can be implemented by classes that define a method called iterator() that returns an Iterator object.

Other Built-in Iterators
  • Built-in iterators in languages and used in statements while hiding the Iterator pattern and make it easy

  • Java’s enhanced for statement - used for Collections and arrays

    for (Animal a: animals) {
        a.makeSound()
    }
  • Python’s for/in statement - used for string, list and tuple

    for i in range(1,10):
        print(i)
  • JavaScript’s for/of statement - used in string, array, map, set

    for (let value of aggregate) {
        console.log(value)
    }
Design Principle - Single Responsibility Principle (SRP)
Definition:: A class should have only one reason to change.
Example:: Think of a restaurant. Every person has a specific role. The waiter is responsible for taking orders only. They don't cook for you.
Imagine a restaurant where the waiter takes your order, cooks for you, goes shopping and does the taxes. This is unmanageable.

Adhering to this principle minimizes the chances that a class is going to need to change in the future.

One thing to remember about giving responsibilities to a class, is that for every additional responsibility, a class has another reason it might have to change in the future. So by giving a class multiple responsibilities we give the class more than one reason it might have to change.

6.6. Factory Method Pattern

Type

Creational

Definition

The Factory Method pattern defines an interface for creating an object, but lets subsclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

6.6.1. Simple Factory Pattern

This is NOT a GoF pattern .Class Diagram image::images/simple_factory_pattern_class_diagram.png[] .Simple Factory Pattern Example image::images/simple_factory_pattern_example.png[] .Simple Factory Pattern Code Example image::images/simple_factory_pattern_code_example.png[] When we see code like the above, we know that if requirements change, and we want to add new duck types, we’re going to have to open up this code and change it and that violates the open closed principle. We might also end up writing this same code in several places in this application, making the situation even worse.

Simple Factory pattern allows us to decouple the process of creating objects from the clients that use those objects. Hence, we respect 'Encapsulate what varies principle.'

6.6.2. Factory Method

factory method pattern class diagram
Figure 16. Class Diagram
factory method pattern example
Figure 17. Example
factory method practical example
Figure 18. Practical Example
When to use Factory Method?

When we know we need to create a new concrete object, but we can’t predict which kind of concrete object to make. Start by creating a Simple Factory then iterate.

Subclasses are given possibility of what object to create.

Design Principles and Factory Method
  1. Encapsulate what varies

  2. Program to an interface, not an implementation

  3. Open for extension, Closed for modification

  4. Depend on Abstractions

Difference between Factory Method and Abstract Factory Pattern
Factory Method

Uses inheritance and relies on a subclass to handle the desired object instantiation. It is Class Creational pattern.

Abstract Factory Pattern

a class delegates the responsibility of object instantiation to another object (client) via Composition. It is Object Creational pattern

6.7. Abstract Factory Pattern

Type

Creational

Definition

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

abstract factory pattern class diagram
Figure 19. Class Diagram
abstract factory pattern example
Figure 20. Example
When to use Abstract Factory Method?

Imagine a scenario where you have a system that supports users with different roles. We use the abstract factory pattern whenever we have a system that must be independent of how its products are created and represented, and the system is configured with one of multiple families of products. If we decide the client should use a different family of products, all we have to do is switch out which factory the client is using to create the products and the new family of products is created for us.

Design Principles and Abstract Factory Pattern
  1. Loosely coupled

  2. Similar principles like the Factory Method Pattern:

    1. Encapsulate what varies

    2. Program to an interface, not an implementation

    3. Depend on Abstractions

Advantage
  1. Identical code for all sub-classes in the client.

  2. Only requirement is to create the correct factory.

Disadvantage
  1. Adding new features reqquires changes in many classes.

6.8. Builder Pattern

Type

Creational

Definition

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

builder pattern example
Figure 21. Example

That means we want to allow the client the flexibility to create different representations of the same kind of object.

Difference between Builder and Factory Pattern
Factory Pattern

Concerned with encapsulating the decision about what type of products to create.

Builder Pattern

Concerned with encapsulating the complexities of how we build an individual object.

Flexibility in configuration
  • The director uses a builder to build a product step by step. So, the builder interface must be flexible and general enough to support a variety of concrete builders and the products they make. By creating a general interface, we are building in flexibility to the builders we use.

  • Using builder gives us fine control over the construction process by splitting the process into steps and giving control of that process to the director.

Design Principles and Builder Pattern

The builder keeps our code open for extension by supporting a variety of concrete builders. And by using a common interface for the builders keeps the code in the client and the director closed for modification.

When to use
  1. When you want to construct objects in multiple ways

6.9. Prototype Pattern

Type

Creational

Definition

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying the prototype.

A Prototypical Instance is an object we’ve created that will be used to make copies of itself to create new objects.

Why Prototype?
  • When we copy an existing object, we get the complex setup for free.

  • We don’t need to know the concrete class of the object we’re creating

  • The client is independent of how an object is created because it’s up to the object to make a copy of itself

Design Principles and Prototype Pattern

Program to the interface, not the implementation principle so the client can refer to the existing prototype using an interface, ask the prototype to create a copy of itself, and get a new object back.

Use

This can be handy in situations where you are creating objects on the fly, or based on user input, for instance. Used to improve object instantiation time.

Implementation
  1. Use a built in method In Java, objects that implement the Cloneable interface use a clone() method to copy themselves.

  2. Create a new instance of the class and then copy the properties of the prototype object into that new object. As long as what you return to the client is a new instance of that object type, the client doesn’t need to know or care how that new instance is made.

Comparing Factory Method and Prototype Patterns

In the Factory Method pattern, we have a parallel hierarchy of classes i.e. the Factory Hierarchy, and the Product Hierarchy. These two Hierarchy’s mirror one another, because each Factory produces a different product. With Prototype pattern, we have no need for a parallel hierarchy because the type of object instance we get, is determined by the type of object we clone. This can be decided at run time, based on state.

Benefits of Prototype Pattern
  1. Prototype is particularity helpful when we’re creating objects that are complex, or expensive to create new.

  2. Encapsulates object creation inside a prototypical instance object

  3. Client depends on the prototype abstraction, and not the concrete types of the objects created from the prototype, or the details of how new objects of that type are created.

  4. Java’s `Cloneable interface allows objects to clone themselves with the clone() method.

  5. JavaScript has direct support for object prototypes. In JavaScript, we can create new objects directly from other objects using Object.create() and copy properties from object to another using Object .assign(). Unlike Java, JavaScript uses prototypal inheritance meaning that all objects inherit directly from other objects.

6.10. Singleton Pattern

Type

Creational

Definition

Ensure a class only has one instance, and provide a global point of access to it.

Class Diagram

image::images/singleton_pattern_class_diagram.png

NOTE

One caveat with Singleton. The implementation of this pattern is somewhat language-dependent. When you’re implementing a Singleton, think about how to make your code thread safe so you don’t have to worry about multiple threads creating more than one instance of the Singleton.

Design Principles and Singleton Pattern
  1. Encapsulate what varies

  2. Single Responsibility Principle

  3. NOT loosely coupled - common criticism of Singleton

6.11. Memento pattern

Type

Behavioural

Purpose

Without violating encapsulation, capture and externalise an object’s internal state so that it cn be restored to this state later.

memento pattern
Figure 22. Pattern UML
memento pattern example
Figure 23. Pattern Example

6.12. State Pattern

Type

Behavioural

Definition

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

state pattern class diagram
Figure 24. Class Diagram
state pattern example
Figure 25. Example

6.13. Bridge Pattern

Type

Structural

Definition

Decouple an abstraction from its implementation so that each may vary independently.

bridge pattern example foobarmotorco
Figure 26. Example
Use

Very useful when you have 'extensions' or new behaviors that extend the abstract class and extend it by defining new methods.

6.14. Composite Pattern

Type

Structural

Definition

Compose Objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

6.15. Facade Pattern

Type

Structural

Definition

Provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

Client programs only need to invoke the method on the instance of the Facade. Therefore, the Client needs no knowledge of what should be done and what other objects are created. In a special circumstance, the individual methods are still available for calling as required. If a new process is needed, then only the Facade needs to change. Hence, details are hidden from the client.

6.16. Flyweight Pattern

Type

Structural

Definition

Use sharing to support large numbers of fine-grained objects efficiently.

The Flyweight pattern allows you to reference a multitude of objects of the same type and having the same state, by only instantiating the minimum number of actual objects needed.

This is typically done by allocating a pool of objects which can be shared and this is determined by a Flyweight Factory class.

Use
  1. When you want to avoid creating same objects again and again.

  2. Good to reuse same object

  3. Memory efficient.

6.17. Proxy Pattern

Type

Structural

Definition

Provide a surrogate or placeholder for another object to control access to it.

The Proxy pattern provides a stand in object until time-consuming resources are complete, allowing the rest of your application to load. Proxy pattern is useful if you cannot modify the original source for some reason.

How can you 'force' client programs to use the Proxy class instead of the normal class for object instantiation?

One approach is to make the constructors of the instantiable classes (E.g. StandardEngine and TurboEngine) package-private (i.e. using no access modifier); then the provided Proxy (e.g. EngineProxy) is in the same package, it will be able to instantiate them but outside objects won’t.

It is also common to have a 'factory' class to make instantiation simpler. E.g. by providing a createStandardEngine() method.

6.18. Chain of Responsibility Pattern

Type

Behavioural

Definition

Avoid coupling the sender of the request to its receiver by giving more than one object a change to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

This pattern allows us to define a separate handler objects that all conform to a Handler interface. This enables us to keep each handler independent and loosely-coupled.

6.19. Command Pattern

Type

Behavioural

Definition

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

The Command pattern allows us to uncouple an object making a request from the object that receives the request and performs the action, by means of a 'middle-man' object known as a 'command object'.

Typical uses of the Command Pattern
  1. One of the most frequent uses of is in UI toolkits. These are pre-built components like graphical buttons and menu items that cannot possibly know ehat needs to be done when clicked, because that is always specific to your application. E.g. in Java, this is implemented through the Action interface’s actionPerformed()

GUIs often define both a menubar and a toolbar icon that perform the same action, where a single command object handles the action performed.

  1. Another aspect is the provision of an undo mechanism by storing the state of the object prior to performing the code in the execute() method.

7. Applying patterns

  • Design patterns can be powerful.

  • Don’t think of patterns as a magic bullet. Patterns aren’t the solution to every problem. Once you’ve found a pattern that appears to be a good match, make sure it has a set of consequences you can live with and study its effects on the rest of your design. Use patterns when you have a practical need to support change in a design today.

  • KISS

    • Remember, always solve things in the simplest way you can. Other developers will appreciate and admire the simplicity of your design.

  • Design principles and patterns give us some useful tools that help us create software that is truly more flexible and resilient to change.

  • Refactoring time is pattern time.

  • If you don’t need to use a pattern now, don’t use it now. Otherwise, you end up with 'Design smell' in your project i.e. overly complicated design.

Simplicity is the Ultimate Sophistication
— Leonardo Da vinchi
Don’t abuse the design patterns!
— Mosh

About

Design Patterns Notes and Code

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages