A design pattern is a way of structuring code in order to solve a specific problem.
Pros:
- common problems can have similar solutions
- reuse existing knowledge
- flexible, shorter, reusable, & more elegant code of better quality
- having guidelines saves time
Design Patterns: Elements of Reusable Object-Oriented Software (1994) by Erich Gamma, John Vlissides, Ralph Johnson, Richard Helm (the gang of four)
- Java Design Patterns: Creational
- Java Design Patterns: Behavioral Part 1
- Java Design Patterns: Behavioral Part 2
- Java Design Patterns: Structural
LinkedIn course by Bethan Palmer
- Used to abstract the process of instantiating objects, when the system should not be aware of how objects are created: they encapsulate the object creation code. They become more useful the larger & more complex our application becomes.
- They are about defining a small subset of behaviors that can be used for the basis of lots more complex behaviors
- Themes defining all creational design patterns:
- they encapsulate knowledge about which concrete class the system should use
- they conceal how objects are created and put together.
- Used when constructors are expected to be too complex, e.g. with a long list of parameters (wrong order of arguments may be given or sometimes not all information is required in the creation of an object)
- Used instead of the telescoping constructor pattern, aka having a large selection of possible constructors for each object (aka with all possible & useful combinations of parameters) while retaining the flexibility of our code
We have the class Bedroom
containing 8 fields and the Architect
class which manages the main
class which creates Bedroom
objects. Use the Builder Pattern in order to add flexibility to the app.
Solution:
a. [Simple] We create the BedroomBuilder
class which contains setters for all Bedroom
fields & the createBedroom
method. The architect class now uses this builder's setters in order to specify whichever field we wish to set, and the rest are left with the default value (null). If this is not desirable, we may add a default value to the fields. I have added the logging in the main app & the toString()
method for Bedroom
in order to verify the results.
b. [Complete] We expand the functionality of the app by adding the ability to create more types of rooms. We extract the Builder
interface.
c. [Complex] We expand the app in order to create houses with rooms (via RoomBuilder
) and they can also have multiple stores. We expand the builder in order to facilitate all of the aformentioned functionalities.
Used when we need to make sure only one instance of a class can be created (e.g. Windows managers, DB connectors, File managers, UI dialogs, resource allocators & spoolers). The Java API already uses this pattern in some of its classes (e.g. System
, Desktop
and Runtime
)
In the PrintSpooler
class we have a private constructor, thus not allowing any other class to initialize it. We also have the private method init()
which would handle a more complex initialization process, and the method getInstance()
which fetched the unique instance of PrintSpooler
of our app.
Use the Singleton pattern in a multithreaded app.
Solution: make the getInstance()
method synchronized
.
Java's API of SingletonList
, SingletonSet
and SingletonMap
can assist in the implementation of this pattern. Using these methods does not turn the collection into a singleton, but it does provide a single point of access that always returns the same instance.
In this application, we wish to implement a network connector of resources.
Makes copies of one original object.
Useful when...
- we wish to create an object similar to one already created
- creating a new instance is a memory intense process (faster app, shorter & cleaner code)
- we want to keep the app agnostic to how an object is created
- classes are loaded dynamically
This allows for cloned entities to still be modified.
Elements:
- abstract prototype class, with a
clone()
method. - concrete implementations which override the
clone()
method.
We use an app which simulates rabbits breeding. We use the Cloneable
interface provided by Java and override the clone()
method. When we run the main process we see that the original and the clone rabbit both have the same age.
However, the clone()
method belongs to Object
, not Cloneable
. An alternative to this interface is the use of copy constructors. We could also just implement our own clone()
method. Secondly, the clone()
method only creates a shallow copy, meaning if a class contains mutable values, problems will arise.
Note: It is worth reading Effective Java by Joshua Bloch for more info about the issues of this interface.
We add the field Person owner
to the Rabbit
class (a mutable object) and overcome the aforementioned issue.
Used when a class (candyStore
) doesn't know exactly what object types (candy
) need to be created.
We add the factory class which handles deciding which class to return, thus keeping the other classes immutable if another type of objects needs to be handled by the candyStore
class, adding maintainability to the code.
Given a candy store simulation app, use the aforementioned pattern in order to ameliorate the code.
Introduce hierarchy.
The full example.
Provides an interface for creating families of objects, without specifying what their concrete typea are.
Useful when there is a system that creates objects, but how these objects are created should be hidden from view. (families of objects), e.g. bike parts for different types of bikes.
This pattern improves consistency, as all bikes are created with the same parts, albeit different types of them.
Start of the implementation.
End result of the implementation.
Make the above implementation extensible.
LinkedIn course by Bethan Palmer
- They are about how objects assign responsibilities between each other/how objects are connected & how they communicate & how responsibilities are assigned between them.
- Class patterns (
isA
relationships)- how classes share responsibilities between themselves
- they use inheritance
- Object patterns (
hasA
relationships)- the most common type
- how different object interact w/ each other
- sometimes they help different objects work together in order to perform a task
- sometimes they delegate requests to other objects
- they use composition.
- Used when it is decided in runtime which entity handles a request.
- Passes along a request until it is handled
- Decouples the sender from the receiver
- Example use cases:
- handling authentication
- servlet filters for handling HTTP requests
- context-dependent buttons in UI
- What to watch out for:
- circular chains of requests
- requests may never be handled
- confusing stack traces
- We define the abstract class
DocumentHandler
which later on will be implemented for each document type we wish to handle. This handler has aDocumentHandler
field namednext
, indicating the next handler. This class handles the next handler, as we do not know which type the next handler might be each time. - We then define the other handlers, e.g.
SlideshowHandler
,SpreadsheetHandler
andTextDocumentHandler
. These implement a method namedopenDocument()
which checks the document's extension and simply log whether they can handle this document or not. - We also create the
Client
class, which handles the main function. In the following line:
DocumentHandler chain = new SlideshowHandler(new SpreadsheetHandler(new TextDocumentHandler(null)));
we define the successor chain starting with a slideshow handler and ending with a text document handler. We then can add another handler if we need to handle another type of documents as the argument of the innermost constructor (here, TextDocumentHandler
), after we implement said handler.
Implement request handling using the same design pattern.
- Useful when it is unknown what is requested or what is receiving the request.
- Encapsulate a request inside an object
- Decouples the object that invokes the command from the object that knows how to handle it.
- Useful for commands we wish to perform later on, e.g.:
- Support undo/redo functionality
- Queueing/logging requests to be performed at different times
Goal: Decouple BE functionality from FE (button clicking)
Starting:
- GUI class: hold simple information for UI, also holds the main method.
- Button (with a
click()
method) - the invoker - Document (will hold the functionality of printing/saving documents) - the receiver.
We then add the Command
interface and its implementations for SaveCommand
and PrintCommand
.
Implement order handling using the same design pattern.
Use Cases:
- write custom regular expressions
- write custom compiler
- translate human languages
- parse SQL
- create a simple calculator Key points:
- express a recurring problem as a sentence & interpret it
- define grammar
- build an abstract syntax tree Components:
-
Context
(what we handle, e.g. aString
) -
Abstract
Expression
(defines a methodinterpret(context)
)Two possible implementations:
- Terminal expression (the last time
interpret()
gets called) - Non terminal expression (calls the interpret method, which alters the context and then it passes it onto another expression until terminal expression)
- Terminal expression (the last time
-
Client
which creates instances of expression to interpret.
Positives:
- easy to extend/implement the grammar
- works best with simple grammar (complex ones new a new expression class per new rule)
Implement a language interpreter which can also rename variables to meet Java conventions.
We create the Expression
interface which defines the interpret()
method & its implementations: NameNotPrimitiveType
, FirstLetterIsLowerCase
, FirstLetterNotUnderscore
, which all handle their corresponding cases by altering the context. We can test this in the Main
class.
Therefore we check that firstly the variable name does not begin with _
, then that it doesn't start with a capital letter and lastly that it's not a primitive type.
Implement a language interpreter which checks sentences for a capital first letter, no repeated words and that verifies that they end with a period.
Example uses: Collections
class with forEach
loops
Benefits: agnostic as to the type of collection, as long as it implements Collection
.
The iterator
is a seperate object which is responsible for moving along the list, keeping track of which elements have been traversed already.
Create an iterator in order to print only the items that are in stock.
Create an iterator in order to print the names of the employees.
Loosely coupled objects communicate through the mediator.
Given the existing implementation for the e-commerce site, implement the mediator pattern in order to introduce loose coupling onto the app. At the beginning, we notice that there is a circular dependency, as the ECommerceSite
has a Customer
field and vice versa.
How we do this: we implement the Mediator
class containing a field for each object in our app (Customer
, ECommerceSite
& Driver
). We there create the method buy()
. We also decouple the existing classes. We also alter the Main
class accordingly, using the Mediator
's method.
Simplify the airport application using the Mediator Pattern in order to decouple the objects and ameliorate the code.
Without breaking the principle of encapsulation, we wish to retain & externalize the previous state of an object in order to implement e.g. the undo functionality.
Terms:
- Originator: the object whose states we wish to retain
- Caretaker: the object changing the state of the originator
- Memento: the object between the two which handles the state change
Notes:
- If the originator contains a lot of data, this pattern increases the memory usage (so it is might be best to omit it)
Goal: Add save
and undo
operations onto a document handling app.
Beginning: The TextDocument
which write
s and print
s the text and the DocumentViewer
class which also contains the main
method.
Solution: We add the Memento
class which contains the state
field, we add a Memento
field to the TextDocument
class, and we also add there the methods to save()
and undo()
which utilize the pattern. We update the main
method accordingly.
Similarly, implement the memento pattern in order to handle the calculator app state.
LinkedIn course by Bethan Palmer
Useful in OOP projects where many classes need to interact with each other without being tightly coupled; when many classes need to be notified of changes on another object.
Elements:
- the subject/the observable object
- the observer(s), which watch the object
Note: Prior to Java 9 there was the Observer
/Observable
classes, but have since been deprecated, since they did not log enough information on the updates on the subject. Instead, it is recommended to use PropertyChangeListener
and PropertyChangeSuppport
classes.
A simple social media platform which notifies one's connections when one updates their status, showing that update on their feed.
Connection
represents the connection with another user, with astatus
String field (observable)SocialMediaFeed
represents our feed, with astatuses
List of String field (observer)Main
which contains the main class. Initially it prints nothing as the elements are not observing each other
Use the observer pattern to refactor an app which handles updates in traffic for various cities.
Allows objects to alter their behaviour depending on their state (e.g. play/pause button) simplifying the otherwise required if/else blocks we could use for this issue.
The state
is seperated out from the client and is usually an interface, with concrete implementations for different states, which handle the behaviour based on the different state of the item.
We implement a media player app with the state pattern.
The MediaPlayer
class contain the fields state
and icon
& the Main
class handles the changes.
We create the State
interface and we define the pause()
and play()
methods, with a MediaPlayer
argument each. We then implement the PlayingState
and the PausedState
. We add a State
field to the MediaPlayer
class & also alter the play()
& pause()
methods.
Similarly, refactor an app handling a fan's temperature. Note that the fan cannot go from low to high immediately, meaning we need to use the medium setting in between.
Define a family of algorithms used dynamically selected based on whichever state we are. (e.g. different file encryption methods, different validation methods, different sorting algorithms, saving different file types etc)
context
which maintains a reference to the strategy objectsstrategy
as an interface and its concrete implementations (which are not needed as of Java 8 - see below)
Implement the above pattern on an app handling various encryption methods.
Refactor an application handling customer payments with various ways.
A superclass defines the (common) steps of an algorithm and the subclasses redefine some of these steps. This solves code duplication.
It is important to note:
- it can be difficult to communicate intent to users
- it makes the program flow harder to follow/more complex
Use the above pattern in order to remove duplicate code when printing two slightly different pizza recipes.
Use the above pattern in order to remove duplicate code for a simple RPG game.
A visitor object handles the communication between objects.
Having an interface with concrete implementations, we wish to apply some operation to each implementation, slightly different for each one. We could create a method to accept visitors in each implementation (defined in the interface) but this might be harder to understand or make changes to. Instead, we can create a Visitor interface, defining a visit method for each of the concrete implementations.
Highly useful when having a composite or list of other elements. Best used when the elements are not likely to change.
It's fairly complext & for specific only use cases, so it's scarce to be used.
It makes use of the open-closed principle.
The Groceries
interface is extended by Milk
and Bread
, with getPrice()
and setPrice()
methods. We also have the GroceryList
class which implements Groceries
and has a ArrayList<Groceries> groceries
field. This returns the sum of the prices of all groceries in the list. The Client
handles the main process.
Our goal is to apply a discount on all groceries, without altering the GroceryList
class, as it does not need to be affected by this.
- We create a
Visitor
interface withvisit()
methods for each implementation ofGroceries
as well as the interface itself. We implement the methods in theDiscountVisitor
implementation ofVisitor
and alter the main process accordingly. - We add the
accept()
methods in theGroceries
interface & the implementations (Milk
,Bread
,GroceryList
) and update the main process accordingly.
Use the Visitor pattern in order to refactor an app calculating employees salaries.
LinkedIn course by Bethan Palmer
- Design patterns refering to how classes and objects are structured & used together to form larger structures.
- Class Patterns (
isA
)- They are focused on how classes are structured & interact.
- They focus on inheritance
- Object Patterns (
hasA
)- They are focused on how we use objects
- They allow objects to change behavior at runtime.
It helps when we need to adapt a class from a e.g. third-party app to our own app's needs. We simply create the adapter interface to bridge the gap.
Fun fact! There are some examples of this pattern in the JDK itself. For example, the collections class has two methods lists and enumeration, which use the adapter pattern to provide compatibility with Legacy APIs that work with the enumerations. Another example is the as list method of the arrays class, which uses the adapter pattern to allow array based and collection based APIs to work together.
The app calculates prices of different types of vehicles.
At first we have the PriceCalculator
interface with the calculatePrice()
method, with two implementations (CarPriceCalculator
and TruckPriceCalculator
) (the formulas are random, simple ones for the sake of the example). The main class simply calculates & prints these prices.
These prices are all in USD. Within the lib
folder, we have uk-car-price-calculator.jar
(also as a dependency in out pom
). Through its JavaDocs we can see that it has a class (UKCarPriceCalculator
) with a constructor and a getPrice()
method.
Solution: We create an Adapter
class implementing the PriceCalculator
interface with a field UKCarPriceCalculator
. We create a constructor and we override the calculatePrice()
method.
Implement the aforementioned pattern in order to ameliorate the app printing city temperatures around the globe, therefore handling Fahrenheit as well as Celsius.
Separates hierarchy: e.g. we have shapes (circle, triangle) which can be red and blue. Then we add the colour green, so we have to add new code in both shapes. If we desire to alter the red implementation, we alter all reds. If we add a new shape, we have to add all the colours again, etc - we differentiate the shape and the colour in different classes/trees. Now, when we add a new colour-shape combo, we use the existing, corresponding implementations (e.g. for blue & for triangle), instead of writing anew (largely duplicate) code.
Goal: Simplify the shapes & colours canvas app with the above pattern.
Tip: Some IDEs can display the class relationships in a diagram, e.g. IntelliJ.
Refactor an app that draws buttons in a similar manner.
We find that the buttons defined at the start of this challenge are named Size
Type
Button
, where size: small, medium or large & type: radio, dropdown or checkbox.
We therefore need to introduce the interface ButtonSize
. We then remove the useless classes like SmallCheckboxButton
& refactor the existing classes.
Varargs in Java: methods whose argument is in this format: method(Type... fieldName)
; this means when calling this method, we can pass as argument any amount of Type
arguments.
This is used when we want to treat single objects & groups of objects in the same manner, or when we have code that is very similar/identical to handle different types of objects.
We have an app managing a company's expenses:
Manager
has a fieldname
and a methodpayExpenses(int amount)
which prints that the person with namename
has been paidamount
dollars.Salesperson
has a fieldManager
and a fieldname
& a similarpayExpenses()
methodSalesTeam
has two lists, one ofManager
s and one forSalesperson
s and, again, the similarpayExpenses()
method.
We can refactor these classes in order to unify the identical payExpenses()
methods and reduce duplicate code.
We add the Payee
interface which defines the identical method. Now, Manager
, Salesperson
& SalesTeam
all implement it.
Refactor similarly an app which simulates books checkouts & returns.
It is used when:
- we wish to add additional responsibilities to an object dynamically
- we want more flexibility than subclassing allows
Basically, we have the interface and some implementations. When we wish to add traits onto one implementation, we encapsulate it into another.
Given a canva app which paints circles, we wish to add the functionality of drawing circles with red borders.
Solution: We add the interface ComponentsWithRedBorder
which implements Component
and alter the draw()
method accordingly. Then, in our Canvas
class, we decorate the desired circle (e.g. circle2
)
Refactor a pizza ordering app similarly, in order to facilitate users desiring extra cheese on their pizza's toppings.
Hide complexity behind an interface, in order to simplify user experience.
Benefits:
- makes it easier to use complex code
- promotes loose coupling
- it is not enforced - user can interact with the complex classes hidden behind the façade, if needed.
We have an app which manages vacations (car rentals, hotel bookings & flight arrangements). With the façade pattern, we can unify the client's many calls of methods into one which simply books the entire vacation's details.
Solution: We create the façade class which handles the same functionality & update our main process.
Refactor a driving car app similarly.
- classes
Accelerator
,Clutch
,GearStick
,Handbrake
&Ignition
hold info as well as functions for each car part & its actions Car
class handles the main process which calls upon the methods needed in order to drive a car.
Useful when an app uses resources heavily, solves the issue of java.lang.OutOfMemory
errors.
Also can maximize performance when creating a new object.
e.g. when typing a document, instead of creating a new char a = 'a';
each time the user types the letter a
, we reuse the same one.
Intrinsic vs Extrinsic State
Intrinsic state is stored in the flyweight; it consists of information that's independent of the flyweight's context, thereby making it sharable. Extrinsic state depends on and varies with the flyweight's context and therefore can't be shared. - source
So, in the letter example, intrinsic state refers to the character code (same for all objects) whereas extrinsic state to the position in page (different for every object).
Flyweight objects allow to share the intrinsic states, so we reuse that, and the extrinsic state's info can be passed to the flyweight object when it needs it.
We have an app which predicts traffic.
Classes:
Vehicle
interface withgetType()
,getLocation()
,setLocation()
methods- its implementations:
Car
,Truck
TrafficSimulator
which holds the main process
Solution:
- Create
VehicleFactory
class to depict the flyweight object. Within it, we haveHashMap
ofVehicle
s which hold the info for each vehicle. We check if a vehicle object of the specified type already exists; if it does, we handle it, otherwise we create a new one. - We add the factory to the main process, & when creating a new vehicle, we call its
getVehicle()
method implementing the aforementioned functionality.
Refactor an animal population simulation app similarly.
Virtual proxies are used to save memory & improve performance. We create a proxy of an item when we want to access the item's inexpensive operations (e.g. getWidth()
, getHeight()
of an image
) and we access the actual thing when we need the expensive operations (e.g. loadImageFromDisk()
) - lazy loading.
Common uses:
- representation of large objects in GUI,
- representation of expensive db ops
- protection proxies for authentication of access
Given an app which displays images like a slideshow, refactor using the Proxy Pattern.
We have the interface called DisplayObject
which holds the display()
method, and the class ImageFile
implementing it. Lastly, in ImageGallery
we call these methods in order to display images from the resources
folder.
The load()
method in ImageFile
is an expensive process consuming memory & time, and it's our refactoring goal.
Solution: We create the ImageProxy
class implementing DisplayObject
with two fields, the image path and the actual imageFile, and an ameliorated display()
method which checks if the file already exists. We also alter our main process accordingly.
Similarly refactor an app which handles a super market's inventory. The items are stored in a List
, however in a real world scenario it would be a database, and thus accessing it would be even more costly. The issue here lies in the fact that Main
we create the inventory at the start of the program but only access it at the end, and we could even never access it at all. We refactor the app in order to reduce memory usage & improve start up time, by not having the inventory be created there.