What Are Object-Oriented Design Patterns?Definition A pattern of fixed class structure (think UML class sub-diagram) and messaging that repeatedly pops up as a particularly elegant and useful programming idiom.
- The original source for object-oriented design patterns: Gamma et al. Design Patterns book (click on link for JHU access), circa 1995. The authors are known as the "Gang of Four" ("Go4" below).
- The Head First Design Patterns (HFDPs, pronounced "Heff Dupps") (click for JHU access) textbook is a much better description/motivation of the patterns; we will use some of their examples below.
- The Refactoring.guru design patterns pages is a good on-line reference with additional examples.
- They are "advanced" / "clever" code structurings that you may not find on your own.
- They are needed often enough -- for the average OOSE project we see at least 3-4 patterns being helpful in the design.
- They are not hard and fast structures, each one is a theme you can apply variations to.
- There are other advanced design patterns beyond the ones we cover, but they tend to be more domain-specific. DO keep an eye out for them in your own coding.
- Beyond Object-Oriented design patterns there are also patterns of good design for how frameworks are interfaced to and composed, and for non-coding aspects of design in about any context (the design pattern idea originally came from the field of architecture).
Separation of concerns and design patterns
- A primary focus of good design is our well-known friend, separation of concerns
- In good code, every class / method should have a distinct focus of responsibility
- Separate each different concern into a different class or method.
- Many design patterns have as their goal an improved separation of concerns.
- Delegate (forward) some messages to other objects
- Replace switch with inheritance and dynamic dispatch (we saw this in refactoring)
- Reify an action/method (a verb) as an object (a noun) to manipulate it in new ways.
We are going to start with the patterns that focus more on separation of concerns since that is a major theme.
Go4 description: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Diagram for Java's built-in Observer Class - Java comes with a template to fill out if you want to use this pattern.
- This is a classic pattern pre-dating the Go4, when it was called "publish/subscribe" ("pub/sub"). The publisher is the object and the observers are its subscribers.
- The key advantage of observer is it decouples subjects and observers, they are only loosely acquainted. More separation of concerns.
- Subject can read/write its fields; Observer is a part that only needs to read the subject fields so can be decoupled into a separate class.
- Subject in particular does not need to concern itself with when and what particular fields the observer needs - the observer has a reference to the subject so can figure all that out on its own.
- Internally how it works: see the code.
- Observer of some sort is found in nearly all GUI libraries
- the model is the underlying logic (the weather data in the above)
- various views register as observer so they can update when data changes
- A particularly common method to use Observer in views is MVC, see below.
Go4 description: Attach additional responsibilities to an object
dynamically. Decorators provide a flexible alternative to
subclassing for extending functionality.
- Java IO Example - Java IO makes excellent use of decorators
- Here is a code example to make clear how the decorator "decorates"
- Notice in particular how the decorator method X calls the same method X on the "decoratee" and then does its thing with the result (or, does nothing extra); this is one of many examples of patterns using delegation for better separation of concerns.
- (Also note that the contract here requires calls to the superclass method as part of the contract; this is an instance of the Call super anti-pattern but there is no nice alternative here.)
Design Principle: The Open-Closed PrincipleThis principle is illustrated well by the decorator pattern; we covered it in the design principles lecture but will revisit it now.
- Decorator allows functionality to be added which are extensions which allowed the original codebase to not change the original code
- In particular notice how we are not inheriting to add functionality, the decorator is a new object forwarding messages to the decoratee
- I.e. the original code is "open to extension, closed to modification" -- OCP!
Go4 description: Encapsulate a request on an object, thereby letting one parameterise
clients with different requests, queue or log requests, and
support undoable operations.
- Command is for the case that there are many requests for some action which may themselves need to be controlled in some way (prioritize/delay/undo/etc).
- Book example: remote control commands for a remote which can control lights, garage door, ceiling fan, etc.
- In this diagram,
RemoteLoaderholds the set of commands,
LightOnCommand(plus the not shown
FanOnCommandetc etc etc) contains the code to perform the command via its
Commandis an abstract superclass so we can generically support a multitude of concrete commands.
- Since these commands are now objects we can put them on a log
queue, make macro commands (a
Listof commands can be a command), implement an
percentDone()on long-running commands to gauge progress, etc.
- In addition, the command structure is more decoupled from the actual operations: the remote doesn't need to know about fans or lights.
- Command is an instance of the reification meta-pattern: turn an action/method (a verb) a class (a noun).
Go4 description: 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 is needed when there is an interface,
but it isn't quite what you need and either you can't change it
or if you changed it to the way you wanted it would mess up
- It may have method names slightly different;
- It may be missing one of the methods and so some default value must be popped in for that method
- Some translation of the data may be needed, e.g. your interface expects rectangular coordinates for a point and the object you want to use expects polar coordinates.
- You may want to add some extra functionality in the
Decoratorspirit, e.g. log requests.
- Then, the solution in the above case is to create a new
intermediate object, the adapter, which serves to
adapt the interface for that object.
- and, the bad non-solution is not to make a new object solely reponsible for conversion but to inline the conversion code
- This non-solution doesn't factor the responsibilities as much as Adapter can - maximally separate concerns/responsibilities
- The new Adaptor object has one and only one responsibility: data conversion - focused responsibility is good!!
- The book has an example of how the old Java
Enumerationclass can be adapted to work like the new
Go4 description: Provide a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use.
- A facade turns a big bunch of objects into a component, by putting the interface of interaction with those objects on one object, the façade.
- The high-level interface abstracts away details that users of the objects don't need to be concerned with - yet another example of decoupling / separation of concerns
- Facade objects don't do significant work, they just delegate (forward messages) to other objects.
- Facades can also combine some adapting a la the Adapter pattern, such as supplying some of the arguments to lower-level methods, invoking multiple methods, etc.
- Example: Real-world home theater system example from book
- Before: user needs to interact with all the individual components
- After: the
HomeTheaterFacadeprovides the complete interface to all the classes to outsiders, including regular ways to interact with them that may be a complex series of events.
- code examples of how methods on facade can do a lot more than just forward messages to the underlying object(s)
the methods on the facade are at a higher level of abstraction.
Design Principle: The Principle of Least Knowledge
Talk only to your immediate friends
- Facade is a great pattern primarily because of how effectively it implements this principle.
- The "users" of the large collection of objects under the facade only need to talk to the facade object (their immediate friend) and not all of the other objects.
- This is also yet another instance of separation of concerns, the user can avoid having to understand the low-level details of the pieces.
Aside: Components"Component" is a word that gets abused by computer scientists; its actually a fairly simple idea.
A component is a "big object" -- it is a dynamic entity with an interface like an object (methods), but is more heavyweight: it may come with metadata, there may be multiple objects involved behind the scenes, it may be remote with an interface for networked interaction, etc.Examples: façades, Microsoft COM objects, RESTful web services, plugin modules, etc.
- Factories are methods creating objects rather than directly invoking
- Advantage of using interfaces or superclasses
as abstractions breaks down when you
new: it requires you to give the concrete class you are making, its impossible for
newto be parametric.
... Factories to the rescue: they allow factory-calling code to
create objects by invoking them instead of grubbing around with
Window w = aWindowFactory.makeWindow()can return a say
DarkWindow, both of which are
Windows, and the class with the above code need not be concerned with which it got back. Impossible to do with
A factory method is a generalization of a simple factory:
- There is a superclass or interface which has the factory or method declared
- Subclasses or implementers specialize this factory to create the kinds of objects they need
- Advantage over simple factory above is no switch statement is needed when the factory
decides what kind of object to make: the choice of subclass already made the decision.
-- recall switch is a smell, and we are removing a switch
Go4 description: Define an interface for creating an object, but let subclasses decide
which class to instantiate. Factory Method lets a class defer
instantiation to subclasses.
Design Principle: The Dependency Inversion Principle"High level code should not depend directly on low level code; instead, both should depend on an abstractions"
- In other words, put an interface in the middle! Separate those concerns!
- We skipped this principle in the Design Principles lecture since Factory Method is a really good iillustration.
- The initial pizza-making code without a factory required the top-level
PizzaStoreclass to itself choose the kind of pizza: see picture from book.
- Arrows in the above (read as "dependent-on") show high-level
code directly dependent on details of low-level code (it needs
their names to create them via
- This picture shows how the depenencies get inverted whan a factory method is used, meaning PizzaStore only deals with Pizza abstraction, not all the low-level details.
- The dependencies were inverted - D-I-P!
Provide an interface for creating families of related or dependent
objects without specifying their concrete classes.
- Abstract Factories for Pizzas and some Factory code
- The key difference here compared to Factory Method is the FAMILY of related classes getting created, and the factory is a separate CLASS.
- It probably doesn't deserve to be a design pattern, its just one of many variations possible on factories.
Go4 description: Provide a surrogate or placeholder for another object to control access to it.
- Proxy is a special case of delegation where nearly all methods of one object delegate to the same method in another object.
- Virtual Proxy: maybe actual object hasn't been created or loaded
-- for example when browsing thumbnails you don't need the full Picture object, a PictureProxy in the middle may only have the thumbnail loaded and can itself load the Picture into memory if needed)
- Proxies can be used for access control: e.g.
ReadOnlyGoobercould be a proxy class for a
Gooberwhich does not forward setter messages to
Goober, but forwards all others.
- Java RMI uses remote proxies for remote objects, and Hibernate uses proxies for database-stored objects.
Go4 description: Define the skeleton of an algorithm in an operation, deferring some
steps to subclasses. Template Method lets subclasses redefine
certain steps of an algorithm without changing the algorithm's
- Template method is needed if subclasses have methods that are similar (violating DRY) but also have different bits so it cannot be directly lifted to superclass.
- The template is an algorithm with code in the superclass but which has "holes" (steps not yet known at the superclass level) in it.
- The subclasses implement the "holes"
- The steps in the algorithm common to all implementations can be in the superclass (notice how this avoids code duplication)
- the subclasses provide the missing bits by instantiating some methods that were abstract in the superclass.
- Example code for coffee or tea brewing; diagram of this example
- See also the Form Template Method refactoring
Design principle: the hollywood principle
Don't call us, we'll call youTranslation:
- Context: a high-level component is talking to a low-level component.
- It is the high-level component that is in control of the process
- High-level component has a (general) interface for invoking low-level component
- Low-level component services the high-level component via the calls
- The reason to do this is to put some order to the madness: limit circular/tangled dependencies
- The superclass with the template in it is the high-level component
- The subclasses are the low-level components
- The super is in control and calls the subclasses
- The subclasses don't call the super.
Go4 description: Provide a way to access the elements of an aggregate object
sequentially without exposing its underlying representation.
- You know this one well: the Java collections all implement an Iterator interface.
- Key benefit: it hides the underling kind of collection since its irrelevant to users just iterating over it.
- Iterators need to hold the state of progress of one iteration through the object, so you make a new one of these guys for each iteration through the collection.
- The take-home message here is you can implement
Iteratoron your own classes which you may need to iterate through
-- e.g. a
TreeIteratorwhich does a depth-first walk
-- you can also add
reset(), etc behavior needed for your domain, etc.
Go4 description: Compose objects into tree structures to represent part-whole
hierarchies. Composite lets clients treat individual objects
and compositions of objects uniformly.
- If you needed a recursive union type in C, use the composite pattern in an OO language -- Composite is important!
- HFDPS Example: restaurant menus with submenus and the solution using a Composite
- The key advantage of this pattern is how
print()can be implemented: an individual item just prints itself, and the composite prints all of its components.
- Notice how the common superclass
MenuComponenthas all the methods of an individual
MenuItemas well as of a composite
Menu. This violates LSP (and because of that lots of
UnsupportedOperationExceptions must be raised in e.g.
isVegetarian) but is needed for the useful uniformity.
- The key advantage of this pattern is how
- Java Swing UI example (also found in other nested window systems):
- Swing implements a variation on Composite:
JComponentis a subclass of
Container, and so
JComponentand all its subclasses (i.e., all swing widgets) are themselves containers.
JComponentcontainer first paints the component (
paintComponent()) and then all of its children, if any (
- Swing implements a variation on Composite:
Go4 description: Allow an object to alter its behaviour when its internal state
changes. The object will appear to change its class.
- Book example is a gumball machine
- The actions of inserting a coin, turning crank, etc vary
depending on the state of the machine (coin previously
(aside: behind every state pattern is a finite-state automaton)
- The smelly way to do it -- a bunch of
- better way to do it: with the state pattern! here is the main class code and here are some state classes.
- This is an example of the Replace Type Code with State/Strategy refactoring
- Observe the delegation here, the state-dependent methods now are forwarded to the
- The actions of inserting a coin, turning crank, etc vary depending on the state of the machine (coin previously inserted etc)
- Another Example: in Fowler Refactoring Chapter 1, the state pattern
was used for movies because their classification could dynamically
change (a new class
Pricewas introduced which is the
Stateabove, and it had subclasses
Contextabove, holding the
Pricestate: See this Figure).
- State is great: the underlying states of the design now have clear reifications as class names.
Go4 description: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
- This is very similar to State, it supports dynamic changing of some aspects of a class--in this case an algorithm.
- The class diagram is identical as well.
Go4 description: Ensure a class has only one instance and provide a global point of
access to it.
- For a class with only one member, something quite common in practice.
- Code it so that multiple instances can't be created.
- Have a class method to get the sole instance. Java version (from Bloch's book).
- Static methods are directly globally visible, violating encapsulation boundaries.
- You can't pass classes as arguments, but you can pass objects (note some languages do allow classes to be passed)
- There is no object polymorphism with classes - ClockFace f where ClockFace is an interface means f at run-time could be e.g. a DigitalClockFace or AnalogClockFace object
- This will not be extensible if for some reason two (or more) are needed.
- Architectural patterns are patterns of system architecture, used by frameworks
- They are at a larger scale than design patterns and may not be involving objects alone
Model - View - Controller (MVC)
This is a classic architectural pattern encouraging separation of concerns.
- MVC means factor out the front-end View (output) and the Controller (input) into separate components (often classes).
- Whats left is the Model, the code only doing the actual underlying logic of the app.
- Originally applied to GUI apps, e.g. Smalltalk UI's, Java Swing and JavaFX, Android Activities.
- More recently extended to web-based apps with some (or a lot of) abuse of the original terminology.
MVC and Observer
- Views can register as Observers of model, ready to update on any change to underlying model
- Multiple views can be observing the same model - fits nicely into Observer pattern
- Controller is also an observer of the global event queue
MVC and modern web architectures
Web (and other RESTful) apps have a related architecture pattern, usually called a three-tier architecture
- V and C is top tier, model is middle, and at bottom is the persistence layer, the connection to the database.
- Example from the Todo app:
Todois the model (its very simple; for e.g. your Lights out game you should have had several core classes for the underlying game such as
TodoControlleris the controller which is the interface for receiving inputs;
- The view is all on the front-end so there is no view in the Java code;
TodoServiceimplements all the persistence operations - don't mix this into model or controller.
MVC and the Active record pattern in Rails and Django
Rails claims to have an MVC architecture but its different in several ways from the classic one.
- (Rails is the RESTful server technology stack for Ruby; Django is similar)
- Model objects persisted in the database by the framework (via the active record architecture pattern)
- model objects can e.g. save() themselves to the database, framework does it all -- automatic Object-relational mapping (ORM)
- To Do app is manually mapping objects to database -- manual ORM.
MVVM (Model - View - ViewModel)
- This is a modern variation on MVC which exists in the front-end of some web frameworks (e.g. Angular 1)
- The ViewModel is an adapter (see below) - it is converting data in the model into a form the view can digest it.
Component-based Architecture (Angular 2 and React)
- This is a relatively novel architectural pattern for front-end development; see e.g. this tutorial
- Rather than designing a monolithic view for a whole page, views can be composed of multuple component views which the framework automatically stitches together
- In each component you can also separate out the html, css, and js just for that component, which the framework will also compose for you to give the html/css/js for the whole page view.
- The net effect is you have more "objects" of these html/css/js which can compose like normal objects do.
More Gang of Four PatternsThe patterns from here on down are relatively infrequiently used and we are not going to cover them or ask you to learn them. They are summarized in Chaper 14 of the HFDPs book.
Go4 description: Decouple an abstraction from its implementation so that the two can
Bridge is an apt name, because it forms a bridge between two inheritance heirarchies.
Bridge emerges as a result of a refactoring which introduces delegation:
- Suppose you have a complex inheritance tree (example:
- Suppose there are also different modalities of implementation of the whole tree, for example PC, UNIX, and Mac-specialized implementations are needed.
- Refactor this mess into two trees, an
abstraction hierarchy which is the original
Dialogtree with the PC/Mac/UNIX implementation bits removed, and an implementation tree which has an abstract
Implementationclass and each concrete
- The abstract tree then delegates to its implementation object for the low-level code.
Go4 description: Separate the construction of a complex object from its representation
so that the same construction process can create different
Directorneeds to create many different kinds of parts to make the full product. In the stupid method, the director has a whole pile of different classes he has to
- In the smart method above, he has a uniform pile (an
Builder's, and by invoking
BuildPart()on each one in a loop, he gets all his parts made with minimal code fuss.
ConcreteBuilderis a particular concrete
Builder, a factory class, designed to create
Chain of ResponsibilityHFDPs p.616
Go4 description: Avoid coupling the sender of a request to its receiver by giving
more than one object a chance to handle a request. Chain the
receiving objects and pass the request along the chain until an
object handles it.
- This pattern shares some structural similarities with Java exceptions: either they are handled or passed on. But with exceptions they are implicitly passed on if they are not handled; here the passing on is explicit
- This pattern is useful for hierarchical structures where a request can be handled at multiple layers.
- Example: GUI event handling can be done hierarchically. If a contained view doesn't want to handle an event it can delegate it to its container, etc up the chain to the window. Java doesn't use this event model however.
Go4 description: Use sharing to support large numbers of fine-grained objects
- This is a more specialized pattern, not used as often as the others.
- Example: Suppose a card game required 50 decks of cards. Only one set of actual cards need be created and shared
InterpreterHFDPs p. 620
Go4 description: Given a language, define a representation for its grammar along
with an interpreter that uses the representation to interpret
sentences in the language.
- This is a very specific pattern to represent language syntax.
- It is a variation on
Composite. In general it is also like
uniontypes are encoded via this recursive diagram structure.
MediatorHFDPs p. 622 Put someone in charge (a mediator) of an interaction between two classes.
Go4 description: Define an object that encapsulates how a set of objects
interact. Mediator promotes loose coupling by keeping objects
from referring to each other explicitly, and it lets one vary
their interaction independently.
MementoHFDPs p. 624
Go4 description: Without violating encapsulation, capture and externalise an object's
internal state so that the object can be restored to this
- This pattern can be one useful way to interact with a database in an object-oriented fashion: keep mementos around of all objects
PrototypeHFDPs p. 626
Go4 description: Specify the kinds of objects to create using a prototypic instance
and create new objects by copying this prototype.
- When you want new objects, copy from a prototype instead of creating directly from a class.
- Useful when its a significant effort to create object structure from scratch.
To create a new
Deckof 52 playing cards, cards could be copied from a static variable in
Deckwhich was originally initialized when the class was loaded to hold a "fresh"
Cardobjects rather than making cards all over again.
VisitorHFDPs p. 628; also see Bob Tarr's slides
Represent an operation to be performed on the elements of an object
structure. Visitor allows one to define a new operation without
changing the classes of the elements on which it operates.
- Suppose you have an
Elementobject in a variable and need to perform a
switchon what concrete subclass of
Elementwe in fact have.
- Note that this is an incredibly common C programming
uniontypes -- you are casing on which branch of the union you are in (the C analogy of inheritence is union).
- The problem is this notion does not fit well with O-O, the union is treated as passive in this switch; you are also casing at run-time on what class an object is, a brittle programming pattern.
- Alternative 1: add a method to each class in the union to do the walkthrough
- Big Advantage: we kept things highly O-O!
- Big Disadvantage: this is shotgun surgery -- each time we want to do such a switch we have to add a method to all the classes in the tree. Code gets all spread out.
- Alternative 2: Visitor
- Add an intermediary class, the visitor, which holds all the cases
- The classes in the original inheritance hierarchy gets a new method
Acceptto help "walk" the visitor through the union
- ... this is a compromise, we are not completely violating O-O and we avoid shotgun surgery when adding an operation over the tree, but it adds complexity to the design.
- Note that if we add a new concrete element type we on the other hand have to do surgery on all visitors. But, we have localized the surgery to just the visitors.
- This pattern is another pattern that is useful to get rid of switch statements.
- Abstract superclass
Visitoris the superclass of all visitors
ConcreteVisitor1is a concrete visitor (e.g. we make a class
getHealthRating()in the menu example); we make new
ConcreteVisitorXfor each different switch we wanted to do over the union.
ConcreteVisitor1has a method
visitConcreteElementAetc for each kind of node A/B/.. in the original union structure - this is where the code in the original switch for the case it is
anElement.accept(aVisitor)starts the visiting process
- This method in each inheritence class
ConcreteElementAetc in turn calls the correct "case" to be performed on it, e.g.
visitConcreteElementA(this)which will run the correct case of the switch..