Object-Orientation - Design Patterns in Software Development
Common problems and their solutions.
Overview
Concept
Design Patterns are good quality re-usable solutions to common problems.
In other words, Design Patterns represent a set of best practices for particular contexts, often dealing with how objects should be created, composed, and how they should be designed to intercommunicate to solve common problems.
What they offer
Design Patterns describe a particular design problem.
They do not prescribe a solution. Rather, they indicate a set of values to guide the designer toward a solution to the problem that is best for their particular situation.
Nature in Flux
There is no complete set of design patterns.
- Some are well-established for particular contexts
- Others are made up all the time and disappear as quietly as they came
- Some patterns can be made redundant in languages that have built-in support for solving the problem.
History
Philosophy
The way is long if one follows precepts, but short and helpful, if one follows patterns.
Seneca the Younger spoke of the value of abstract patterns versus their concrete implementations circa 65 AD in his letter to his friend, Lucilius, now referred to as “On Sharing Knowledge”:
Architecture
A Pattern Language, by Christopher Alexander, Sara Ishikawa, Murray Silverstein
- A seminal book published in 1977
- Layed out the concept of a pattern language, as applied to the architecture of progressively larger structures - nooks, rooms, homes, neighborhoods, and cities.
- Inspired software engineers, including Kent Beck (inventor of Unit Testing) and Ward Cunningham (creator of first-ever wiki) to apply its concepts to the realm of code.
Architecture (continued)
Architecture (continued again)
Software
Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides
An equally-seminal book published in 1994
- Colloquially known as the “Gang of Four” book after its four authors
- First popular application of the concept of pattern languages to object-oriented software in Java
- Still the standard reference today, and commonly discussed in job interviews!
Software (continued)
The Gang of Four outlined 23 object-oriented design patterns:
- Creational - patterns related to the creation of objects
- Structural - how classes are designed using inheritance, composition, and aggregation
- Behavioral - communication between objects as a program is running
Criticisms
Concept
Most people agree that programming design patterns are useful and that following such patterns in known situations leads to better code that is more robust and easier to maintain by teams.
However, design patterns have their critics…
Language Flaws
A common criticism of Design Patterns is that their necessity indicates flaws or ommissions in the underlying programming language they are designed for.
In fact, some of the design patterns identified in the Gang of Four’s book about the Java language have been resolved by features that are native to other programming languages.
Overly-complicated
Another criticism holds that Design Patterns can lead to overly complex and verbose code, when a simpler unpatterned solution might be simpler and easier to maintain.
Creational
Overview
The following creational design patterns, from the Gang of Four book, describe ways to create objects in various circumstances.
Prototype
A fully initialized instance to be copied or cloned.
Useful when the setup of an object is time/resource consuming, and you do not want to repeat it more than once, if possible.
-
Real world example: the starting position of a chess game. There is a certain position of all pieces necessary to start the game. An object can be created with this setup, and then this object can be cloned for each subsequent chess game.
-
Programming world example: Javascript has no ‘classes’. Instead, inheritance or replicating of objects is handled through prototypes, a built-in feature in the Javascript language. This is designed to make the Prototype pattern the ‘regular’ way of doing things in Javascript, unlike many other languages. The prototype behind an object can be accessed by calling Object.getPrototypeOf(object). Any changes to this prototype affect all other objects that are based on this prototype. Read more.
Prototype (continued)
Python example:
import copy
class Dog:
def __init__(self, name, breed, age):
self.name = name
self.breed = breed
self.age = age
def bark(self):
print(f"{self.name}, the {self.age} year old {self.breed}, says 'Woof!'")
def clone(self):
return copy.deepcopy(self)
dog1 = Dog("Fido", "German Shepherd", 6)
dog2 = dog1.clone()
dog2.bark() # "Fido, the 6 year old German Shepherd, says 'Woof!'"
Builder
Separates object construction from its representation.
The builder pattern provides a simple interface to create an object that actually has a complex structure.
Useful when there’s a complex object structure that is complicated to build.
-
Real world example: a multi-course dinner. Such a dinner consists of a drink, starters, main course, and a dessert. The composition of this object is complicated, but the ‘user’ of the code can use a simplified builder to put it together.
-
Programming world example: in Java, an example of a Builder pattern included in the Java API is the StringBuilder class. This is a helper class used for instantiating and setting up Strings, which are shockingly difficult to manipulate directly in Java code.
Builder (continued)
Python example:
class Dog:
def __init__(self):
self.name = None
self.breed = None
self.age = None
def __str__(self):
return f"{self.age} year old {self.breed} named {self.name}"
class DogBuilder:
def __init__(self):
self.dog = Dog()
def set_name(self, name):
self.dog.name = name
return self
def set_breed(self, breed):
self.dog.breed = breed
return self
def set_age(self, age):
self.dog.age = age
return self
def build(self):
return self.dog
builder = DogBuilder()
dog = builder.set_name("Fido").set_breed("German Shephderd").set_age(6).build()
print(dog) # '8 year old German Shepherd named Fido'
Singleton
A class of which only a single instance is allowed to exist.
For example you have one object that represents the entire running application or service… there is only one instance at any time.
Singleton (continued)
Python example of a Dog singleton where only 1 dog ever exists, named Fido, a 6 year old German Shepherd.
class Dog:
_instance = None # will store the only instance of this class
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.name = "Fido"
cls._instance.breed = "German Shepherd"
cls._instance.age = 6
return cls._instance
def bark(self):
print(f"{self.name}, the {self.age} year old {self.breed}, says 'Woof!'")
dog1 = Dog()
dog2 = Dog() # dog2 is the same object as dog1
Factory
Separates the instantiation of objects from their use. Makes it streamlined to create objects and can allow hiding of objects that there is no reason to know about.
Python example:
class Dog:
def __init__(self, name, breed, age):
self.name = name
self.breed = breed
self.age = age
def bark(self):
print(f"{self.name}, the {self.age} year old {self.breed}, says 'Woof!'")
class DogFactory:
def create_dog(self, name, breed, age):
return Dog(name, breed, age)
factory = DogFactory()
dog = factory.create_dog("Fido", "German Shepherd", 6)
dog.bark() # "Fido, the 6 year old German Shepherd, says 'Woof!'"
Structural
Proxy
An object representing another object. It’s an imposter! Hides the complexity of the object it represents.
Example: credit card is a proxy for a bank account.
Programming world example: Mongoose is a framework that streamlines interacting with a MongoDB database from a Node.js application. The Node.js application issues simplified commands to Mongoose, which in turn validates those commands, modifies them as necessary, and relays them to the MongoDB instance. This is a form of proxying.
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost/test:27017') // connect to database
const Dog = mongoose.model('Dog', { name: String, breed: String, age: Number }) // design a data model
const fido = new Dog({ name: 'Fido', breed: 'German Shepherd', age: 6 }) // instantiate an object from that model
fido.save().then(() => console.log('Woof!')) // save that object to database
Facade
A single class that represents the entire subsystem.
Useful for web applications. Reducing network calls. Reduces coupling (between web layer and back-end). Helps rollback all steps if one fails.
Example: Event manger. Imagine organizing an event - it requires management of the decorations, food categering, invitations, music group, etc. The facade is such a manager that takes care of doing all the things necessary.
Example: online book order. The user might click a single button that on the back-end involves checking whether the book is in stock, reserving the book, making payment, updating the stock, generating an invoice, etc. The facade pattern allows a separation between all these complicated steps and where they are called. So a single call can trigger all of them.
Facade (continued)
Python example:
class WebServer:
def __init__(self):
self.inventory = InventorySystem()
self.reservations = ReservationsSystem()
self.payment = PaymentSystem()
def order_book(self, request):
if self.inventory.available(request) and self.reservations.reserve(request):
try:
self.payment.charge(request)
self.inventory.reduce(request)
return True
except PaymentError:
self.reservations.cancel(request)
return False
Decorator
Adds responsibility to objects dynamically. Makes it easy to add behavior at runtime.
Example: imagine a pizza shop with 10 types of pizza. The pizza could be thought of as one code class. Then imagine that this shop decides to allow 3 types of toppings. This would require 3 new code classes. Then imagine this shop decides to allow 3 different sizes of these toppings: small, medium, and large. These would require more classes that probably inherit from other topping classes. When creating the pizza class, you wouldn’t possibly imagine that it needs to support all these different classes.
The decorator pattern would allow the toppings to be dynamic. Rather than put the toppings on the pizza, the decorator pattern would have the toppings be created, and the pizza added to them.
Behavioral
Chain of responsibility
A way of passing a request between a chain of objects.
A message passes through a series of objects until one of them is able to handle it, at which point is doesn’t get passed any further.
Iterator
Sequentially access the elements in a collection.
Useful when the internal representation of a collection of objects isn’t important, and you simply want to be able iterate through the objects in the collection.
Observer
A way of notifying a change to an object to a number of other objects. Objects can be registered to receive notifications.
Situation: When one of the objects changes, it needs to let several of the other objects know.
Conclusions
This has been a brief, but inspirational introduction to design patterns.
- Thank you. Bye.