- 1. Object Oriented Programming Concepts
- 2. Design Patterns
- 3. Design Principles vs Design Patterns
- 4. Design pattern categories
- 5. Creational Patterns
- 6. GoF Design Patterns
- 6.1. Strategy Pattern
- 6.2. Adapter Pattern
- 6.3. Observer pattern
- 6.4. Decorator Pattern
- 6.5. Iterator Pattern
- 6.6. Factory Method Pattern
- 6.7. Abstract Factory Pattern
- 6.8. Builder Pattern
- 6.9. Prototype Pattern
- 6.10. Singleton Pattern
- 6.11. Memento pattern
- 6.12. State Pattern
- 6.13. Bridge Pattern
- 6.14. Composite Pattern
- 6.15. Facade Pattern
- 6.16. Flyweight Pattern
- 6.17. Proxy Pattern
- 6.18. Chain of Responsibility Pattern
- 6.19. Command Pattern
- 7. Applying patterns
- 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.
-
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
|
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.
-
Not reinventing the wheel
-
Building resilient code that can withstand change
-
Prepare for future additions and change
-
Serves as shared vocabularies
-
Patterns allow you to say more with less
-
Talking at the pattern level allows you to stay 'in the design' longer
-
Shared vocabularies can turbo-charge your development team - go faster and less misunderstanding
-
Shared vocabularies encourage more junior developers to get up to speed.
-
- 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.
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.
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.
-
Because we are overriding a lot of methods, we are not getting the benefits of inheritance like code reuse
-
We have code duplicated across classes like no flying and quacking overriding
-
It is hard to get the whole knowledge about ducks by looking at the super class
-
Changes can lead to unintended side effects
-
All behaviour is assigned at compile time. Hence, runtime behaviour change is difficult
-
Inheritance is powerful but it can lead to inflexible and fragile designs.
-
An interface defines the methods an object must have inorder to be considered a particular type
-
Interfaces allow different classes to share similarities. Not all classes need to have the same behavior, though.
-
It destroys code reuse - every duck will implement its own fly() and quack() method
-
Hence, maintenance becomes a nightmare
-
It doesn’t allow for runtime changes in behaviour
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.
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();
- 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.
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.
- 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.
-
The client is composed with the adapter class, and the Adapter is composed with the adaptee.
-
The adapter sits between the client and the adaptee.
-
The adapter delegates calls to the adaptee, and returns any needed value.
-
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.
-
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(); } }
-
When you have incompatible class, create an Adapter and make it 'adapt'
-
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.
-
The Subject/Publisher owns the copy of the data which makes the design cleaner than many objects owning the data.
-
The Dependents/Subscribers/Observers gets notified when the data changes in the Subject/Publisher.
- 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.
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.
- Type
-
Structural
- Definition
-
This pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Decorator pattern uses Composition in a different way than Strategy pattern.
-
Powerful, but it can lead to inflexible designs
-
All classes inherit the same behavior
-
We can still 'inherit' behavior by composing objects
-
We can make dynamic runtime decisions
-
We can add new behavior without altering existing code
-
We can include behaviors not considered by the creator
-
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.
- Type
-
Behavioural
- Definition
-
This pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Aggregate objects collect Objects. E.g. Arrays, Java Collection classes like ArrayList, List, Set, Map, Dictionary
-
Ask the object for its iterator
-
Use the iterator to iterate through the items in the aggregate.
-
Iteration code now works with any kind of aggregate object.
-
Java offers a built-in
Iterator
interface,Java.util.Iterator
which is an implementation of theIterator
pattern.
public interface Iterator<E> { public boolean hasNext(); (1) public E next(); (2) public void remove(); (3)
-
returns true if another item exists
-
returns the next item
-
removes the last returned item
-
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
calledListIterator
provides some additional methods.
java.lang.Iterable
can be implemented by classes that define a method called iterator()
that returns an Iterator
object.
-
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.
- 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.
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.'
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.
-
Encapsulate what varies
-
Program to an interface, not an implementation
-
Open for extension, Closed for modification
-
Depend on Abstractions
- 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
- Type
-
Creational
- Definition
-
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
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.
-
Loosely coupled
-
Similar principles like the Factory Method Pattern:
-
Encapsulate what varies
-
Program to an interface, not an implementation
-
Depend on Abstractions
-
-
Identical code for all sub-classes in the client.
-
Only requirement is to create the correct factory.
-
Adding new features reqquires changes in many classes.
- Type
-
Creational
- Definition
-
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
That means we want to allow the client the flexibility to create different representations of the same kind of object.
- 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.
-
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.
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 you want to construct objects in multiple ways
- 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.
-
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
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.
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.
-
Use a built in method In Java, objects that implement the
Cloneable
interface use aclone()
method to copy themselves. -
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.
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.
-
Prototype is particularity helpful when we’re creating objects that are complex, or expensive to create new.
-
Encapsulates object creation inside a prototypical instance object
-
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.
-
Java’s `Cloneable
interface allows objects to clone themselves with theclone()
method. -
JavaScript has direct support for object prototypes. In
JavaScript
, we can create new objects directly from other objects usingObject.create()
and copy properties from object to another usingObject .assign()
. UnlikeJava
,JavaScript
uses prototypal inheritance meaning that all objects inherit directly from other objects.
- Type
-
Creational
- Definition
-
Ensure a class only has one instance, and provide a global point of access to it.
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.
-
Encapsulate what varies
-
Single Responsibility Principle
-
NOT loosely coupled - common criticism of Singleton
- Type
-
Behavioural
- Definition
-
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
- Type
-
Structural
- Definition
-
Decouple an abstraction from its implementation so that each may vary independently.
Very useful when you have 'extensions' or new behaviors that extend the abstract class and extend it by defining new methods.
- Type
-
Structural
- Definition
-
Compose Objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
- 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.
- 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.
-
When you want to avoid creating same objects again and again.
-
Good to reuse same object
-
Memory efficient.
- 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.
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.
- 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.
- 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'.
-
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’sactionPerformed()
GUIs often define both a menubar and a toolbar icon that perform the same action, where a single command object handles the action performed.
-
Another aspect is the provision of an
undo
mechanism by storing the state of the object prior to performing the code in theexecute()
method.
-
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
Don’t abuse the design patterns!