|
|
Design Patterns
Iterator
-
Also known as Cursor.
-
Provide a way to access the
elements of an aggregate object sequentially without exposing its underlying
representation.
-
To traverse the aggregate
in different ways, depending on what you want to accomplish.
-
to have more than one
traversal pending on the same aggregate object
-
The key idea in this
pattern is to take the responsibility for access and traversal out of the
aggregate object and put it into an iterator object
-
The Iterator class defines
an interface for accessing the aggregate’s elements.
-
An iterator object is
responsible for keeping track of the current element; that is, it knows
which elements have been traversed already.
-
Separating the traversal
mechanism from the List object lets us define iterators for different
traversal policies without enumerating them in the List interface. For
example, FilteringListIterator might provide access only to those elements
that satisfy specific filtering constraints.
-
Use
the Iterator pattern
-
to
access
an aggregate object's contents
without
exposing its internal representation.
-
to support
multiple traversals
of aggregate
objects.
-
to provide a
uniform
interface for
traversing different aggregate structures (that is, to support polymorphic
iteration).
-
The Iterator pattern has
three important
consequences:
-
It supports variations in
the traversal of an aggregate. Complex aggregates may be traversed in many
ways. For example, code generation and semantic checking involve
traversing parse trees. Code generation may traverse the parse tree
inorder or preorder. Iterators make it easy to change the traversal
algorithm: Just replace the
iterator
instance with
a different one. You can also define Iterator
subclasses
to support new traversals.
-
Iterators
simplify the Aggregate
interface.
Iterator's traversal interface obviates the need for a similar interface
in Aggregate, thereby simplifying the aggregate's interface.
-
More than one traversal
can be pending on an aggregate. An iterator keeps track of its own
traversal state. Therefore you can have
more than one traversal in progress
at
once.
-
external iterator / Active iterator
à
client controls
the iteration
à
Clients that use an external iterator must advance the traversal and request
the next element explicitly from the iterator
à
more flexible e.g.: easy to compare two collections for equality
-
Internal iterator / Passive
Iteratorà
the iterator controls iteration.
à
The client hands an internal iterator an operation to perform, and the
iterator applies that operation to every element in the aggregate.
à
easier to use, because they define the iteration logic for you.
-
It’s easier just to use an
internal
iterator (rather
than external iterator) for recursive
aggregate structures like
composite. It
can record the current position simply by calling itself recursively,
thereby storing the path implicitly in the call stack.
-
Cursor
à
iterator to store just the state of the iteration. It merely points to the
current position in the aggregate
à
aggregate defines the traversal algorithm
-
Cursors
are a simple example of the Memento pattern and share many of its
implementation issues.
-
Why
Cursor??
à
If the iterator is responsible for the traversal algorithm, then it's easy
to use different iteration algorithms on the same aggregate, and it can also
be easier to reuse the same algorithm on different aggregates. On the other
hand, the traversal algorithm might need to access the private variables of
the aggregate. If so, putting the traversal algorithm in the iterator
violates the encapsulation of the aggregate.
-
Robust
iterator
à
ensures that insertions and removals won't interfere with traversal, and it
does it without copying the aggregate.
-
NullIterator
à
is a degenerate iterator that's helpful for handling
boundary
conditions. By
definition, a NullIterator is always done with traversal; that is, its
IsDone operation always evaluates to
true.
-
Composite:
Iterators are often applied to recursive structures such as
Composites.
-
Factory
Method:
Polymorphic iterators rely on factory methods to instantiate the appropriate
Iterator subclass.
-
Memento
: is often used in conjunction with the Iterator pattern. An iterator can
use a memento to capture the state of an iteration. The iterator stores the
memento internally.
MEDIATOR
Pattern:
-
Define an object that
encapsulates how a set of objects
interact.
-
promotes
loose
coupling by
keeping objects from referring to each other explicitly, and it lets you
vary their interaction
independently
-
Colleagues
send and receive requests from a Mediator object.
-
The mediator implements the
cooperative behavior by routing requests between the appropriate
colleague(s).
-
It
limits
subclassing. A
mediator localizes behavior that otherwise would be distributed among
several objects. Changing this behavior requires subclassing Mediator only;
Colleague classes can be reused as is.
-
It
decouples
colleagues. A
mediator promotes loose coupling between colleagues. You can vary and reuse
Colleague and Mediator classes independently.
-
It
simplifies object
protocols. A
mediator replaces many-to-many interactions with one-to-many interactions
between the mediator and its colleagues. One-to-many relationships are
easier to understand, maintain, and extend.
-
It
abstracts how objects
cooperate.
Making mediation an independent concept and encapsulating it in an object
lets you focus on how objects interact apart from their individual behavior.
That can help clarify how objects interact in a system.
-
It
centralizes
control. The
Mediator pattern trades complexity of interaction for complexity in the
mediator. Because a mediator encapsulates protocols, it can become more
complex than any individual colleague. This can make the mediator itself a
monolith that's hard to maintain.
-
Facade differs from
Mediator in that it abstracts a subsystem of objects to provide a more
convenient interface. Its protocol is
unidirectional;
that is, Facade objects make requests of the subsystem classes but not vice
versa. In contrast, Mediator enables cooperative behavior that colleague
objects don't or can't provide, and the protocol is
multidirectional.
-
Colleagues
can communicate with the mediator using the
Observer
pattern. Colleagues have to communicate with their mediator when an event of
interest occurs. One approach is to implement the
Mediator as an
Observer using
the Observer pattern. Colleague
classes act as
Subjects,
sending notifications to the mediator whenever they change state. The
mediator responds by propagating the effects of the change to other
colleagues
-
There's no need to define
an abstract Mediator
class when
colleagues work with only one mediator. The abstract coupling that the
Mediator class provides lets colleagues work with different Mediator
subclasses, and vice versa
FAÇADE Pattern:
-
Provide a
unified
interface to a
set of interfaces in a
subsystem.
-
defines a
higher-level
interface that
makes the subsystem easier to
use
-
Provides a
single, simplified
interface to the
more general facilities of a subsystem.
-
Clients communicate with
the subsystem by sending requests to Facade, which forwards them to the
appropriate subsystem object(s).
-
Although the subsystem
objects perform the actual work, the facade may have to do work of its own
to translate its interface to subsystem interfaces.
-
Clients that use the facade
don't have to access its subsystem objects directly.
-
Facade
·
Knows
which subsystem classes are responsible for a request.
·
Delegates
client requests to appropriate subsystem objects.
·
Implement
subsystem functionality.
·
Handle
work assigned by the Facade object.
·
Have no knowledge of the
facade; that is,
they keep no references to it.
-
The Facade pattern offers
the following benefits:
-
It shields clients from
subsystem components, thereby
reducing the number of objects that
clients deal
with and
making the subsystem easier to use.
-
It promotes weak coupling
between the subsystem and its clients. Often the components in a subsystem
are strongly coupled. Weak coupling lets you vary the components of the
subsystem without affecting its clients. Facades help layer a system and
the dependencies between objects. They can
eliminate complex or circular
dependencies.
This can be an important consequence when the client and the subsystem are
implemented independently.
-
Reducing compilation
dependencies
is vital in large software systems. You want to save time by minimizing
recompilation when subsystem classes change. Reducing compilation
dependencies with facades can limit the recompilation needed for a small
change in an important subsystem. A facade can also simplify porting
systems to other platforms, because it's less likely that building one
subsystem requires building all others.
-
It doesn't prevent applications from using
subsystem classes if they need
to. Thus you
can choose between ease of use and generality.
-
Use the Facade pattern
when
·
You want
to provide a simple interface to a
complex subsystem.
Subsystems often get more complex as they evolve. Most patterns, when applied,
result in more and smaller classes. This makes the subsystem more reusable and
easier to customize, but it also becomes harder to use for clients that don't
need to customize it. A facade can
provide a simple default view of the subsystem that is good enough for most
clients. Only clients needing more customizability will need to look beyond
the facade.
·
There
are many dependencies between clients and the implementation classes of an
abstraction. Introduce a facade to
decouple the subsystem from clients and
other subsystems,
thereby promoting subsystem independence and portability.
·
You want
to layer your
subsystems. Use a
facade to define an entry point to each
subsystem level.
If subsystems are dependent, then you can simplify the dependencies between
them by making them communicate with each other solely through their
facades.
1.
Abstract
Factory can be
used with Facade to provide an interface for creating subsystem objects in a
subsystem-independent way. Abstract Factory can also be used as an alternative
to Facade to hide platform-specific classes.
2.
Mediator
is similar to Facade in that it
abstracts functionality of existing
classes. However,
Mediator's purpose is to abstract arbitrary communication between colleague
objects, often centralizing
functionality that doesn't belong in any one of
them. A mediator's
colleagues are aware of and communicate with the mediator instead of
communicating with each other directly. In contrast,
a facade merely abstracts the interface
to subsystem
objects to make
them easier to use; it doesn't define
new functionality,
and subsystem classes don't know about
it.
3.
Usually
only one Facade object is required. Thus Facade objects are often
Singletons
.
OBSERVER Pattern:
-
Also Known As
Dependents,
Publish-Subscribe
-
Define a
one-to-many dependency
between objects
so that when one object changes state, all its dependents are notified and
updated automatically.
-
Maintain
consistency between related
objects by
making the classes loosely
coupled, because
that increases their
reusability.
-
Abstract coupling
between Subject and
Observer.
All a subject knows is that it has a list of observers, each conforming to
the simple interface of the abstract Observer class. The subject doesn't
know the concrete class of any observer. Thus the coupling between subjects
and observers is abstract and minimal. Because
Subject and Observer aren't tightly coupled, they can belong to different
layers of abstraction in a system. A lower-level subject can communicate and
inform a higher-level observer, thereby keeping the system's layering
intact
-
Support for broadcast
communication.
Unlike an ordinary request, the notification that a subject sends needn't
specify its receiver. The notification is
broadcast automatically to all
interested objects that subscribed to
it. The subject
doesn't care how many interested objects exist; its only responsibility is
to notify its observers. This gives you the freedom to add and remove
observers at any time. It's up to the observer to handle or ignore a
notification.
-
Unexpected
updates.
Because observers have no knowledge of each other's presence, they can be
blind to the ultimate cost of changing the subject. A seemingly innocuous
operation on the subject may cause a cascade of updates to observers and
their dependent objects. Moreover, dependency criteria that aren't
well-defined or maintained usually lead to spurious updates, which can be
hard to track down.
-
Use the Observer pattern in
any of the following situations:
-
When an abstraction has
two aspects, one dependent on the other. Encapsulating these aspects in
separate objects lets you vary and
reuse them
independently.
-
When a change to one
object requires changing others, and you
don't know how many objects need to
be changed.
-
When an object should be
able to notify other objects
without making assumptions about who these objects
are. In other
words, you don't want these objects tightly coupled.
PROTOTYPE Pattern:
-
Specify the kinds of objects to
create using a
prototypical instance, and create new
objects by
copying this prototype.
-
Prototype
à
declares an
interface
for cloning itself.
-
ConcretePrototype
à
implements an
operation
for cloning itself.
-
Client
à
creates a new object by
asking
a prototype to clone itself.
-
A client can
install and
remove
prototypes at
run-time.
-
Specifying new objects by varying
values. Highly
dynamic systems let you define new behavior through
object
composition—by
specifying values for an object's variables, for example—and not by defining
new classes. You effectively define new kinds of objects by instantiating
existing classes and registering the instances as prototypes of client
objects. A client can exhibit new behavior by delegating responsibility to
the prototype. This kind of design lets users define new "classes" without
programming. In fact, cloning a prototype is similar to instantiating a
class. The Prototype pattern can greatly reduce the number of classes a
system needs.
-
Specifying new objects by varying
structure. Many
applications build objects from parts and subparts. Editors for circuit
design, for example, build circuits out of subcircuits.1 For convenience,
such applications often let you instantiate complex, user-defined
structures, say, to use a specific subcircuit again and again. The Prototype
pattern supports this as well. We simply add this subcircuit as a prototype
to the palette of available circuit elements. As long as the composite
circuit object implements Clone as a deep copy, circuits with different
structures can be prototypes.
-
Reduced
subclassing.
Factory Method often produces a hierarchy of Creator classes that parallels
the product class hierarchy. The
Prototype pattern lets you clone a prototype instead of asking a factory
method to make a new
object. Hence
you don't need a Creator class
hierarchy at
all.
-
Configuring an application with classes
dynamically.
Some run-time environments let you load classes into an application
dynamically
-
Use the Prototype pattern
-
when a
system should be independent of how
its products are created, composed, and
represented;
and
-
when the classes to
instantiate are specified at
run-time,
for example, by dynamic loading; or to avoid building a class hierarchy of
factories that parallels the class hierarchy of products;
or
-
When instances of a class can have one of
only a few different combinations of
state. It may
be more convenient to install a corresponding number of prototypes and
clone them rather than instantiating the class manually, each time with
the appropriate state.
-
The
main
liability of the
Prototype pattern is that
-
Each subclass of Prototype must implement
the Clone operation, which may be
difficult. For
example, adding Clone is difficult when the classes under consideration
already exist.
-
Implementing Clone can be
difficult when their internals include
objects that don't support copying
or have circular references
-
Prototype and
Abstract
Factory are
competing patterns in some ways, as we discuss at the end of this chapter.
They can also be used together, however. An Abstract Factory might store a
set of prototypes from which to clone and return product objects.
-
Designs that make heavy use
of the Composite
and Decorator
patterns
often can benefit from Prototype as well.
Proxy
Pattern:
-
Also Known As
surrogate
-
Intent
à
Provide a surrogate or
placeholder for
another object to control
access to
it.
-
One reason for controlling
access to an object is to defer the
full cost of its creation and
initialization
until we actually need to use it.
-
Proxy is applicable
whenever there is a need for a more
versatile or sophisticated reference to an
object than a
simple pointer.
-
Remote
proxy
à
also called Ambassador
à
provides a local representative for an object in a different address
space.
-
Remote proxy can hide the
fact that an object resides in a different address space.
-
Virtual
proxy
à
creates expensive objects on demand.
-
Protection
proxy
à
controls access to the original object. Protection proxies are useful when
objects should have different access rights.
-
Smart
reference
à
is a replacement for a bare pointer that performs additional actions when an
object is accessed. Typical uses include
-
Counting the
number of
references to
the real object so that it can be freed automatically when there are no
more references (also called smart
pointers
[Ede92]).
-
Loading a persistent
object into memory when it's first
referenced.
-
Checking that the real
object is
locked
before it's accessed to ensure that no other object can change it.
-
Both
protection proxies and smart
references allow
additional housekeeping
tasks when an
object is accessed.
-
Provides an
interface identical to
Subject's so
that a proxy can by substituted for the real subject.
-
Controls access to the
real
subject and may
be responsible for creating and
deleting
it.
-
Proxy forwards requests to
RealSubject when
appropriate, depending on the kind of proxy.
-
copy-on-write
à
related to creation on demand
à
another optimization that the Proxy pattern can hide from the client.
à
Copying a large and complicated object only if
it is by using proxy.
-
Vs
Adapter
à
an adapter provides a
different
interface to the
object it adapts. In contrast, a
proxy
provides the same interface as its
subject.
However, a proxy used for access protection might refuse to perform an
operation that the subject will perform, so its interface may be effectively
a subset of the subject's.
-
Vs
Decorator
à
although decorators can have similar implementations as proxies, decorators
have a different purpose. A decorator
adds one or more responsibilities to an object, whereas a proxy controls
access to an
object.
-
Proxies vary in the degree
to which they are implemented like a decorator.
A protection proxy might be
implemented exactly like a
decorator.
-
On the other hand, a
remote proxy will not contain a
direct reference to its real
subject but only
an indirect reference, such as "host ID and local address on host."
A virtual proxy will start off with
an indirect reference such as a file name but will eventually obtain and use
a direct
reference.
ADAPTER Pattern:
·
Also
Known As Wrapper
( Decorator
is also known as Wrapper )
·
Intent
à
to convert the interface of a class
into another interface clients
expect.
·
Adapter
lets classes work together that couldn't otherwise because of
incompatible
interfaces.
·
Participants
o
Target
à
defines the domain-specific interface that Client uses.
o
Client
à
collaborates with objects conforming to the Target interface.
o
Adaptee
à
defines an existing interface that needs adapting.
o
Adapter
à
adapts the interface of Adaptee to the Target interface.
·
Clients
call operations on an Adapter instance. In turn, the adapter calls Adaptee
operations that carry out the request.
·
Use the
Adapter pattern when
o
You
want to use an existing class, and its
interface does not match the one you
need.
o
You want
to create a reusable class that
cooperates with unrelated or unforeseen
classes, that is,
classes that don't necessarily have compatible interfaces.
o
(Object
adapter only) you need to use several existing subclasses, but it's
impractical to adapt their interface by subclassing every one.
An object adapter can adapt the
interface of its parent class.
·
Vs
Bridge
has a structure similar to an object adapter, but Bridge has a different
intent: It is meant to separate an interface from its implementation so that
they can be varied easily and independently. An adapter is meant to change the
interface of an existing object.
·
Vs
Decorator
enhances another object without
changing its
interface. A
decorator is thus
more
transparent
to the application than an adapter is. As a consequence,
Decorator supports recursive
composition, which
isn't possible with pure adapters.
·
Vs
Proxy
defines a representative or surrogate for another object and
does not change its
interface
·
class adapter
o
Adapts
Adaptee to Target by committing to a
concrete Adapter
class. As a
consequence, a class adapter
won't work when we want to adapt
a class and all its
subclasses.
o
Lets
Adapter override some of Adaptee's
behavior, since
Adapter is a subclass of Adaptee.
o
Introduces only one
object and no
additional pointer indirection is needed to get to the adaptee.
·
object adapter
o
Lets a
single Adapter work with many
Adaptees that is,
the Adaptee itself and all of its subclasses (if any). The Adapter
can also add functionality to all
Adaptees at once.
o
Makes it
harder to override Adaptee
behavior. It will
require subclassing Adaptee and making Adapter refer to the subclass rather
than the Adaptee itself.
·
Pluggable
adapters
à
classes with built-in interface adaptation
à
a class is more reusable when you minimize the assumptions other classes must
make to use it.
à
Interface adaptation lets us incorporate our class into existing
systems that might expect different interfaces to the class.
·
Two-way
adapters
à
to provide transparency
à
an adapted object no longer conforms to the Adaptee interface, so it can't be
used as is wherever an Adaptee object can. Two-way adapters can provide such
transparency.
à
Specifically, they're useful when two
different clients need to view an object differently.
COMPOSITE Pattern:
-
Compose objects into
tree
structures to
represent part-whole
hierarchies.
-
Composite lets
clients treat individual objects and
compositions of objects
uniformly
using recursive
composition.
-
Defines
class
hierarchies
consisting of primitive objects and composite objects. Primitive objects can
be composed into more complex objects, which in turn can be composed, and so
on recursively. Wherever client code expects a primitive object, it can also
take a composite object.
-
Makes the client
simple. Clients
can treat composite structures and individual objects uniformly. Clients
normally don't know (and shouldn't care) whether they're dealing with a leaf
or a composite component. This simplifies client code, because it avoids
having to write tag-and-case-statement-style functions over the classes that
define the composition.
-
Makes it
easier to add new kinds of
components.
Newly defined Composite or Leaf subclasses work automatically with existing
structures and client code. Clients don't have to be changed for new
Component classes.
-
Can make your design overly
general. The
disadvantage of making it easy to add new components is that it makes it
harder to restrict the components of
a composite.
Sometimes you want a composite to have only certain components. With
Composite, you can't rely on the type system to enforce those constraints
for you. You'll have to use run-time
checks instead.
Clients use the Component
class interface to interact with objects in the composite structure. If the
recipient is a Leaf, then the request is handled directly. If the recipient is
a Composite, then it usually forwards requests to its child components,
possibly performing additional operations before and/or after
forwarding
-
Participants
-
Component
-
declares the interface
for objects in the composition.
-
implements default
behavior for the interface common to all classes, as appropriate.
-
declares an interface
for accessing and managing its child components.
-
(optional) defines an
interface for accessing a component's parent in the recursive structure,
and implements it if that's appropriate.
-
Leaf
-
represents leaf objects
in the composition. A leaf has no children.
-
defines behavior for
primitive objects in the composition.
-
Composite
-
defines behavior for
components having children.
-
stores child
components.
-
implements
child-related operations in the Component interface.
-
Client
à
manipulates objects in the composition through the Component interface.
-
Often the component-parent
link is used for a Chain of
Responsibility.
-
Decorator
is often used with Composite. When decorators and composites are used
together, they will usually have a common parent class. So decorators will
have to support the Component interface with operations like Add, Remove,
and GetChild.
-
Flyweight
lets you share components, but they can no longer refer to their
parents.
-
Iterator
can be used to traverse composites.
-
Visitor
localizes operations and behavior that would otherwise be distributed across
Composite and Leaf classes.
COMMAND Pattern:
-
Also Known As
Action,
Transaction
-
Intent
à
Encapsulate a request as an
object, thereby
letting you parameterize
clients with
different requests, queue or log requests, and
support undoable
operations.
-
To issue requests to
objects without knowing anything about the operation being requested or the
receiver of the request.
-
Consequences:
-
Command
decouples
the object that invokes the operation from the one that knows how to
perform it. i.e. decouple invoker from
receiver
-
Commands are first-class
objects. They
can be manipulated and extended like any other object.
-
You can assemble commands
into a composite
command.. In
general, composite commands are an instance of the Composite pattern.
-
It's
easy to add new
Commands,
because you don't have to change existing classes.
-
Use the Command pattern
when you want to
-
Parameterize
objects by an
action to perform.. Commands are an object-oriented replacement for
callbacks.
-
Specify, queue, and execute requests at
different
times. A
Command object can have a lifetime independent of the original request. If
the receiver of a request can be represented in an address
space-independent way, then you can transfer a command object for the
request to a different process and fulfill the request there.
-
Support
undo. The
Command's Execute operation can store state for reversing its effects in
the command itself. The Command interface must have an added Unexecute
operation that reverses the effects of a previous call to Execute.
Executed commands are stored in a history list. Unlimited-level undo and
redo is achieved by traversing this list backwards and forwards calling
Unexecute and Execute, respectively.
-
Support logging
changes so
that they can be reapplied in case of a
system
crash. By
augmenting the Command interface with load and store operations, you can
keep a persistent log of changes. Recovering from a crash involves
reloading logged commands from disk and reexecuting them with the Execute
operation.
-
Structure a system around high-level
operations built on primitives
operations.
Such a structure is common in information systems that support
transactions. A transaction encapsulates a set of changes to data. The
Command pattern offers a way to model transactions. Commands have a common
interface, letting you invoke all transactions the same way. The pattern
also makes it easy to extend the system with new transactions.
Collaborations
·
The
client creates a
ConcreteCommand
object and specifies its
receiver.
·
An
Invoker
object stores the ConcreteCommand object.
·
The
invoker issues a request by calling
Execute
on the command. When commands are undoable, ConcreteCommand stores state for
undoing the command prior to invoking Execute.
·
The
ConcreteCommand object invokes operations on its receiver to carry out the
request.
·
Command
decouples the invoker from the receiver (and the request it carries
out).
Participants:
·
Command
à
declares an interface for executing an operation.
·
ConcreteCommand
à
-
Defines a binding
between a Receiver object and an action.
-
Implements Execute by
invoking the corresponding operation(s) on Receiver.
·
Client
(Application)
à
creates a ConcreteCommand object and sets its receiver.
·
Invoker
à
asks the command to carry out the request.
·
Receiver
à
knows how to perform the operations associated with carrying out a
request. Any class may serve as a Receiver.
-
A
Composite
can be used to implement
MacroCommands.
-
A
Memento
can keep state the command requires to undo its effect.
-
A command that must be
copied before being placed on the history list acts as a
Prototype
BRIDGE
Pattern:
-
Also Known As
Handle/Body
-
Decouple an abstraction from its
implementation
so that the two can vary
independently.
-
Inheritance
binds an implementation to the abstraction permanently, which makes it
difficult to modify, extend, and reuse abstractions and implementations
independently
-
Use
the Bridge pattern when
-
You want to
avoid a permanent binding between
an abstraction and its
implementation.
This might be the case, for example, when the implementation must be
selected or switched at run-time.
-
Both the abstractions and their
implementations should be extensible by
subclassing.
In this case, the Bridge pattern lets you combine the different
abstractions and implementations and extend them independently.
-
Changes in the implementation of an
abstraction should have no impact on
clients; that
is, their code should not have to be recompiled.
-
You want to
share an implementation among
multiple
objects
(perhaps using reference counting), and this fact should be hidden from
the client.
-
Abstraction
forwards client requests to its
Implementor
object.
-
consequences:
-
Decoupling interface and
implementation.
An implementation is not bound permanently to an interface. The
implementation of an abstraction can be configured at run-time. It's even
possible for an object to change its implementation at run-time.
-
Decoupling Abstraction
and Implementor also eliminates
compile-time
dependencies
on the implementation. Changing an implementation class doesn't require
recompiling the Abstraction class and its clients. This property is
essential when you must ensure binary compatibility between different
versions of a class library.
-
Furthermore,
this decoupling encourages layering
that can lead to a better-structured
system. The
high-level part of a system only has to know about Abstraction and
Implementor.
-
Improved
extensibility.
You can extend the Abstraction and Implementor hierarchies independently.
-
Hiding implementation details from
clients. You
can shield clients from implementation details, like the
sharing of implementor
objects and
the accompanying reference
count
mechanism (if any).
-
An
Abstract
Factory can
create and configure a particular Bridge.
-
The
Adapter
pattern is geared toward making unrelated classes work together. It
is usually applied to systems after
they're
designed.
Bridge,
on the other hand, is used up-front
in a design to
let abstractions and implementations vary independently.
FLYWEIGHT Pattern:
-
Intent
à
Use
sharing
to support
large
numbers
of fine-grained
objects
efficiently.
-
A flyweight is a
shared
object that can
be used in multiple contexts
simultaneously.
-
The flyweight acts as an
independent object in each
context—it's
indistinguishable from an instance of the object that's not shared.
-
Flyweights cannot make
assumptions about the context in which they operate.
-
Intrinsic
state
à
stored in the
flyweight
à
information that's independent of the flyweight's context
à
sharable.
-
Extrinsic
state
à
depends on and varies with the
flyweight's
context
àcan't
be shared.
à
Client objects are responsible for passing extrinsic state to the flyweight
when it needs it.
-
Flyweights
model concepts or
entities that
are normally too plentiful to represent with objects.
-
The Flyweight pattern's
effectiveness depends heavily on how and where it's used. Apply the
Flyweight pattern when
all
of the following are true:
-
An application uses a
large number of
objects.
-
Storage costs are
high because
of the sheer quantity of objects.
-
Most object state can be made
extrinsic.
-
Many groups of objects may be replaced by
relatively few shared objects once extrinsic state is
removed.
-
The application
doesn't depend on object
identity.
Since flyweight objects may be shared, identity tests will return true for
conceptually distinct objects.
-
Savings = f (flyweights no.
+ amt of intrinsic state + amt of extrinsic state + extrinsic
stored/computed )
-
The more flyweights are
shared, the greater the storage savings. The savings increase with the
amount of shared state.
-
The greatest savings occur when the objects
use substantial quantities of both intrinsic and extrinsic state, and the
extrinsic state can be computed rather than
stored. Then you
save on storage in two ways: Sharing
reduces the cost of intrinsic state, and you trade extrinsic state for
computation
time.
-
Because objects are shared,
clients shouldn't instantiate them directly.
FlyweightFactory
lets clients
locate
a particular flyweight.
-
FlyweightFactory objects
often use an associative
store to let
clients look up flyweights of interest
-
Sharability also implies
some form of reference counting or
garbage
collection to
reclaim a flyweight's storage when it's no longer needed.
-
Related Patterns:
-
The Flyweight pattern is
often combined with the Composite
pattern to
implement a logically hierarchical structure in terms of a
directed-acyclic graph with shared
leaf
nodes.
-
It's often best to
implement State and Strategy
objects as
flyweights.
MEMENTO Pattern:
-
Also Known As
Token
-
Intent
à
Without violating
encapsulation,
capture and externalize an object's
internal state
so that the object can be
restored
to this state later.
-
A
memento is an object that stores a
snapshot of the internal state of another
object—the
memento's originator.
-
The undo mechanism will
request a memento from the originator when it needs to checkpoint the
originator's state. The originator initializes the memento with information
that characterizes its current state.
Only the originator can store and
retrieve information from the memento—the memento is "opaque" to other
objects.
-
Use the Memento pattern
when
-
a snapshot of (some
portion of) an object's state must be saved so that it can be restored to
that state later, and
-
a direct interface to
obtaining the state would expose implementation details and break the
object's encapsulation
Participants:
·
Memento
o
stores
internal state of the Originator object. The memento may store as much or as
little of the originator's internal state as necessary at its originator's
discretion.
o
protects
against access by objects other than the originator. Mementos have effectively
two interfaces.
Caretaker
sees a narrow
interface to the
Memento—it can only pass the memento to other objects.
Originator,
in contrast, sees a wide
interface, one
that lets it access all the data necessary to restore itself to its previous
state. Ideally, only the originator that produced the memento would be
permitted to access the memento's internal state.
·
Originator
o
creates
a memento containing a snapshot of its current internal state.
o
uses the
memento to restore its internal state.
·
Caretaker
o
is
responsible for the memento's
safekeeping.
o
never operates on or
examines the contents of a memento.
·
A
caretaker requests a memento from an originator, holds it for a time, and
passes it back to the originator, as the following interaction diagram
illustrates
·
Sometimes
the caretaker won't pass the memento back to the originator, because the
originator might never need to revert to an earlier state.
·
Mementos
are
passive.
Only the originator that created a memento will assign or retrieve its
state.
·
The
Memento pattern has several consequences:
o
Preserving encapsulation
boundaries.
Memento avoids exposing information that only an originator should manage but
that must be stored nevertheless outside the originator. The pattern shields
other objects from potentially complex Originator internals, thereby
preserving encapsulation boundaries.
o
It simplifies
Originator. In
other encapsulation-preserving designs, Originator keeps the versions of
internal state that clients have requested. That puts all the storage
management burden on Originator. Having clients manage the state they ask for
simplifies Originator and keeps clients from having to notify originators when
they're done.
o
Using mementos might be
expensive.
Mementos might incur considerable overhead if Originator must copy large
amounts of information to store in the memento or if clients create and return
mementos to the originator often enough. Unless encapsulating and restoring
Originator state is cheap, the pattern might not be appropriate.
o
Defining narrow and wide
interfaces. It may
be difficult in some languages to ensure that only the originator can access
the memento's state.
o
Hidden costs in caring
for mementos. A
caretaker is responsible for deleting the mementos it cares for. However, the
caretaker has no idea how much state is in the memento. Hence an otherwise
lightweight caretaker might incur large storage costs when it stores
mementos.
·
Related
Patterns:
o
Command
: Commands can use mementos to maintain
state for undoable
operations.
o
Iterator
: Mementos can be used for iteration
|