The below notes are based on the book Designs Patterns - Elements of Reusable Object-Oriented Software. The idea is that all well structured object-oriented architectures are full of patterns. When you design a system, you need to focus on the common collaborations among its objects (i.e. its design pattern). The book shows you the importance that patterns can play in architecting complex systems, then it provides a reference of well-engineered patterns that a developer can apply to their own applications.
Designing reusable object-oriented software is hard. Recording the experience of designing object-oriented software
is known as design patterns, that way we can reuse successful designs and architectures. For a more specific
definition, we’ll say that design patterns are descriptions of communicating objects and classes that are customized
to solve a general design problem in a particular context
. Each design pattern identifies participating classes
and instances, their roles and collaborations, and the distribution of responsibilities.
Besides software engineering, you can see design patterns used. Playwrights don’t write plots from scratch. They normally follow a pattern like the ‘Tragically Flawed Hero’ or ‘The Romantic Novel’. Once you know the patterns, a lot of design decisions follow automatically.
A design pattern has four essential elements:
Design Patterns fall into three categories.
The above categories are classified based off two criteria:
1.) Purpose reflects what a pattern does
Creational patterns are about object creation. Structural patterns deal with the composition of classes or objects Behavorial patterns characterize the ways in which classes or objects interact and distribute responsibility
2.) Scope specifies whether the pattern applies primarily to classes or to objects. For Python, everything is an object while classes are essentially a template to create your objects
Class patterns deal with relationships between classes and their subclasses; these relationships are established through inheritance, so they are static - fixed at compile-time.
Object patterns deal with object relationships, which can be changed at run-time and are more dynamic.
Almost all patterns use inheritance so some extent so the only patterns labeled ‘class patterns’ are those that focus on class relationships. Most patterns are thus labeled in the Object patterns.
-------------------------------------------------------------------
| Purpose |
-------------------------------------------------------------------
| Creational | Structural | Behaviroal |
------------------------------------------------------------------------------------
|Scope | Class | Factory Method | Adapter (class) | Interpreter |
| | | | | Template Method |
| -----------------------------------------------------------------------------
| | Object | Abstract Factory | Adapter (object) | Chain of Responsibility |
| | | Builder | Bridge | Command |
| | | Prototype | Composite | Iterator |
| | | Singleton | Decorator | Mediator |
| | | | Facade | Memento |
| | | | Flyweight | Observer |
| | | | Proxy | State |
| | | | | Strategy |
| | | | | Visitor |
----------------------------------------------------------------------------------
Common design patterns include:
Out of the above, the simplest and most common patterns are:
Creational design patterns abstract the instantiation process. Why is this important? As systems evolve, they depend more on object composition than class inheritance. That means when you crate objects with specific behaviors, it requires more than just instantiating a class.
The below five creational patterns are closely related; let’s see what this means for building a maze for
a computer game. Let’s assume a maze is a set of rooms and the maze knows its neighbors, which are represented
by classes for Room
, Door
, and Wall
(which has a common abstract class of MapSite
).
So let’s say we define a MazeGame
class that creates the maze. We can setup this maze a few ways:
But what if we need to create an EnchantedMaze
class that has new components like DoorNeedingSpell
or when we call the funciton to add a room, we now need an EnchantedRoom
instead of a regular Room
?
The creational patterns provide different ways to remove explicit references to concrete classes from
code that needs to instantiate them.
CreateMaze
passed an object as a parameter to use to create rooms, then
you can change the classes of rooms by passing a different parameterCreateMaze
is passed an object that can create a new maze in its entirety using
operations for adding rooms to the maze it builds, then you can use inheritance to change parts of the
mazeCreateMaze
calls virtual functions instead of constructor calls to create
the rooms, then you can change the classes that get instantiated by making a subclass of MazeGame
and redefining those virtual functionsCreateMaze
is parameterized by various prototypical room objects, which it then copies
and adds to the maze, then you can change the maze by replacing these prototypical objects with a different oneIntent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes
Motivation: An example of an Abstract Factory would be a user interface toolkit that supports multiple look =-and-feel standards. You can define different look-and-feel appearances and behaviors for various user interface ‘widgets’ like scroll bars, windows, and buttons.
Solution: We define an abstract WidgetFactory
class that declares an interface for creating each basic
kind of widget. We have an abstract class for each kind of widget and concrete subclasses
implement widgets for specific look-and-feel standards. The WidgetFactory interface has an operation
that returns a new widget object for each abstract widget class. Clients call these operations
to obtain widget instances (but clients aren’t aware o the concrete classes they’re using, meaning
that clients stay independent of the look and feel).
So what does this do? Clients create widgets through the WidgetFactory interface. There is no knowledge
of the classes that implement widgets for a particular look and feel. The clients only have to commit
to an interface defined by an abstract class (not a particular concrete class).
Application: Use the AbstractFactory pattern when:
Partipants:
Consequences: The Abstract Factory pattern has the following benefits and liabilities:
Structural Patterns are concerned with how classes and objects are composed to form larger structures. Structural class patterns use inheritance to compose interfaces or implementations.
Think how multiple inheritance mixes two or more classes into one. The result is a class that combines the properties of its parent class. This might be useful for making independently developed class libraries to work together. An example code snippet in Python might look like:
class Subclass(BaseClass1, BaseClass2, BaseClass3, ...):
pass
Another example is the Adapter class. The adapter makes one interface (the adaptee’s) conform to another, providing a uniform abstraction of different interfaces. Say you have “x” and you need “y”, then Adapter solves this problem. There are a number of ways to accomplish it, but here is the general idea with an example code snippet:
class WhatIHave:
def g(self): pass
def h(self): pass
class WhatIWant:
def f(self): pass
class MyAdapter(WhatIwant):
def __init__(self, whatIHave):
self.whatIHave = whatIHave
def f(self):
# implement behavior using methods in WhatIHave
self.whatIHave.g()
self.whatIHave.h()
class WhatIUse:
def op(self, whatIWant):
whatIWant.f()
Another example of a structural pattern is Facade, where we apply the rule “If something is ugly, hide it inside an object”. Usually this is implemented as a singleton abstract factory. You might use this if you have a confusing collection of classes and interactions that the client programmer doesn’t really need to see, you can create an interface that is useful for the client programmer and that only presents what’s necessary.
The above ‘Creational’, ‘Structural’, ‘Behaviorla’ are design patterns from ‘The Gang of Four’. There’s also a few architectural patterns and styles:
In web applications, we commonly see the Model/View/Controller (MVC) classes used to build user interfaces. We have:
MVC allows decoupling of views and models by establishing a subscribe/notify protocol between them. A view must ensure that its appearance reflects the state of the model. When a model’s data changes, the model notifies views that depend on it. In response, each view gets an opportunity to update itself. You can then attach multiple views to a model to provider different presentations.
We can see this as a general pattern of decoupling views from models, or we can see this apply to a more general problem: decoupling objects so that changes to one can affect any numer of others without requiring the changed object to know details of the others, which is known as the Observer Pattern.
MVC allows views to be nested. An example might be a control panel of buttons that appears as a view containing a lot of button views. We can think of this user interface that groups together multiple views as a Composite View, basically treating the Composite View like one of the nested Views. This general design pattern is the Composite Pattern, which lets you create a class hierarchy where some subclasses define primitive objects (e.g. Button) and other classes define composite objects (e.g. Composite View) that assembles the primitives into more complex objects. Basically, a group of objects is treated the same way as a single instance of the same type of object.
MVC’s View uses an instance of the Controller subclass to implement a particular response strategy; to implement a different strategy, you just replace the instance with a different kind of controller. For example, a view can be disabled so that it doesn’t accept any input by giving it a controller that ignores input events.
MVC has a View-Controller relationship that is an example of the Strategy Pattern. A Strategy is an object that represents an algorithm. It’s useful when you want to replace the algorithm either statically or dynamically, when there’s a lot of variants of the algorithm, or when the algorithm has complex data structures you want to encapsulate.
MVC might have a View that needs the scrolling functionality. We can use the Decorator Pattern to add scrolling capabilities to a single object.
MVC might have a Controller that specifies the default Controller Class for a View. We can use the Factory Pattern to create objects without having to specify the exact class of the object that will be created.
Here’s several approaches to finding the design pattern that’s right for your problem:
So let’s take a step back and look at what makes up object-oriented programs: objects.
An object has both data and the procedures that operate on that data.
Requests are the only way to get an object to execute an operation. Operations are the only way to change an object’s internal state. Because of these restrictions, the object’s internal state is said to encapsulated; it cannot be accessed directly and its representation is invisible from outside the project.
You can do object-oriented design a few different ways:
Objects are known only through their interfaces. So what’s an interface?
Every operation declared by an object specifies the operation’s name, the objects it takes as parameters, and the operation’s return value (aka the operator’s signature). The set of all signatures defined by an object’s operations is called the interface to the object.
A type is a name used to denote a particular interface. An object can have many types. A type is a subtype of another if its interface contains the interface of its supertype. Often you’ll hear of a subtype inheriting the interface of its supertype
Adapter (aka __wrapper) lets classes work together that normally couldn’t because of incompatible interfaces.
Here’s a list of design principles, which all you to ask questions about your proposed design.