Benefits of Design Patterns
- Teaches good design
- Proven best practice
- Define set of concepts used in communication
between designers, architects and developers
- Systematicly captures reusable experience, solving re-occurring types of problem
GoF Business Design Patterns
- Fundamental
- Delegation - or when not to use inheritance
- Interface
- Immutable
- Marker Interface
- Proxy
- Creational
- Object Pool
- Partitioning
- Layered Initialization
- Filter
- Structural
- Dynamic Linkage
- Virtual Proxy
- Cache Management
- Behavioral
- Little Language
- Snapshot
- Null Object
- Concurrency
- Single Threaded Execution
- Guarded Suspension
- Balking
- Schedular
- Read/Write lock
- Producer-Consumer
- Two Phase Termination
1. Fundamental Patterns
Fundamental: Involved with the basic handling of objects.
1.1 Delegation (When not using inheritance) [Grand98]
Defines how to differentiates between use of inheritance/delegation.
Inheritance defines a new class, which use the interface of a parent class
while adding extra, more problem-specific methods. Delegation is a way of
reusing and extending the behavior of a class by writing a new class that
incorporates the functionality of the original class by using an instance
of the original class and calling its methods. No. 1 issue in OO is if a
class A should inherit from B or A should use B.
Inheritance is a common way of extending and reusing the functionality of
a class. However, inheritance is inappropriate for many situations:
- Inheritance is useful for capturing is-a-kind-of relationships which
are rather static in nature.
- is-a-role-played-by relationships are awkward to model by inheritance,
where delegation could be a better choice. Using instances of a class to
play multiple roles.
Places where not to use inheritance (but rather delegation):
-
Don't use inheritance where roles interchange. For example, consider the
example of an airline reservation system (SCEA-2 assignment). A reservation
system may include such roles as passenger, ticket selling agent and flight
crew. A class called Person may use subclasses corresponding to each of these
roles. Problem is that the same person can fill more than one of these roles.
A person who is normally part of a flight crew can also be a passenger. Etc.
This way, the number of subclasses would increase exponentially.
-
Don't use inheritance where there will arise a need for an object to be
a different subclass of a class at different points in time, then it should
not be a subclass of that class in the first place. Like in the reservation
situation, pilot may be derived from Person - but when the pilot sells tickets,
he ought to have been derived from ticket seller...
-
Don't use inheritance if you end up in a situation where a class is trying to
hide a method or variable inherited from a superclass from other classes, then
that class should not inherit from that subclass.
-
Don't use inheritance of a utility class, where you're not in control of the
parent class and it may change scope later (inheriting java.util.Vector is a
very, very bad idea since sun may later declare methods depricated). It's
always easier to replace changing a class you just use - than one you inherit
from.
-
Don't use inheritance from a class, which is written very specificly to a
narrow problem - because that will mait it more difficult to inherit from
another class later. Client classes that use the problem domain
class may be written in a way that assumes the problem domain class is a
subclass of the utility class. If the implementation of the problem domain
changes in a way that results in its having a different superclass, those
client classes that rely on its having its original superclass will break.
An even more serious problem is that client classes can call the public
methods of the utility superclass, which defeats its encapsulation.
Benefits:
- Advantage of delegation is that it easy to compose behaviour
at runtime.
- Advantage of delegation is that modification of delegation
class interface has little ripple effects on the interface
of other classes.
- Advantage of delegation is more general purpose than
inheriatance and thus
any extension to a class accomplished by inheritance may
equally well be accomplished by delegation - but
not necessarily the other way around.
- Advantage of inheritance is transparent relationships
which is clear already at compile time compile-time.
Disadvantage:
- Primary disadvantage of delegation is that it is less
structured than inheritance. Simply, relationships between
classes built using delegation are less obvious than those
built using inheritance.
When to use:
Strategies for improving the clarity of delegation based relationships:
- Use in context with other well-known design and coding patterns.
A person reading code that uses delegation will be more
likely to understand the role that the objects play if
the roles are part of a well know pattern or a pattern
the recurs frequently in your program.
- Use in context with consistent naming schemes to refer
to objects in a particular role. For example if multiple
classes delegate the creation of widget objects, the role
of the delegatee object becomes more obvious if all of the
classes that delegate that operation refer to delegatee
objects through a variable called widgetFactory.
- Use in context where purpose of a delegation is clarified by
writing comments.
- Note that it is possible and advantageous to use all three
of these strategies at the same time.
- By determining its superclass, a class' declaration
determines the behavior that a class inherits from its
superclass. Inheritance is not useful when the behavior
that a class should build on is determined at run time.
1.2 Interface / class decoupling [Grand98]
Defines use of interfaces - the process of having a class accessing data
and services provided by other classes independent of their implementation
- by usage of interfaces. To avoid the coupling of classes because they share
a uses/used-by relationship, make the usage indirect through an interface.
The Client class other class implementing the IndirectionIF interface.
IndirectionIF interface provides the indirection that keeps the Client
class independent of the class that is playing the Service role
Benefits:
- Allows development at parallel levels, where top-level
implementation does not necessarily depend on completed
implementationat lower levels (behind the interface) until
execution of code at runtime
-
Postphones actual implementation of lower-level
functionality to later time. Not being forced to implement
lower level functionality first may allow more planning
- possibly resulting in better technical solutions.
- Functionality behind the interface may be substituted at
a later point in time. As long as the interface is not
compromized, the other code running at other levels
should remain unaffected.
Disadvantage:
- Like any other indirection, the Class Decoupling pattern can
make a program more difficult to understand.
When to use:
- In practical life, interface is used in a situation
where an instances of a class use
other objects and all they require of those objects
is implementation of certain methods. Then the class
dependency may be limited to just this requirement by
defining interface including exclusively
those methods - and letting the class use that interface.
- In large projects it can be beneficial to define programmable
interfaces between various participants in projects to
divide up areas of responsability (divide and conquer).
- When actual implementation has not been completely decided
on a project, hiding actual programming logic behind an
interface may allow subsequent changes of low-level
implementation without
affecting higher prgramming layers.
1.3 Immutable [Grand98]
Defines a class that never changes after contruction - used in contexts with
an object shared by multiple other objects and where state is fetched more
often than changed. Immutable class pattern increases the robustness of objects
that share references to the same object and reduces the overhead of
concurrent access to an object. The Immutable Object pattern avoids need
of synchronization between multiple executable threads when sharing an object.
Main advantage is simplicity. Once the object never changes, no code is
necessary to manage changes. While an immutable object is static in nature -
a read-only-object is a class, which presents a read-only interface - but
actually can still change internal state (maybe acting on timeticks). So
the two things are not the same. An immutable class is read-only - but not
the other way around.
Benefits:
- Since the state of immutable objects never changes, there
is no need to write code to manage such changes. Also, there
is no need to synchronize threads that access immutable
objects. Amounts to simpler programming logic.
- Your program uses instances of a class that is passive
in nature. The instances do not ever need to change their
own state. The instances of that class are used by multiple
other objects. Amounts to simple exchange of information.
- If access to a shared object's state information involves
multiple threads and modification of its state information,
then the threads that access the state information must be
synchronized in order to ensure consistency.
Disadvantage:
-
Operations that would otherwise have changed the state of an
object must create a new object. This is an overhead that
mutable object do not incur.
When to use:
-
When having a situation where no method, other than a constructor, should modify the values of a class' instance variables.
-
Or when any method that computes new state information must store that information in a new instance of the same class, rather than modifying the existing object's state.
1.4 Marker / Semantic Interface [Grand98]
Defines procedure of hiding implementation of a class - by using interfaces
that declare no methods or variables to indicate semantic attributes of a
class. Mostly used for utility classes, which must determine something about
objects without assuming they are an instance of any particular class.
Main advantage is providing interface while hiding implementation. Interface
makes it possible to determine how use an object without relying on the object
being an instance of any particular class.
Applicability:
1.5 Proxy [originally GOF pattern]
Defines central object acting as a surrogate for another object, by
receiving all method calls for that other object and subsequently
delegating them to the other object. But in fact this is a pattern used
in various forms, in many other patterns. Proxy objects are
declared as classes in a way that minimizes client object's awareness
that they are actually dealing with a proxy.
Put to use in a number of contexts:
- Used in "The Asynchronous Invocation pattern", the proxy pattern is used in
asynchronous communication to provide quick return from a method, which in
fact takes a long time to complete.
- Used in "The Remote Proxy pattern", the proxy pattern us used to give illusion
that an object on another physical machine appears to exists on the local
machine.
- Used in "The Access Proxy pattern" or "DAO pattern", the proxy pattern controls
access to a service or data, possibly improving performance by caching
identical, frequent requests.
- Used in "The Virtual Proxy pattern", the proxy pattern gives the illusion that
an object exists before it actually does.
Benefits:
- Provides a stable interface for a service, which might not in real life be
contiously able to offer the services at all times or places (fx. the actual service
may actually only be available within a specified opening hours).
- Hiding implementation and complexity of the real service provider behind the proxy.
- Access to a service prover should be controlled without adding
complexity or putting restriction on implementation by coupling the service to the access control policy.
- Transparent access to the service provider will reduce complexity on the client side.
When to use:
- Applicable in numerous SOA contexts.
- Used where there is a need for an object to receive method calls on behalf of
another object. Client objects call the proxy object's method. The proxy
object's methods do not directly provide the service that its clients
expect. Instead, the proxy object's methods call the methods of the object
that provides the actual service.
2. Creational Patterns
Creational: Related to the object instantiation process.
2.1 Object Pool [Grand98]
Defines a performance efficient way to re-use objects, which might be
expensive to create - by recycling a limited number of used objects
through a pool.
Benefits:
- Simulate infinite system resources where the a process may require
an unlimited number of objects - provided(!) only a finite number is used simultaneously
- Overall reduced need for system ressources - from basic fact that objects are reused.
- Scalable usage of system resources - since pool can take various size
- and may be restrained by runtime limitations for the system.
- Improved speed efficiency - since occasional time-consuming creation of objects can be avoided.
- Less need for garbage handling since objects are not disposed of so frequently.
When to use:
- used in a context where a process has a need for a large number
of identical objects with changing contents. Could fx. be a database connection
with need to map database objects - or a data compression program with need to
maintain a finite windows into backlog data.
3. Partitioning Patterns
Partitioning: organizing complex actors and concepts into multiple classes
3.1 Layered Initialization [Grand98]
Defines encapsulation of common and specialized logic in separate
objects. Addresses problem where implementation requires common logic
to be used when deciding, which specialized subclass to create. In such
a situation, it is not always optimal to just define a class that
encapsulates common logic and then define subclasses that contain the
different forms of specialized logic - because that does not allow the
common logic to be used at implementation level to actually pick out the
most appropriate specialized logic.
Benefits:
-
Processing of complex data is done by a specialized class
Transparent encapsulation of the logic to choose a specialized class
to process complex data.
-
To maintain low coupling, only one of the objects that participate in the
Layered Initialization pattern should be visible to the object that provides
the complex data.
-
Encapsulating the logic deciding which class to instantiate into a separate
class reduces the effort required to maintain the other classes. If a database
migrates to a different type of engine or a new class becomes available
that provides better access to it (JDBC driver), then the corresponding
change in the program is limited to the class that decides what class to
instantiate (which JDBC driver to use).
When to use:
- use in situations where preprocessing must be done on
selection data before deciding which specialized class to instantiate.
3.2 Filter [BMRSS96]
Defines how to obtain complex transformations of data by combining simple
filters with compatible interfaces. By using output from one filter as input
to another filter, the filters can work together to create different
computations and transformations on streams of data. Each filter may
concentrate on simple and understandable computations. Working together, the
filters can provide transformations on much higher level of complexity.
Major force with this patteren is substitutable and transparent transformation
of date, which is usefull during data analysis.
Source Filter... Sink filter... The differeence is who take
initiative to move the data. Whether pushing or pulling data through the
filter. Similar to the difference between polling and interrupts (polling
when you ask for data... interrupt when waiting to receive data):
- Source Filter... data flows as a result of a data sink object calling a
method in a data source object (data are actively moved)
-
Sink filter... data flows when a data source object passes data to a method
of a data sink object (data are received).
In a situation with multiple filters, they should implement a
a common superclass for all classes so an instance of one can use an
instance of another without have to care which class the object instantiates.
Diagram for the version of Filter, where data sink objects get data by
calling methods in data sources
Diagram for the version of Filter where data source objects pass data to
methods of data sink objects:
Benefits:
- offers abstraction,where an overall complex operation (permutation)
is broken down into simple operations
- reusability of the filters, when applied in changed order in another
context - or atleast reducing the scope of changes when it is possible to
restrain modifications to individual filters
- speed effiency, where each filter may be implemented in a in the most
efficient way. Fx. the permutation in DES encryption is done much
more efficient as a hash-table lookup at first - than being done as
a difficult understandable side-activity during the general encryption process.
- filters are runtime interchangeable - since they
does not maintain internal state (and perhaps even may implement the same
interface).
When to use:
- where data is to undergo several transformations
in changing order. After completing each transformation, the process is left
stateless. Only the order of transformation may change. Fx. different layers of
encryption.
4. Structural Patterns
Structural: organizing object to work with each other.
4.1 Dynamic Linkage (aka Dynamic class-loading) [Grand98]
Defines procedure of arbitrary loading and usage of classes at runtime - with
only requirement for the classes, that they implement an interface,
known at compile-time. Dynamic linkage simply reduces compile-time
restrictions on behalf of more run-time freedom regarding class implementation.
The procedure allows change in implementation of classes at run-time,
without neither re-compiling nor stopping the program.
Benefits:
- to postphone design decision as far as possible - possibly to the runtime level
- at runtime, the process will be able to load and use
arbitrary classes that it has no prior knowledge of
- classes which may be implemented at a point in time
after the final completed implementation of the
main process.
When to use:
- When design decisions are either not firm - or may require
flexibility to undergo changes - at runtime level
- as presented, it is required that the environment knows
about the AbstractLoadableClass class and that the loaded
class knows about the EnvorinmentInterface. In cases which
requires less structure, other mechanisms for interoperation
are possible - such as fx. reflection in Java, where one object
may ask another about which methods it implements - and
consequely call a method it had not prior interface information
about.
4.2 Virtual Proxy [Larman98]
Defines procedure of using objects before they're actually created. This is
possibly by using a proxy interface, behind which the objects are created
at a later date. Reasons for doing that may be that objects are expensive
to create and later there is a possibility that the objects will not required
anyway... In such a scenario, it may be beneficial to postpone instantiation
until the existance of objet is absolutely required. It goes without saying,
that the proxy needs to implement the same interface as the object and the
proxy need to be able to evaluate, when creation of the object is required.
Benefits:
- avoiding time overhead while constructing classes
(and perhaps populating with all required data). In the end it might
turn out to be completely unnecessary to instantiate it at all.
- delaying instantiation of classes until actual use will allow a more
continous workload distribution - avoiding processing bottlenecks in
multithreaded systems, both in regard to the instantiation process and
probably also the subsequent ressource deallocation (garbage handling).
- clients (classes) using the proxy need not be aware if the service class
is loaded or not, an instance of it exists or that the class even exists
When to use:
- where there is a significant cost overhead
while creating classes - or it is impractical for
arbitrary reasons
- where the functionality of the service is not necessarily
required right after instantiation - but may actually be
used a little later on. Fx. even though it is usual to
declare variables in a common block in a program flow, it
does not necessarily mean they are all immdiately used
- some of them may actually turn out never to be used at all.
4.3 Cache Management [Grand98]
Defines strategy of keeping a local copy of objects fetched from outside of
a program, such as a remote server or database. Reason for doing that
is normally performance improvements in order to save the relatively high
expense of fetching such objects. "Cache management" defines the strategy of
managing the cache. Fx. how many objects to keep in cache, which to keep,
how long to keep them, etc. In data compression, managing the cache of
historical data decides not only speed - but also prediction probablity for
processing new data - and thus influences the overall compression ratio.
Link: http://www.developer.com/java/other/article.php/630481
Benefits:
- performance - retrieving objects from external sources can
take thousands or even millions of times longer that accessing an object that
is already cached in internal memory
Drawbacks:
- determining the size of a cache is usually a runtime issue and can
be a very challenging (let's be honest... difficult) task. It depends on
the amount of available system ressources as well as the user pattern.
This runtime paramenter may require runtime interaction. But however
difficult to estimate, it is essential to the performance of the system.
- an appropriate scheeme is required for updating and managing cache.
Really there are two issues: which items to keep and which to dispose.
Assuming newest items are always kept, the challenge is usually
selecting which object to discard. This vital process directly
affects the cache hit rate. If the discarded
object is always the next one requested then the hit rate will be 0%. But
if the object discarded will not be requested before any of the other
object in the cache, then discarding will have least negative
impact on hit rate. Making a good choice of which object to
discard requires a scheme for forecasting future object requests... This
is a runtime issue, which can be very difficult to deal with a compile
time.
- read consistency is required - meaning that the cache always must reflect updates to
information in the original object source. If the objects being cached are
database entries, then the data in the object source can change while the
data in the cache will no longer be current. To achieve absolute read
consistency while caching objects in a cache, the object source must
notify the program of updates to cached objects. This may be accomplished
using the Publish-Subscribe pattern.
- write consistency is required - meaning that updates to the the original object
source always reflects updates to the cache.
- get the design right (!) to incorporate use of cache - fx. when
encapsulating a loop construction within another loop - and letting the inner
loop process a sequence of objects, beginning with those which just tipped out
of the cache - might not make the best use of the cache (assuming the sequence
of objects exceeds cache size).
When to use:
- cache works best in an environment with plenty reads and few modifications
- otherwise cache management processes will be fulltime occupied by
synchronizing cache objects with the actual data they represent.
- cache works specificly well if a limited number of objects are used frequently
- otherwise managing a cache introduced an unnecessary work overhead instead.
- the size of the cache should correspond to easy accessible system
resources. If storing cache entries in a not-so-easy-accessible way,
retrieving cache objects will end up taking the same time as retrieving the
original items
- systems often needs to be designed with the cache system in mind !!!
Using the cache right is essential to performance. Fx. when
encapsulating one loop operation within another, it is important to
access elements close to each other at all times - in order to be sure to
operate on elements in the cache. Fx. failing to get loop constructions
right will defeat the whole purpose of using a cache.
secondary cache is a concept introduced where it is realized that
is not possible to achieve a high cache hit rate with available memory by fetching
objects from the original data source. Then it may be considered to use a
secondary cache - which is typically a disk file, used as a cache.
The secondary cache may takes longer to access than the primary cache
that is in memory. But as long as it takes sufficiently less time to fetch
objects out of a local disk file than fetching them from the
original object source, it can be advantageous to use this secondary cache.
5. Behavioral Patterns
Behavioral: the process of organizing, managing and combining
responsabilities between objects.
5.1 Little Language [Grand98]
Defines an approach where you define a meta language to express solutions
to problems using the same common break-down solution elements. Goes without
saying, this only works in a scenario where solutions to problems are confined
to various combinations of a small number of elements. Then a little language
may be defined to express the solutions. This pattern is basicly a
modification of the Interpreter pattern [GoF95] and a note by Jon
Bentley[Bentley] about little languages.
With a language follows definition of syntax and semantics. Syntax defines
the "grammer" fx. what words and symbols make up the language and how they
may be combined. Semantics defines the meaning of the words, symbols and
their combinations that make up the language.
Explanations of the class roles in the Little Language pattern:
- Client is an instance of a class running a little language
program, feeding it whatever data it needs and using the results that the
program produces. It creates an instance of the Parser class to parse programs
that it supplies through InputStream objects. The Parser object's parse method
returns an instance of the AbstractNonterminal class to the Client object.
That object is the root of a parse tree. The Client object calls the
AbstractNonterminal object's execute method to run the program.
- Lexical Analyzer is used when a Parser object's parse method is called.
It creates a LexicalAnalyzer object to read characters from the same
InputStream object that was passed to it. The LexicalAnalyzer object reads
characters from the InputStream object, recognizes terminal tokens it finds
using the lexical rules and return those tokens to the Parser class when it
calls the LexicalAnalyzer object's nextToken method. The nextToken method
returns the next terminal token that it finds in the input.
- Parser gets its parse method called from the client in order
to parse input from InputStream objects by matching the tokens in the input
against the productions of the grammar. The parse method builds a parse tree
as it matches the productions and returns a reference to the parse tree's root
to the Client object.
- AbstractNonterminal is the abstract superclass
of all of the classes whose instances can be parse tree nodes. A Client
object calls its abstract execute method to execute the program.
- ConcreteNonterminal1, ConcreteNonterminal2 are used a parse tree
nodes.
- TerminalToken defines no variables or methods.
Its subclasses correspond to the terminal tokens that the LexicalAnalyzer
class recognizes.
- InputStream is used to read a stream of characters from the data
source.
Benefits:
- reduces the requirement for different implementations
while introducing an abstraction level to deal with multiple
problems, which are fundamentally just different combinations of a
limited number of elements or operations
- parser for little languages is usually small and simple enough
for it to be understood in its entirety. Spreading the parsing logic
over multiple classes makes understanding much more difficult.
When to use:
- when dealing with multiple
problems, which are fundamentally just different combinations of a
limited number of elements or operations
5.2 Snapshot [Grand98]
Defines generalization of the Memento Pattern [GoF95]. Defines procedure to
take a "snapshot" of an objects state.
Snapshot pattern using memento objects:
Explanations of the class roles in the variation of the Snapshot pattern that uses Memento objects:
- Originator is a class whose instance's state information is to
be saved and restored. When its createMemento method is called, it creates
a Memento object that contains a copy of the Originator object's state
information. Later, you can restore the Originator object's state by p
assing a Memento object to its setMemento method.
- Memento is a private static class of the Originator class that
implements the MementoIF interface. Its purpose is to encapsulate snapshots
of an Originator object's state. Because it is a private member of the
Originator class, only the Originator class is able to access it. Other
classes must access instances of the Memento class either as instances
of Object or through the MementoIF interface.
- MementoInterface is the interface used by classes other than the
Originator class to access Memento objects. Interfaces in this role may
declare no methods. If they do declare any methods, the methods should
not allow the encapsulated state to be changed. That ensures the consistency
and integrity of the state information.
- Caretaker maintains a collection of Memento objects. After
a Memento object is created, it is usually added to a Caretaker object's
collection. When an undo operation is to be performed, a Caretaker object
typically collaborates with another object to select a Memento object.
After the Memento object is selected, it is typically the Caretaker
object that calls the Originator object's setMemento method to restore
its state.
Snapshot pattern serialization:
Explanations of the class roles in the variation of the Snapshot pattern that uses serialization:
- Target is the passive receiver of objects containing the
state to preserve. In any case, either the ObjectOutputStream object or
ObjectInputStream object do all of the work. ObjectOutputStream object
converts the state of instances of classes in this role to a byte stream.
ObjectInputStream object restores the state of instances of classes in this
role from a byte stream.
- ObjectOutputStream is usually the destination data stream, which
accepts serializable objects (in J2EE it would be java.io.ObjectOutputStream).
It discovers and accesses a Target object's state information and writes it
to byte stream with additional information that allows an ObjectInputSteam
object to restore the state information.
- OutputStream An object in this role is an instance of a
subclass of the standard Java class java.io.OutputStream. If the state
information needs to be saved indefinitely, then the OutputStream object
may be a FileOutputStream. If the state information needs to be saved no
longer than the duration of a program run, then the OutputStream object
may be a ByteArrayOutputStream.
- ObjectInputStream is the source data output stream, which
delivers deserialized objects (in J2EE usually it would be
java.io.ObjectInputStream or a subclass of it). Instances of these classes
read serialized state information from a byte stream and restore it. If you
do not override the default behavior, an ObjectInputStream object puts the
original Target object's state information in a new instance of the Target
object's class. Using techniques described under the "Implementation"
heading, you can arrange for ObjectInputStream objects to restore the
saved state to an existing instance of the Target class.
Benefits:
- Snapshot pattern reduces complexity of saving and restoring an
object's state - seperating the issue from its class
Drawbacks:
- not very suitable for undoing a fine grained sequence of
commands. Making many snapshots of an object can consume a
prohibitive amount of storage. Capturing the changes to an
object's state (the Command pattern) may be more efficient
When to use:
- where it is necessary to make a snapshot of an objects
state (by pattern definition)
5.3 Null object [Woolf97]
Defines usage of a simple object (the Null Object) used to initialize
variables not used. Replaces commenly known practice of initilizing
variables using "null" (nil) pointer to indicate the absence of an object to
delegate an operation to. Using null (nil) to indicate the absence of
such an object requires a test for null (nil) before each call to the other
object's methods. Instead of using null (nil), the Null Object pattern uses a
reference to an object that doesn't do anything (empty methods).
Benefits:
- simpler code may result from remvoing need to check if
objects exists before being put into use. Calling the
empty methods on the Null Object will just result in no
operations - just as a check for null would have
caused the object to be skipped.
Drawbacks:
- it is usually more performance efficient to do a single
check for null and skipping further work on an object than
calling numerous empty methods - which may subsequently
call a cascade of other empty methods in a possible
pyramid-like pattern. Even the simplest operations can create
performance overhead when performed enough times.
- the pattern may increases the number of classes in the
program, where there is not already a class or interface in the
AbsractOperation role. Thus the Null Object pattern may
introduce more complexity through the introduction of
additional classes than it removes by the simplification of
code.
When to use:
- where requirements to the sourcecode dictates no checks
for existance of objects (more or less by pattern definition)
6. Concurrency Patterns
Concurrency: coordinating concurrent operations with regard to
shared ressources and the sequence of operations.
6.1 Single Threaded Execution [Grand98]
Defines procedure of scheduling single-threaded execution of operations,
which are not suitable for concurrent execution because of shared ressources
or data.
This pattern is not necessarily an excuse for bad design, since at the
most basic level in a program, there will often be operations, which are
not threadsafe. Fx. ensuring file pointer is not moved by others during
disk I/O, writing uncut messages to the console, etc. The whole idea of
good concurrency design is to make these occasions as few and isolated as
possible. A lot of these non-threadsafe operations are dictated by the
operating system (OS) and maybe not possible to correct. Fx. in some OS'
the number of file pointers has been limited and using one for each
execution thread has simply not been realistic. In that case, good design
is about dealing effectively with these restraints - not just passing on
problems at another level.
The class in the diagram has two kinds of methods:
- unguarded methods: safeOp1, safeOp2, which can be safely called
concurrently by different threads
- synchronized methods: unsafeOp1, unsafeOp2, which cannot
be safely called concurrently by different threads
When different threads call the guarded methods of a Resource object at the
same time, only one tread at a time is allowed to execute the method. The
rest of the threads wait for that thread to finish
Benefits:
- ensures proper execution of non-threadsafe operations
- which could be a poor excuse for bad design or
simply the consequences of circumstances, which are not
possible to be changed.
-
When to use:
- when there are non-threadsafe operations, which
can not be executed otherwise. Going to the lowest level of
operations, there will quite usually be operations (fx.
disk I/O), which will require protection by semafores or
other means (fx. 'synchronized' in J2EE) to ensure
singlethreaded execution.
6.2 Guarded Suspension [Lea97]
Defines the suspended execution of a method call until a precondition is satisfied.
Waiting for non-threadsafe ressourcess is unavoidable where multiple threads
request simultaneous access. Here access is restrained to only one thread
- which must be allowed to execute and leave the guarded state in order for
another to get in and do its task.
Benefits:
- using java.lang.Object.wait() and java.lang.Object.notify()
it is possible to efficiently restrain access to non-threadsafe
operations without the need of complex semafore semantics.
When to use:
- (as the definition states) where execution of a method must
be restrained from execution until a precondition is satisfied.
6.3 Balking [Lea97]
Defines procedure of returning from a method call without doing anything if the
object is in an invalid state.
This procedure should be considered as just one out of several (others) alternatives
to handle commen error situations:
- as in design pattern "Guarded Suspension" delaying completion of the call until the object is back in a valid state
- throwing exception to be handled by the caller
- return an error code (assuming the caller is ready to check for one)
Benefits:
- safe handling of calls to objects, which are in an inappropriate to execute a method call
When to use:
- when an object is in an invalid state, where method call can not return
- and it is inapppropriate to postphone execution until state becomes valid
- and suspending execution can be done safely, where the object is in an invalid state
6.4 Schedular [Lea97]
Defines manager for the execution order of threads (the schedular), which
executes specified threads at an appropriate time and in a defined context.
Schedular pattern suggests definition of a scheduling policy without
restricting itself by any implementation limitations. Scheduling is fx.
relevant when managing non threadsafe code, where execution order may be
significant.
Explanations of the class roles in the Scheduler pattern:
- SchedulerRequest must implement the interface in the
ScheduleOrdering role. SchedulerRequest objects encapsulate a request
for a SchedularProcessor object to do something.
- SchedulerProcessor must perform computation described by a
SchedulerRequest object. May face multiple SchedulerRequest objects
to process, but can only process each one sequentially after each other.
A SchedulerProcessor object delegates to a Scheduler object
the responsibility of scheduling ScheduleRequest object for execution in
sequential order.
- Scheduler manages SchedularRequest objects for processing by
a SchedulerProcessor object. To decrease restrints and increase
reusability, a Scheduler class have no knowledge of the Request class that
it schedules. Instead, it accesses SchedulerRequest objects through the
ScheduleOrdering interface that they implement.
- ScheduleOrdering is an interface implemented by
SchedulerRequest objects. Interfaces in this role serve the purposes:
1) By referring to a ScheduleOrdering interface, Processor classes avoid
a dependency on a SchedulerRequest class. 2) By calling methods defined by the
ScheduleOrdering interface, SchedulerProcessor classes are able to delegate
the decision of which SchedulerRequest object to schedule for processing next,
which increases the reusability of SchedulerProcessor classes. The above
class diagram indicates one such method named scheduleBefore.
Benefits:
- provides explicitly control over thread execution
- scheduling policy is encapsulated in its own class and is
possibly reusable - atleast on the abstraction level
Drawbacks:
- since Scheduler pattern allows increased flexibility
beyond just serializing execution of threads one after the other in a random
sequence (as by the synchronized method), it may cause significant processing
overhead relative to applying simple synchronization.
When to use:
- when it is required to execute threads in a specific
order, at specific time or in a specific context (assuming
dependencies on conditions). Schedular is a concept
incorporated into some application servers (fx. BEA weblogic)
where execution of processes may be configured to occur under
a defined set of circumstances (fx. point in time).
6.5 Read/write Lock [Lea97]
Defines a context with allowed concurrent read access to an object but
required exclusive access for write operations
Explanations of the class roles in the Scheduler pattern:
- Data has methods to get/set its instance information.
Any number of threads are allowed to concurrently get a Data object's
instance information. No two threads are allowed to simultaneously set
its instance information. Its set operations must occur seperately under
conditions 1) set done one at a time, 2)no concurrent get operations being
executed. Data objects must coordinate their set and get operations so
that they obey those restrictions.
- ReadWriteLock offers methods to synchronize safe access to data
while honoring the two conditions above. Before the get method in Data
retrieves anything, it calls the associated ReadWriteLock object's
readLock method, which issues a read lock to the current thread. During
the time the thread possesses a read lock, the get method can be sure
that it is safe for it to get data from the object. Since while there
are any outstanding read locks,
the ReadWriteLock object will not issue any write locks. In case of
outstanding write locks (when the ReadWriteLock object's readLock method is
called), it does not return until all the outstanding write locks have
been relinquished by calls to the ReadWriteLock object's done method.
Otherwise, calls to the ReadWriteLock object's readLock method
return immediately.
Benefits:
- transparently coordinates concurrent calls to a Data object's get and set
methods so that calls to the object's set methods do not
interfere with each other or calls to the object's get
methods
-
in a context with substantially more calls to get than set - the Read/Write
Lock pattern may offer superiour performance compared to the
approach where just using single-thread-execution
(synchronizing) for all get/set calls to the
Data object - since the Read/Write Lock pattern actually
allows concurrent calls to (frequently occurring) get.
Drawback:
-
in a context with substantially less calls to get than set - the Read/Write
Lock pattern may offer inferiour performance compared to the
approach where just using single-thread-execution
(synchronizing) for all get/set calls to the
Data object - since the time overhead used to coordinate
calls to get/set will not be justified by a sufficient number of concurrent
calls to get.
When to use:
- in situations, which require control of read/write
access to a data object - and where there may be a
significant number of attempts to retrieve data compared to
the number of times data is stored
6.6 Producer-Consumer [-]
Defines procedure for asynchronous production and consumption of information or objects
Explanations of the class roles in the Producer-Consumer Pattern
- Producer supply objects that are used by Consumer objects. Instances of Producer classes produce objects asynchronously of the threads that consume them. That means that sometimes a Producer object will produce an object when all of the Consumer objects are busy processing other Consumer objects. Rather than wait for a Consumer object to become available, instances of Producer classes put the objects that they produce in a queue and then continue with whatever they do.
- Queue act as a buffer for objects produced by instances of Producer classes. Instances of Producer classes place the objects that they produce in an instance of a Queue class. The objects remain there until a Consumer object pulls them out of the Queue object.
- Consumer use the objects produced by Producer objects. They get the objects that they use from a Queue object. If the Queue object is empty, a Producer object that wants to get an object from it must wait until a consumer object puts an object in the Queue object.
Benefits:
- reduced dependencies as objects are produced or
received asynchronously of their use or consumption
When to use:
- when decoupling of sending/receiving data is required
6.6 Two Phase Termination [Grand98]
Defines procedure for orderly shutdown of a thread or process through the
setting of a latch. The thread or process checks the value of the latch at
strategic points in its execution.
Explanations of the class roles in the Scheduler pattern:
- Client is the central class initiating the concurrent threads or
processes and also subsequently initiating a coordinated shutdown of them all
- Thread1, Thread1, .. Thread(N) are the threads, which are required to call
the isShutdownRequested method in the Terminator class at strategic points
in their execution - where they will be informed if they're requested to
shut down
- Terminator is used by the main process (Client) to initiate a
shutdown (by calling doShutDown()). Each of the process' threads
will call the isShutdownRequested method at strategic points in their
execution. If it returns true, the thread cleans up and shuts down.
When all the application threads have died, the process exits.
Benefits:
- should provide better conditions for a thread or process
to clean up after itself after forcibly being shut down
Drawbacks:
- Two Phase Termination pattern can delay the termination
of a process or thread for an unpredictable amount of time
- assuming there is no timeout after which all threads or
processes will be forced to shutdown. Delay is possible
because nirmally: 1) it is not guaranteed that the threads
in finite time will actually pick up a shutdown signal from
the Teminator class' method isShutdownRequested() - and
2) having requested the shutdown request, they're not
guaranteed to actually shutting down within a finite time period
When to use:
- where a controlled shutdown is required, which leaves
data processed bt threads or processes in a consistant state
|