Title: Chapter 24: Applying GoF Design Patterns
1Chapter 24 Applying GoF Design Patterns
- 24.1. Introduction
- The Gang of Four (GoF) are four authors Erich
Gamma, Richard Helm, Ralph Johnson, John Vissides
who wrote a very influential book on Object
Oriented design Patterns - Design patterns elements of reusable
object-oriented software - Addison Wesley, 1995, ISBN 0201633612.
A "classic" of software engineering, that is
timeless. It presents 23 patterns useful during
object design. It is more of a follow-up to this
course on OO Design.
2- 24.2 Adapter
- Many of the GoF patterns are in fact
specialisations of the generic GRASPs in other
words, Larmans patterns are more general than
many ordinary patterns - That is not surprising more than 500 patterns
have been documented most overlap and/or are
refinements of others - It is nevertheless a good opportunity to review
the general solution applied - The POSs problem of dealing with many tax
calculators which was solved by applying the
Polymorphism pattern is actually an example of
the Adapter pattern. - Problem How to resolve incompatible interfaces,
or provide a stable interface to similar
components with different interfaces? - Solution Convert the original interface of a
component into another interface, through an
intermediate adapter object.
3- This solution basically adds a level of
indirection within the application
4- Dynamically, a particular adapter instance will
be instantiated for the chosen external service
- (SOAP Simple Object Access Protocol, is a
protocol for exchanging XML-based messages over a
computer network, normally using HTTP.)
5- In conclusion, Adapter supports Protected
Variations with respect to changing external
interfaces or third-party packages through the
use of an Indirection object that applies
interfaces and Polymorphism. - 24.3 Factory
- In the prior Adapter pattern solution for
external services with varying interfaces, who
creates the adapters? And how to determine which
class of adapter to create, such as
TaxMaster-Adapter or GoodAsGoldTaxProAdapter? - A naive solution, would be that some domain
object creates them, but this obviously would
lower their cohesion (and also breaks the
separation of concern principle). - A solution, is to apply the Factory pattern, in
which a Pure Fabrication "factory" object is
defined to create objects.
6- This has the following advantages
- Separate the responsibility of complex creation
into cohesive helper objects. - Hide potentially complex creation logic.
- Problem Who should be responsible for creating
objects when there are special considerations,
such as complex creation logic, a desire to
separate the creation responsibilities for better
cohesion, and so forth? - Solution Create a Pure Fabrication object called
a Factory that handles the creation. - Example
7(No Transcript)
8- Note that in the ServicesFactory, the logic to
decide which class to create is resolved by
reading in the class name from an external source
and then dynamically loading the class. This is
an example of a partial data-driven design. This
design achieves Protected Variations with respect
to changes in the implementation class of the
adapter. Without changing the source code in this
factory class, we can create instances of new
adapter classes by changing the property value. - Factories are often accessed with the Singleton
pattern. - 24.4 Singleton
- First, observe that only one instance of the
factory is needed within the process. Second,
quick reflection suggests that the methods of
this factory may need to be called from various
places in the code, as different places need
access to the adapters for calling on the
external services. Thus, there is a visibility
problem How to get visibility to this single
ServicesFactory instance?
9- One solution is pass the ServicesFactory instance
around as a parameter to wherever a visibility
need is discovered for it, or to initialize the
objects that need visibility to it, with a
permanent reference. This is possible but
inconvenient an alternative is the Singleton
pattern. - Problem Exactly one instance of a class is
allowed it is a "singleton." Objects need a
global and single point of access. - Solution Define a static method of the class
that returns the singleton. - Example
10- Thus, the key idea is that class X defines a
static method getInstance that itself provides a
single instance of X. - With this approach, a developer has global
visibility to this single instance, via the
static getInstance method of the class.
11- public class Register
-
- public void initialize()
-
- do some work
- // accessing the singleton Factory via the
- // getInstance call
- accountingAdapter ServicesFactory.getInstan
ce().getAccountingAdapter() - do some work
-
- // other methods
- // end of class
12- 24.5 Strategy
- The next design problem to be resolved is to
provide more complex pricing logic, such as a
store-wide discount for the day, senior citizen
discounts, and so forth. - The pricing strategy (which may also be called a
rule, policy, or algorithm) for a sale can vary.
During one period it may be 10 off all sales,
later it may be 10 off if the sale total is
greater than 200, and myriad other variations.
How do we design for these varying pricing
algorithms? - Problem How to design for varying, but related,
algorithms or policies? How to design for the
ability to change these algorithms or policies? - Solution Define each algorithm/policy/strategy
in a separate class, with a common interface.
13 14- At any one time there is only one pricing
strategy, but it can change very often (e.g.
every hour). - In a collaboration diagram the Sale object
delegates some of the work to the pricing
strategy object
15- The corresponding Design Class Diagram is
16- There could be many different kinds of pricing
strategy and in addition they must be changeable
at runtime. It therefore makes sense to use the
Factory pattern to create a Pure Fabrication
class responsible for maintaining the current
pricing strategy. - As with the ServicesFactory, it can read the name
of the implementation class of the pricing
strategy from a system property (or some external
data source), and then make an instance of it. - This is uses the data-driven approach one can
change the current pricing strategy at start up
time but also at run time. - A different factory is used (rather than reuse
the previous ServicesFactory) because the purpose
is quite different (helps maintaining the
cohesion of the Factories)
17- Note that because of the frequently changing
pricing policy, it is not desirable to keep the
local created strategy instance in a field of the
PricingStrategyFactory (unlike in the
ServicesFactory), but rather to re-create one
each time, by reading the external property for
its class name, and then instantiating the
strategy.
18- Of course as with most factories, the
PricingStrategyFactory will be a singleton (one
instance) and accessed via the Singleton pattern. - When a Sale instance is created, it can ask the
factory for its pricing strategy
19- Finally, we need to consider the initial discount
values (e.g. the discount percentage). - Best to factor out these in an external data
store (a database, a script file, an xml file
etc.) accessed at run-time. This is again
data-driven design. - Hence, Protected Variations with respect to
dynamically changing pricing policies has been
achieved with the Strategy and Factory patterns.
Strategy builds on Polymorphism and interfaces to
allow pluggable algorithms in an object design. - We have also used the Singleton pattern and
applied data-driven design where possible.
20- 24.6 Factories in C
- As seen, Factories allow us to create instance(s)
of a class that we do not know at compilation
time. A factory is responsible for
creating/destroying a set of objects and
providing references to them on request. - In fact we would like to write
- class Object / ... /
- class Rocket public Object / ... /
- class Npc public Object / ... /
- type aType Rocket
- //anonymously and dynamically allocate
- Object pObject new aType
- unfortunately that is not possible (compiler
wont let you). - A simple and common approach to writing a factory
is to create an enumerated type that represents
the different classes of object you can create
21- enum GameObjectType
- PLAYER,
- ENEMY,
- POWERUP,
- WEAPON,
- // ... rest of the objects...
-
- GameObject ObjectFactoryCreate(GameObjectType
type) - switch (type)
- case PLAYER return new Player()
- case ENEMY return new Enemy()
- case POWERUP return new Powerup()
- case WEAPON return new Weapon()
- default assert( ObjectFactoryCreate
unknown type) -
- This is not very flexible (hardcoded) it is
impossible to add new object types without
modifying the code ...
22- If more flexibility is required then we should
move to a data driven solution to implementing
the Factory. - e.g. by using strings compares rather than the
enumeration type.
23- 24.7 Conclusions
- There are many other patterns that might be
useful Composite, Façade, Observer/Publisher
(Event Modeller to enforce Model View Separation
Principle). - In practice we must absolutely remember that our
ultimate goal is not just code that works, but
also - especially for highly variable projects
such as games code that is also maintainable
and extensible. - Basic, data-driven designs is probably your best
friend to achieve this (see Protected Variations
design pattern). - Factories play a crucial role in game development
and are essential to data driven designs as we
are need to constantly create new objects of all
sorts of different types that we cannot predict
ahead of time.