Reference: EPI (Chapter 22)
A design pattern is a general repeatable solution
to a commonly occurring problem. In the context of object-oriented programming, design patterns address both reuse
and maintainability
. In essence, design patterns make some parts of a system vary independently from the other parts.
Template Method vs. Strategy
Both of them are behavioral patterns
that make algorithms reusable. They differ in the following key way:
- In the template method, a skeleton algorithm is provided in a
superclass
. Subclasses can override methods to specialize th algorithm. (Inheritance)- E.g.: Quicksort. Two of the key steps in quicksort are pivot selection and partitioning. Quicksort is a good example of a
template method
pattern since subclasses can implement thier own pivot selection algorithm, etc.
- E.g.: Quicksort. Two of the key steps in quicksort are pivot selection and partitioning. Quicksort is a good example of a
- The strategy pattern is typically applied when a family of algorithms implements a common
interface
. Such algorithms can be selected by clients. (Interface)- E.g.: Comparator in sorting. It makes the comparison operation used by the sorting algorithm an argument.
Some other differences: In the template method pattern, the superclass algorithm may have "hook"
(calls to placeholder methods that can be overridden by subclasses to provide additional functionality). Sometimes, a hook is not implemented forcing the subclasses the subclasses to implement that functionality; sometimes it offers a “no-operation” or some baseline functionality. There is no analog to a hook in a strategy pattern.
Observer Pattern (Push vs. Pull)
The observer pattern defines a one-to-many
dependency between objects so that one object changes state all its dependents are notified and updated automatically.
Note: This is the push observer
pattern.
The observed object
must implement:
- Register an observer
- Remove an observer
- Notify all currently registered observers
The observer object
must implement:
- Update the observer (or notify).
For example, instead of having the clients poll the service, the service (observed object) provides clients with register and remove capabilities. As soon as its (server) state changes, the service enumerates through registered observers, calling each observer’s update method. (in an active way)
There is another way to update data: Observers pull the information they need from the observed object.
With the pull design, it is the observer’s job to retrieve that information from the subject.
Push Design: It leaves all of the information transfer in the subject’s control. This design seems more object-oriented
, because the subject is pushing its own data out, rather than making its data accessible for the observers to pull. It is somewhat simpler
and safer
in that the subject always knows when the data is being pushed out. No synchronization problem.
Pull Design: It places a heavier load on the observers, but it also allows them to query the subject only when they need
. One important consideration is that by the time the observer retrieves the information from the subject, the data could have changed
(it is a positive or negative result). This design also requires that the subject makes its data public
for the observers. It would likely work better when the observers are running with varied frequency and it suits them best to get the data they need on demand.
Singleton & Flyweights
Singleton Pattern ensures
a class has only one instance, and provides a global point of access to it.
- E.g.: Logger. There may be many clients who want to listen to the logged data. (or database connection, server configurations)
Flyweight Pattern: minimizes
memory use by sharing as much data as possible with other similar objects.
- E.g.: String interning (a method of storing only one copy of each distinct string value). Interning strings makes some string processing tasks more time with space efficiency when the string is created or interned. The distinct values are usually stored in a hash table. Since multiple clients may refer to the same flyweight object, it should be immutable for safety.
Similarity: Both keep a single copy of an object.
Differences:
- Singletons are used to ensure all clients
see the same object
. Flyweights are used tosave memory
. - A singleton is used when there is a
single shared object
. A flyweight is used where there is afamily of shared objects
(objects describing character fonts, or nodes shared across multiple BSTs). - Singleton objects are usually
mutable
. Flyweight objects areimmutable
. - The singleton pattern is a
creational pattern
, whereas the flyweight is astructural pattern
.
In summary, a singleton is like a global variable, whereas a flyweight is like a pointer to a canonical representation.
Sometimes, a singleton object is used to create flyweights.
Class & Object Adapters
The adapter pattern allows the interface of an existing class
to be used from another interface (other classes), often without modifying their source code.
There are two ways to build an adapter: subclassing
(class adapter) and composition
(object adapter).
The class adapter
inherits both the interface that is expected and the one that is pre-existing, while the object adapter
contains an instance of the class it wraps and the adapter makes calls to the instance of the wrapped object.
Creational Patterns (Builder and Factory)
Creational Patterns: builder
, static factory
, factory method
, and abstract factory
.
Builder Pattern: The idea is to build a complex object in phases
. It avoids mutability and inconsistent state by using an mutable inner class that has a build method that returns the desired object. Its key benefits are that it breaks down the construction process, and can give names to steps. Compared to a constructor, it deals far better with optional parameters and when the parameter list is very long.
Static Factory: It is a function for construction of objects. The function’s name can make what it’s doing much clearer compared to a call to a constructor. The function is not obliged to create a new object. It can return a flyweight or a subtype that’s more optimized. For example, the method Integer.valueOf("123")
is a static factory. It caches values in the range $[-128, 127]$ that are already exist to save memory and reduce construction time.
Factory Method: It defines interface
for creating an object, but let subclasses decide which class to instantiate. Drawback: Make subclassing challenging.
1 | // MazeGame is an abstract class! |
Abstract Factory: It provides an interface for creating families of related objects without specifying their concrete classes. For example, a class DocumentCreator
could provide interfaces to create a number of products, such as createLetter()
and createResume()
. Concrete implementations of this class could choose to implement these products in different ways. Client code gets a DocumentCreator
object and calls its factory methods.
Libraries and Design Patterns
Question: Why is there no library of design patterns so a developer do not have to write code every time they want to use them?
- Reason #1: Patterns cannot be cleanly abstracted from the objects and the processes they are applicable to. Libraries provide the implementations of algorithms, while design patterns provide a higher level understanding of how to structure classes and objects to solve specific types of problems.
- Reason #2: It’s often necessary to use combinations of different design patterns. For example,
MVC
, which is commonly used in UI design, consists of the Observer, Strategy, and Composite patterns.s