
All the examples in this course will be run using the Node.js runtime. All of the code is written in TypeScript first and then converted into JavaScript that the Node.js runtime will further interpret and execute.
So, to get started, install Node.js first.
Open a browser and visit https://nodejs.org/en/download/
I am using Windows 10 64-bit, so I will use the 64-bit WIndows Installer (.msi). Be sure to use the correct installer for your operating system.
After the install has finished, depending on your operating system, whether Windows, Linux or Mac OSX, open a terminal/bash/cmd or PowerShell prompt.
and type
node -v
If the install completed without issue, you should see a response similar to
v14.15.3
All the code written in this book should work for versions of Node.js above v10.
Node.js also comes with another program called NPM. We will use NPM to install the TypeScript compiler (TSC).
Check that NPM exists and works, by typing
npm -v
Output should be a version number and not an error. E.g.,
6.14.9
Now that Node.js and NPM work, we can install the TypeScript compiler (TSC) globally on our system.
npm install -g typescript
And then verify that the install is successful by checking the version number.
tsc -v
If you are using PowerShell, you may see an error concerning execution policy.
tsc.ps1 cannot be loaded because running scripts is disabled on this system
You have several options, such as run your commands using the classic Windows CMD prompt, Git Bash or use the tsc.cmd option instead.
tsc.cmd -v
When I was writing the code in this book, I was using Visual Studio Code and executing TSC using PowerShell in the VSCODE integrated terminal.
I recommend to use VSCode, it is free and provides many useful features when coding.
Download VSCode from https://code.visualstudio.com/
When running PowerShell in the VSCode integrated terminal, you may also see the execution policy error.
You can use tsc.cmd in place of tsc.
You can even use a different terminal prompt in the VScode integrated terminal such as the classic Windows CMD, Git Bash and there are many others that you may want to set up.
All the code examples in this book can be viewed from my GitHub repository at https://github.com/Sean-Bradley/Design-Patterns-In-TypeScript
If you have Git installed, you can download a copy locally using the command
git clone https://github.com/Sean-Bradley/Design-Patterns-In-TypeScript.git
You can install Git for Windows from https://gitforwindows.org/.
Linux will normally have git preinstalled.
or,
you can download a zip of all the code, using the link
https://sbcode.net/typescript/zips/Design-Patterns-In-TypeScript.zip
TypeScript is a tool to help you write type-safe JavaScript. JavaScript is a weakly typed language which means that types are assigned implicitly as they are used at runtime. While this can be considered a feature, it can be dangerous if your code needs to be treating types precisely at all times. Enforcing type safety ensures that all usage of the properties, functions and classes are consistent within your application and as a result makes your application more robust.
When writing TypeScript, it will appear in many ways very similar to JavaScript. TypeScript is a subset of JavaScript. So, you can write JavaScript in a TypeScript file first, but the IDE, VSCode in my case, will indicate many suggestions on how to modify your code to be more type safe.
In this next section on TypeScript basics, we will go through many concepts which may be JavaScript first but you will see the TypeScript equivalent so that you can get a better understanding between the languages.
Normally, it is best practice to create a tsconfig.json file in the base of your TypeScript source folder in your project.
So, create a new file called tsconfig.json in the ./src/ folder alongside the existing test.ts
Before running your compiled JavaScript in Node.js, you need to run your TypeScript source files through TSC.
This process of running tsc -p ./src and then seeing the output in Node.js using node ./dist/test.js for example, can be quite tedious if you need to keep typing the tsc and the node commands in turn for every change you make.
Instead, you can run TSC in watch mode, which will cause it to continue running and automatically compile and check any changes you make to the TypeScript source files, and then you open a second terminal that you can use specifically for executing the node or other commands.
Run
tsc -p ./src -w
This section is a basic introduction of the types that you can use in TypeScript. It is useful to help you learn some of the TypeScript basics in case you are new to TypeScript. Each of the types listed below are used throughout the patterns.
A boolean can either be true or false.
A number can be written in many bases or with floating point precision.
An array is a JavaScript object first that can contain a series of any types, but in TypeScript you can set the types explicitly or even as unknown.
A Dictionary is used as a key/value construct, where you can retrieve a value from the dictionary by using a key.
The Tuple is similar to an array, but you are explicitly indicating how many items are in the Tuple and of which type they are when you instantiate it. The Tuple type is not directly supported in JavaScript as a Tuple, but as an array instead. The rules of the Tuple are enforced in TypeScript only when it is created. After the Tuple is created, it behaves the same as an array. You can add/remove/edit items.
The Set object lets you store unique values of any type. Any duplicate items added to the Set won't be added.
Every design pattern in the course uses classes, so it is appropriate to get a basic understanding of them.
Simply, they are a template that can be used when creating custom objects.
Interfaces in TypeScript are a useful tool that you can use for your classes to ensure that they conform to any specific rules that you want them to. This is especially useful if there are many people working on the same code base, and any classes need to follow any specific rules.
You can extend any existing class templates by using the extends keyword. The new class definition will be made up of the original class, but can optionally include its own new bespoke constructor, properties and/or methods. The new class definition is known as the derived class or subclass.
Extending a class is a different concept than implementing an interface. An Interface describes the property types and method signature rules that the class implementing it should comply with. Extending a class copies the base class template and allows you to refine or specialize it further.
With the derived class, the original class being extended is called the base or super class. It is a class that may have methods and properties that are common, but another class can be created from it that extends from this base/super class and has the option to override the constructor, methods and properties. The derived class also has the option to create additional methods and properties specific for its own needs. If the base class is using an interface, then any derived class will already comply provided that the base class was already correctly complying with its chosen interface.
Abstract classes are like a mixture of implementing interfaces and extending a class in one step. You can create a class with optional methods and properties, but also indicate which methods and properties must be implemented in the derived class. Note that your base class, despite enforcing abstract rules, is still able to itself implement any interfaces you desire.
Use the abstract keyword to indicate a class contains abstract methods or properties.
TypeScript supports access modifiers for your class properties and methods.
Public
Private
Protected
On larger projects, it is common to split up your code into separate files. When doing this, you will need to tell each file which other file it needs to reference in case it is using objects, classes, types or interfaces from the other files.
Unified Modeling Language (UML) Diagrams are used throughout the course to help describe the patterns.
When developing code, you may instantiate objects directly in methods or in classes. While this is quite normal, you may want to add an extra abstraction between the creation of the object and where it is used in your project.
You can use the Factory pattern to add that extra abstraction. The Factory pattern is one of the easiest patterns to understand and implement.
Adding an extra abstraction will also allow you to dynamically choose classes to instantiate based on some kind of logic.
An example use case may be a user interface where the user can select from a menu of items, such as chairs.
The user has been given a choice using some kind of navigation interface, and it is unknown what choice, or how many chairs the user will add until the application is actually running and the user starts using it.
So, when the user selected the chair, the factory then takes some property involved with that selection, such as an ID, Type or other attribute and then decides which relevant subclass to instantiate in order to return the appropriate object.
While there are is a large amount of code in this example, and it is spread across several files, the actual factory is the ChairFactory class in the file chair-factory.ts. So, the factory is the part of your program that is creating a separation or abstraction between the instantiating of your object and where it is used.
The Abstract Factory Pattern adds an abstraction layer over multiple other creational pattern implementations.
To begin with, in simple terms, think if it as a Factory that can return Factories. Although you will find examples of it also begin used to return Builder, Prototypes, Singletons or other design pattern implementations.
An example use case may be that you have a furniture shop front. You sell many different kinds of furniture. You sell chairs and tables. And they are manufactured at different factories using different unrelated processes that are not important for your concern. You only need the factory to deliver.
You can create an extra module called FurnitureFactory, to handle the chair and table factories, thus removing the implementation details from the client.
The Builder Pattern is a creational pattern whose intent is to separate the construction of a complex object from its representation so that you can use the same construction process to create different representations.
The Builder Pattern tries to solve,
How can a class create different representations of a complex object?
How can a class that includes creating a complex object be simplified?
The Builder and Factory patterns are very similar in the fact they both instantiate new objects at runtime. The difference is when the process of creating the object is more complex, so rather than the Factory returning a new instance of ObjectA, it calls the builders director constructor method ObjectA.construct() that goes through a more complex construction process involving several steps. Both return an Object/Product.
Using the Builder Pattern in the context of a House Builder.
There are multiple directors that can create their own complex objects.
Note that in the IglooDirector class, not all of the methods of the HouseBuilder were called.
The builder can construct complex objects in any order and include/exclude whichever parts it likes.
The Prototype design pattern is good for when creating new objects requires more resources than you want to use or have available. You can save resources by just creating a copy of any existing object that is already in memory.
E.g., A file you've downloaded from a server may be large, but since it is already in memory, you could just clone it, and work on the new copy independently of the original.
In the Prototype patterns interface, you create a clone method that should be implemented by all classes that use the interface. How the clone method is implemented in the concrete class is up to you. You will need to decide whether a shallow or deep copy is required.
A shallow copy, copies and creates new references one level deep,
A deep copy, copies and creates new references for all levels.
In this example, an object called document is cloned using shallow and deep methods.
I clone the documents instance properties and methods.
The object contains an array of two arrays. Three copies are created, and each time some part of the array is changed on the clone, and depending on the method used, it can affect the original object.
When cloning an object, it is good to understand the deep versus shallow concept of copying and whether you also want the clone to contain the classes methods.
Sometimes you need an object in an application where there is only one instance.
You don't want there to be many versions, for example, you have a game with a score and you want to adjust it. You may have accidentally created several instances of the class holding the score object. Or, you may be opening a database connection, there is no need to create many, when you can use the existing one that is already in memory. You may want a logging component, and you want to ensure all classes use the same instance. So, every class could declare their own logger component, but behind the scenes, they all point to the same memory address.
By creating a class and following the Singleton pattern, you can enforce that even if any number of instances were created, they will still refer to the original class.
The Singleton can be accessible globally, but it is not a global variable. The Singleton class can be instanced at any time, but after it is first instanced, any new instances will point to the same instance as the first.
In the example, there are three games created. They are all independent instances created from their own class, but they all share the same leaderboard. The leaderboard is a Singleton.
It doesn't matter how the Games where created, or how they reference the leaderboard, it is always a Singleton.
Each game independently adds a winner, and all games can read the altered leaderboard regardless of which game updated it.
The decorator pattern is a structural pattern, that allows you to attach additional responsibilities to an object at runtime.
The decorator pattern is used in both the Object Oriented and Functional paradigms.
The decorator pattern adds extensibility without modifying the original object.
The decorator forwards requests to the enclosed object and can perform extra actions.
You can nest decorators recursively.
Let's create a custom class called Value that will hold a number.
Then add decorators that allow addition (Add) and subtraction (Sub) to a number (Value).
The Add and Sub decorators can accept numbers directly, a custom Value object or other Add and Sub decorators.
Add, Sub and Value all implement the IValue interface and can be used recursively.
Sometimes classes have been written and you don't have the option of modifying their interface to suit your needs. This happens if the method you are calling is on a different system across a network, a library that you may import or generally something that is not viable to modify directly for your particular needs.
The Adapter design pattern solves these problems:
How can a class be reused that does not have an interface that a client requires?
How can classes that have incompatible interfaces work together?
How can an alternative interface be provided for a class?
You may have two classes that are similar, but they have different method signatures, so you create an Adapter over top of one of the method signatures so that it is easier to implement and extend in the client.
The example client can manufacture a Cube using different tools. Each solution is invented by a different company. The client user interface manages the Cube product by indicating the width, height and depth. This is compatible with the company A that produces the Cube tool, but not the company B that produces their own version of the Cube tool that uses a different interface with different parameters.
In this example, the client will re-use the interface for company A's Cube and create a compatible Cube from company B.
An adapter will be needed so that the same method signature can be used by the client without the need to ask company B to modify their Cube tool for our specific domains use case.
My imaginary company needs to use both cube suppliers since there is a large demand for cubes and when one supplier is busy, I can then ask the other supplier.
Sometimes you have a system that becomes quite complex over time as more features are added or modified. It may be useful to provide a simplified API over it. This is the Facade pattern.
The Facade pattern essentially is an alternative, reduced or simplified interface to a set of other interfaces, abstractions and implementations within a system that may be full of complexity and/or tightly coupled.
It can also be considered as a higher-level interface that shields the consumer from the unnecessary low-level complications of integrating into many subsystems.
This is an example of a game engine API. The facade layer is creating one streamlined interface consisting of several methods from several larger API backend systems.
The client could connect directly to each subsystems API and implement its authentication protocols, specific methods, etc. While it is possible, it would be quite a lot of consideration for each of the development teams, so the facade API unifies the common methods that becomes much less overwhelming for each new client developer to integrate into.
The Bridge pattern is similar to the Adapter except in the intent that you developed it.
The Bridge is an approach to refactor already existing code, whereas the Adapter creates an interface on top of existing code through existing available means without refactoring any existing code or interfaces.
The motivation for converting your code to the Bridge pattern is that it may be tightly coupled. There is logic and abstraction close together that is limiting your choices in how you can extend your solution in the way that you need.
In this example, I draw a square and a circle. Both of these can be categorized as shapes.
The shape is set up as the abstraction interface. The refined abstractions, Square and Circle, implement the IShape interface.
When the Square and Circle objects are created, they are also assigned their appropriate implementers being SquareImplementer and CircleImplementer.
When each shape's draw method is called, the equivalent method within their implementer is called.
The Square and Circle are bridged and each implementer and abstraction can be worked on independently.
The Composite design pattern is a structural pattern useful for hierarchal management.
The Composite design pattern,
allows you to represent individual entities(leaves) and groups of leaves as the same.
is a structural design pattern that lets you compose objects into a changeable tree structure.
is great if you need the option of swapping hierarchal relationships around.
allows you to add/remove components to the hierarchy.
provides flexibility of structure
Examples of using the Composite Design Pattern can be seen in a filesystem directory structure where you can swap the hierarchy of files and folders, and also in a drawing program where you can group, un-group, transform objects and change multiple objects at the same time.
Demonstration of a simple in memory hierarchal file system.
A root object is created that is a composite.
Several files (leaves) are created and added to the root folder.
More folders (composites) are created, and more files are added, and then the hierarchy is reordered.
Fly in the term Flyweight means light/not heavy.
Instead of creating thousands of objects that share common attributes, and result in a situation where a large amount of memory or other resources are used, you can modify your classes to share multiple instances simultaneously by using some kind of reference to the shared object instead.
The best example to describe this is a document containing many words and sentences and made up of many letters. Rather than storing a new object for each individual letter describing its font, position, color, padding and many other potential things. You can store just a lookup id of a character in a collection of some sort and then dynamically create the object with its proper formatting etc., only as you need to.
This approach saves a lot of memory at the expense of using some extra CPU instead to create the object at presentation time.
The Flyweight pattern, describes how you can share objects rather than creating thousands of almost repeated objects unnecessarily.
In this example, I create a dynamic table with 3 rows and 3 columns each. The columns are then filled with some kind of text, and also chosen to be left, right or center aligned.
The letters are the flyweights and only a code indicating the letter is stored. The letters and numbers are shared many times.
The column cells are the contexts and they pass the extrinsic vales describing the combination of letters, the justification left, right or center, and the width of the table column that is then used for the space padding.
The Proxy design pattern is a class functioning as an interface to another class or object.
A Proxy could be for anything, such as a network connection, an object in memory, a file, or anything else you need to provide an abstraction between.
Types of proxies,
Virtual Proxy: An object that can cache parts of the real object, and then complete loading the full object when necessary.
Remote Proxy: Can relay messages to a real object that exists in a different address space.
Protection Proxy: Apply an authentication layer in front of the real object.
Smart Reference: An object whose internal attributes can be overridden or replaced.
Additional functionality can be provided at the proxy abstraction if required. E.g., caching, authorization, validation, lazy initialization, logging.
The proxy should implement the subject interface as much as possible so that the proxy and subject appear identical to the client.
The Proxy Pattern can also be called Monkey Patching or Object Augmentation
In this example, I dynamically change the class of an object. So, I am essentially using an object as a proxy to other classes.
Every time the tell_me_the_future() method is called; it will randomly change the object to use a different class.
The object PROTEUS will then use the same static attributes and class methods of the new class instead.
The Command pattern is a behavioral design pattern, in which an abstraction exists between an object that invokes a command, and the object that performs it.
E.g., a button will call the Invoker, that will call a pre-registered Command, that the Receiver will perform.
A Concrete Class will delegate a request to a command object, instead of implementing the request directly.
The command pattern is a good solution for implementing UNDO/REDO functionality into your application.
Uses:
GUI Buttons, menus
Macro recording
Multi-level undo/redo
Networking - send whole command objects across a network, even as a batch
Parallel processing or thread pools
Transactional behavior
Wizards
This will be a smart light switch.
This light switch will keep a history of each time one of its commands was called.
And it can replay its commands.
A smart light switch could be extended in the future to be called remotely or automated depending on sensors.
Chain of Responsibility pattern is a behavioral pattern used to achieve loose coupling in software design.
In this pattern, an object is passed to a Successor, and depending on some kind of logic, will or won't be passed onto another successor and processed. There can be any number of different successors and successors can be re-processed recursively.
This process of passing objects through multiple successors is called a chain.
The object that is passed between each successor does not know about which successor will handle it. It is an independent object that may or may not be processed by a particular successor before being passed onto the next.
The chain that the object will pass through is normally dynamic at runtime, although you can hard code the order or start of the chain, so each successor will need to comply with a common interface that allows the object to be received and passed onto the next successor.
In the ATM example below, the chain is hard coded in the client first to dispense amounts of £50s, then £20s and then £10s in order.
This default chain order helps to ensure that the minimum number of notes will be dispensed. Otherwise, it might dispense 5 x £10 when it would have been better to dispense 1 x £50.
The Observer pattern is a software design pattern in which an object, called the Subject (Observable), manages a list of dependents, called Observers, and notifies them automatically of any internal state changes by calling one of their methods.
The Observer pattern follows the publish/subscribe concept. A subscriber, subscribes to a publisher. The publisher then notifies the subscribers when necessary.
The observer stores state that should be consistent with the subject. The observer only needs to store what is necessary for its own purposes.
A typical place to use the observer pattern is between your application and presentation layers. Your application is the manager of the data and is the single source of truth, and when the data changes, it can update all of the subscribers, that could be part of multiple presentation layers. For example, the score was changed in a televised cricket game, so all the web browser clients, mobile phone applications, leaderboard display on the ground and television graphics overlay, can all now have the updated information synchronized.
Most applications that involve a separation of data into a presentation layer can be broken further down into the Model-View-Controller (MVC) concept.
Controller : The single source of truth.
Model : The link or relay between a controller and a view. It may use any of the structural patterns (adapter, bridge, facade, proxy, etc.) at some point.
View : The presentation layer of the data from the model.
The observer pattern can be used to manage the transfer of data across any layer and even internally to itself to add a further abstraction. In the MVC structure, the View can be a subscriber to the Model, that in turn can also be a subscriber to the controller. It can also happen the other way around if the use case warrants.
This example mimics the MVC approach.
There is an external process called a DataController, and a client process that holds a DataModel and multiple DataViews that are a Pie graph, Bar graph and Table view.
Note that this example runs in a single process, but imagine that the DataController is actually an external process running on a different server.
The DataModel subscribes to the DataController and the DataViews subscribe to the DataModel.
The client sets up the various views with a subscription to the DataModel.
The hypothetical external DataController then updates the external data, and the data then propagates through the layers to the views.
The Interpreter pattern helps to convert information from one language into another.
The language can be anything such as words in a sentence, numerical formulas or even software code.
The process is to convert the source information, into an Abstract Syntax Tree (AST) of Terminal and Non-Terminal expressions that all implement an interpret() method.
A Non-Terminal expression is a combination of other Non-Terminal and/or Terminal expressions.
Terminal means terminated, i.e., there is no further processing involved.
An AST root starts with a Non-Terminal expression and then resolves down each branch until all expressions terminate.
An example expression is A + B.
The A and B are Terminal expressions and the + is Non-Terminal because it depends on the two other Terminal expressions.
The example use case will expand on the concept example by dynamically creating the AST and converting roman numerals to integers as well as calculating the final result.
The Iterator will commonly contain two methods that perform the following concepts.
next: returns the next object in the aggregate (collection, object).
hasNext: returns a Boolean indicating if the Iterable is at the end of the iteration or not.
The benefits of using the Iterator pattern are that the client can traverse a collection of aggregates(objects) without needing to understand their internal representations and/or data structures.
The iterator in this brief example will return the next number in the iterator multiplied by 2 modulus 11. It dynamically creates the returned object (number) at runtime.
It has no hasNext() method since the result is modulated by 11, that will loop the results no matter how large the iterator index is. It will also appear to alternate between a series of even numbers and odd numbers.
Also, just to demonstrate that implementing abstract classes and interfaces is not always necessary, this example uses no abstract base classes or interfaces.
Objects communicate through the Mediator rather than directly with each other.
As a system evolves and becomes larger and supports more complex functionality and business rules, the problem of communicating between these components becomes more complicated to understand and manage. It may be beneficial to refactor your system to centralize some or all of its functionality via some kind of mediation process.
The mediator pattern is similar to implementing the Facade pattern between your objects and processes. Except that the structure of the Mediator could also allow multi directional communication between each component and provide the opportunity to add some logic to the messaging flow to make it more cooperative in some way. E.g., managing the routing behavior by serializing or batching messages, the centralization of application logic, caching, logging, etc.
In this example use case, we will implement some behavior into the mediation process.
Before the mediation logic is added, consider that the below example is a series of components all subscribed to a central location being the subject. They all implement the Observer pattern.
Each component is updated independently by external forces, but when it has new information, it notifies the subject which in turn then notifies the other subscribed components.
During the synchronization of all the subscribed components, without the extra mediation, the component that provided the new information will receive back the same message that it just notified the subject of. In order to manage the unnecessary duplicate message, the notifications will be mediated to exclude to component where the original message originated from.
Throughout the lifecycle of an application, an objects state may change. You might want to store a copy of the current state in case of later retrieval. E.g., when writing a document, you may want to auto save the current state every 10 minutes. Or you have a game, and you want to save the current position of your player in the level, with its score and current inventory.
You can use the Memento pattern for saving a copy of state and for later retrieval if necessary.
The Memento pattern, like the Command pattern, is also commonly used for implementing UNDO/REDO functionality within your application.
The difference between the Command and the Memento patterns for UNDO/REDO, is that in the Command pattern, you re-execute commands in the same order that changed attributes of a state, and with the Memento, you completely replace the state by retrieving from a cache/store.
There is a game, and the character is progressing through the levels. It has acquired several new items in its inventory, the score is very good and you want to save your progress and continue later.
You then decide you made a mistake and need to go back to a previous save because you took a wrong turn.
Not to be confused with object state, i.e., one of more attributes that can be copied as a snapshot, the State Pattern is more concerned about changing the handle of an object's method dynamically. This makes an object itself more dynamic and may reduce the need of many conditional statements.
Instead of storing a value in an attribute, and then using conditional statements within an objects method to produce different output, a subclass is assigned as a handle instead. The object/context doesn't need to know about the inner working of the assigned subclass that the task was delegated to.
In the state pattern, the behavior of an objects state is encapsulated within the subclasses that are dynamically assigned to handle it.
This example takes the concept example further and instead assigns then next state in sequence rather than choosing the states subclasses randomly.
It also allows to set the state outside of the context by using a getter/setter.
The client will set the state, and then run a request, and then change the state again, etc, and depending on the state, the behaviour of the method would have changed.
The Strategy Pattern is similar to the State Pattern, except that the client passes in the algorithm that the context should run.
The algorithm should be contained within a class that implements the particular strategies interface.
An application that sorts data is a good example of where you can incorporate the Strategy pattern.
There are many methods of sorting a set of data. E.g., Quicksort, Mergesort, Introsort, Heapsort, Bubblesort. See https://en.wikipedia.org/wiki/Sorting_algorithm for more examples.
The user interface of the client application can provide a drop-down menu to allow the user to try the different sorting algorithms.
Upon user selection, a reference to the algorithm will be passed to the context and processed using this new algorithm instead.
The Strategy and State appear very similar, a good way to differentiate them is to consider whether the state of the context is choosing the algorithm at runtime, or whether the algorithm is being passed into it.
Software Plugins can be implemented using the Strategy pattern.
A game character is moving through an environment. Depending on the situation within the current environment, the user decides to use a different movement algorithm. From the perspective of the object/context, it is still a move, but the implementation is encapsulated in the subclass at the handle.
In a real game, the types of things that a particular move could affect is which animation is looped, the audio, the speed, the camera follow mode and more.
In the Template Method pattern, you create an abstract class (template) that contains a Template Method that is a series of instructions that are a combination of abstract and hook methods.
Abstract methods need to be overridden in the subclasses that extend the abstract (template) class.
Hook methods normally have empty bodies in the abstract class. Subclasses can optionally override the hook methods to create custom implementations.
So, what you have, is an abstract class, with several types of methods, being the main template method, and a combination of abstract and/or hooks, that can be extended by different subclasses that all have the option of customizing the behavior of the template class without changing its underlying algorithm structure.
Template methods are useful to help you factor out common behavior within your library classes.
Note that this pattern describes the behavior of a method and how its inner method calls behave.
Hooks are default behavior and can be overridden. They are normally empty by default.
Abstract methods, must be overridden in the concrete class that extends the template class.
In the example use case, there is an AbstractDocument with several methods, some are optional and others must be overridden.
The document will be written out in two different formats.
Depending on the concrete class used, the text() method will wrap new lines with <p> tags and the print() method will format text with tabs, or include html tags.
Your object structure inside an application may be complicated and varied. A good example is what could be created using the Composite structure.
The objects that make up the hierarchy of objects, can be anything and most likely complicated to modify as your application grows.
Instead, when designing the objects in your application that may be structured in a hierarchical fashion, you can allow them to implement a Visitor interface.
The Visitor interface describes an accept() method that a different object, called a Visitor, will use in order to traverse through the existing object hierarchy and read the internal attributes of an object.
The Visitor pattern is useful when you want to analyze, or reproduce an alternative object hierarchy without implementing extra code in the object classes, except for the original requirements set by implementing the Visitor interface.
Similar to the template pattern it could be used to output different versions of a document but more suited to objects that may be members of a hierarchy.
In the example, the client creates a car with parts.
The car and parts inherit an abstract car parts class with predefined property getters and setters.
Instead of creating methods in the car parts classes and abstract class that run bespoke methods, the car parts can all implement the IVisitor interface.
This allows for the later creation of Visitor objects to run specific tasks on the existing hierarchy of objects.
Remember that design patterns will give you a useful and common vocabulary for when designing, documenting, analysing, restructuring new and existing software development projects now and into the future.
Good luck for your future and I hope that your projects become very successful.
Sean Bradley
(Book) Sometimes you just want to switch off your computer and read from a book. So, all GoF patterns are discussed in my Design Patterns In TypeScript book
https://www.amazon.com/dp/B0948BCH24 : ASIN B0948BCH24
https://www.amazon.com/dp/B094716FD6 : ASIN B094716FD6
Learn All of the 23 GoF (Gang of Four) Design Patterns and Implemented them in TypeScript.
Design Patterns are descriptions or templates that can be repeatedly applied to commonly recurring problems during in software design.
A familiarity of Design Patterns is very useful when planning, discussing, managing and documenting your applications from now and into the future.
Also, throughout the course, as each design pattern is discussed and demonstrated using example code, I introduce new TypeScript coding concepts along with each new design pattern. So that as you progress through the course and try out the examples, you will also get experience and familiarity with some of the finer details of programming with TypeScript.
In this course, you will learn about these 23 Design Patterns,
Creational
Factory
Abstract Factory
Builder
Prototype
Singleton
Structural
Decorator
Adapter
Facade
Bridge
Composite
Flyweight
Proxy
Behavioral
Command
Chain of Responsibility
Observer Pattern
Interpreter
Iterator
Mediator
Memento
State
Strategy
Template
Visitor
In the list of patterns above, there are Creational, Structural and Behavioral patterns.
Creational : Abstracts the instantiation process so that there is a logical separation between how objects are composed and finally represented.
Structural : Focuses more on how classes and objects are composed using the different structural techniques, and to form structures with more or altered flexibility.
Behavioral : Are concerned with the inner algorithms, process flow, the assignment of responsibilities and the intercommunication between objects.
Design patterns will give you a useful and common vocabulary for when designing, documenting, analyzing, restructuring new and existing software development projects from now and into the future.
I look forward to having you take part in my course.
Sean Bradley