Oliver Haase
Design Patterns
Structural Patterns
Purpose
Structural patterns describe how to compose classes (incl.
interfaces) and objects to get larger structures.
‣
Class based structural patterns use interface andimplementation inheritance to compose classes and interfaces.
‣
Object based structural patterns use aggregation, composition, and associations to compose objects, in order to gain new functionality.Adapter
Motivation
Assume, a framework defines a base class (interface) that the specific custom class has to extend (implement), in order to be used in place of the base class (interface).
The custom class might not be able to be hooked in this way, because
1.it is unmodifiable (e.g. third party software), or
2.it cannot extend the base class because it already has its own superclass.
Idea
Write an adapter that adapts the custom class' interface to the required framework interface.
Example
Situation:
How to make legacy class Toy look as if it had type Colored?
custom class framework
isRed()
<<interface>>
Colored getColor()
Toy filter(Vector): Vector
VectorUtils
for ( Colored item: in) if ( item.isRed() ) out.add(item)
?
Example
Solution 1: Object Adapter
custom class framework
isRed()
<<interface>>
Colored getColor()
Toy filter(Vector): Vector
VectorUtils
for ( Colored item: in) if ( item.isRed() ) out.add(item)
return toy.getColor() == RED
isRed() toy
ToyAdapter
Object Adapter - Code
public class ToyAdapter implements Colored { private Toy toy;
public ToyAdapter() { this.toy = new Toy();
}
@Override public boolean isRed() { return toy.getColor() == RED;
} }
Example
Solution 2: Class Adapter
custom class framework
isRed()
<<interface>>
Colored getColor()
Toy filter(Vector): Vector
VectorUtils
for ( Colored item: in) if ( item.isRed() ) out.add(item)
return getColor() == RED isRed()
ToyAdapter
Class Adapter - Code
public class ToyAdapter extends Toy implements Colored { public ToyAdapter() {
super();
}
@Override public boolean isRed() { return getColor() == RED;
} }
Object Adapter - Structure
Client
operation() Target
similarOperation() AdaptedClass
adaptee.
similarOperation() operation()
adaptee Adapter
defines interface that the client uses.
works with target interface. legacy class.
Class Adapter - Structure
Client
operation() Target
operation() Adapter
similarOperation() AdaptedClass
similarOperation()
Class vs. Object Adapter
Class Adapter
‣
single inheritance ⇒ only applicable if Target is an interface‣
only adapts AdaptedClass, not its subtypes‣ Adapter can override some of AdaptedClass's behavior
‣
preserves object unity (Adapter & AdaptedClass) Object Adapter‣
single inheritance ⇒ always applicable‣
adapts AdaptedClass and all its subtypes‣
hard to override AdaptedClass's behavior (⇒ subtypeDiscussion
‣
Adapter might draw on multiple classes to provide target interface.‣
Adapter deals with interface inheritance. For legacy code, this is completely sufficient (→ legacy code can and need not be modified to take advantage of implementation inheritance).‣
For new code that need not use implementation inheritance, the adapter pattern is sufficient, too.‣
Other scenarios will be considered in the following section.Implementation Reuse
Patterns
Motivation
Assume, a framework provides a base class that implements certain functionality that custom classes can subtype so as to inherit this functionality.
The custom class might not able to be hooked in this way, because it already has its own superclass and thus cannot
subclass the base class. However, it wants to benefit from the base class's functionality anyway.
Note: This situation does not apply to unmodifiable legacy or third party software, which cannot and need not use the base
functionality.
Situation
framework operation()
BaseService
OwnSuper
otherOperation()
CustomService2
?
...
operation() ...
...
call of operation() ...
someOperation()
CustomService1
Idea
Use composition and delegation instead of inheritance.
Solution
framework operation()
BaseService
OwnSuper
...
...
delegate.operation() ...
otherOperation() delegate
CustomService2
someOperation()
CustomService1
Case 1: BaseService is a concrete class
Solution
Case 2: BaseService is an abstract class
framework operation()
BaseService
OwnSuper
...
operation() ...
...
delegate.operation() ...
otherOperation() delegate
CustomService2 operation()
SimpleBaseService someOperation()
CustomService1
More Complex Situation
Assume, a framework provides a base class that
•
defines an interface that the specific custom class has to implement (interface inheritance), and•
implements certain functionality that custom classes thatsubtype the base class will inherit (implementation inheritance).
The custom class might not able to be hooked in this way, because it already has its own superclass and thus cannot subclass the base class.
Note: Third party software that cannot be modified can only be
Idea
Combine Adapter and Delegation patterns, i.e.
‣
use an adapter for interface inheritance and‣
delegation for implementation inheritance.Case 1: Concrete Base Class
‣
adapt ConcreteService2 to BaseService interfaceframework
operation1() operation2()
BaseService OwnSuper
operation1'()
ConcreteService2
call of operation2()
?
operation1()
ConcreteService1
operation2()
Case 1: Concrete Base Class
framework
operation1() operation2()
BaseService OwnSuper
operation1'() delegate
ConcreteService2 operation1()
delegate
Adapter
delegate.operation1'()
delegate.operation2() operation1()
ConcreteService1
operation2()
Case 2: Abstract Base Class
‣
adapt ConcreteService2 to BaseService interfaceframework
operation1() operation2()
BaseService OwnSuper
operation1'() delegate
ConcreteService2
delegate.operation2()
?
operation1()
ConcreteService1
operation2()
Case 2: Abstract Base Class
Solution: SmartAdapter combines Adapter and
framework
operation1() operation2()
BaseService OwnSuper
operation1'() delegate
ConcreteService2 operation1()
delegate
SmartAdapter
delegate.operation1'()
delegate.operation2() operation1()
ConcreteService1
operation2()
How is it Done Right?
‣
Providing a base class that needs to be extended forinterface and implementation inheritance is not the silver bullet
‣
But then, how is it done right?‣
provide•
for interface inheritance: an interface, and•
for implementation inheritance:-
an (abstract, incomplete) skeletal implementation, or-
a (concrete, complete) simple implementation of the interfaceInterface & Implementation
operation1() operation2()
<<interface>>
Service
operation1() operation2() operation3()
AbstractService
operation1() operation2()
<<interface>>
Service
operation1() operation2() operation3()
SimpleService
a) skeletal implementation b) simple implementation
Note the naming convention for skeletal implementations!
Interface & Implementation
operation1()
otherOperation() ConcreteService operation1()
operation2()
<<interface>>
Service
operation1() operation2() operation3()
AbstractService
Interface & implementation inheritance thru subclassing (using skeletal implementation, analogous for simple
implementation)
Interface & Implementation
Pure interface inheritance
operation1() operation2()
ConcreteService OwnSuper operation1()
operation2()
<<interface>>
Service
operation1() operation2() operation3()
AbstractService
Interface & Implementation
Implementation inheritance thru delegation:
1) using simple implementation
operation1() delegate
ConcreteService
delegate.operation3()
operation1() operation2()
<<interface>>
Service
operation1() operation2() operation3()
SimpleService
Interface & Implementation
Implementation inheritance thru delegation:
1) using skeletal implementation
operation2() delegate
ConcreteService operation1()
operation2()
<<interface>>
Service
operation1() operation2() operation3()
AbstractService
operation1()
SimpleService
Adapting Legacy Code, I
Class adapter:
similarOperation() LegacyCode
operation1() Adapter operation1()
operation2()
<<interface>>
Service
operation1() operation2() operation3()
AbstractService
Adapting Legacy Code, II
similarOperation() LegacyCode
operation1() delegate
Adapter
delegate.similarOperation() operation1()
operation2()
<<interface>>
Service
operation1() operation2() operation3()
AbstractService
Object adapter:
Adapting Legacy Code, III
similarOperation() LegacyCode
operation1() delegate
Adapter operation1() operation2()
<<interface>>
Service
operation1() operation2() operation3()
SimpleService
If implementation inheritance helps adapting:
Alternative Option : Reverse Delegation
Note: specialization object usually passed into ‘s
operation()
<<interface>>
Service
operation()
specialization: Service SimpleService if ( specialization != null )
specialization.operation() else
// own implementation
anotherOperation() ConcreteService1
operation()
anotherOperation() ConcreteService2
OwnSuper
Reverse Delegation - Example
Java Thread Class
run()
interface Runnable
start() run()
specialization: Runnable Thread
// create new thread
if ( specialization != null ) specialization.run()
else
this.run()
{ }
run()
MyThread1
run()
MyThread2 OwnSuper
Delegation vs. Reverse Delegation
Delegation
+ client uses instance of ConcreteService with its extended interface
- ConcreteService must implement delegation
Reverse Delegation
- client uses instance of Service with its narrow interface
+ ConcreteService does not need to implement delegation
Proxy
Description
‣
Also known as: Surrogate‣
Purpose: Control access to a subject through indirection, i.e. a placeholder object (proxy) that provides the same interface and that delegates operation calls to the actual subject.“Computer Science is the discipline that believes all problems can be solved with one more layer of indirection.” - Dennis DeBruler.
Applicability
Common reasons to hide a subject behind a proxy:
•
local placeholder for remote communication; basic idea behind Remote Procedure Call (RPC), Remote Method Invocation (RMI) → Remote Proxy•
create expensive objects only on demand → Virtual Proxy Example: high resolution graphic in text document, virtualproxy might only know size (bounding box) and position.
Applicability
Common reasons to hide a subject behind a proxy:
•
control access to original subject → Protection Proxy access control can mean to only filter out interfaces that shall not be usable, or a full-fledged authentication and authorization mechanism.•
need for an “intelligent” reference that e.g. counts number of existing references to subject (needed for automatic garbage collection) → Smart ReferenceStructure
operation() Subject Client
operation() RealSubject operation()
delegate Proxy
interface through which clients access subject
•has reference to real subject
•controls access to real subject
•might create real subject on demand
•further functionality depends on kind of proxy
The real thing
Implementation
public interface ServiceUsage { void use();
}
Example: A service interface that contains methods for usage, for administration, and methods to get a usage or
administration proxy:
public interface ServiceAdmin {
void administrate(int state);
}
public interface Service extends ServiceAdmin, ServiceUsage { ServiceAdmin getAdminProxy();
ServiceUsage getUsageProxy();
}
Implementation
public class ConcreteService implements Service { private int state;
private class ConcreteAdminProxy implements ServiceAdmin { @Override public void administrate(int state) {
ConcreteService.this.administrate(state);
} }
private class ConcreteUsageProxy implements ServiceUsage { @Override public void use() {
ConcreteService.this.use();
} }
Simple Service implementation ...
Implementation
@Override public ServiceAdmin getAdminProxy() { return new ConcreteAdminProxy();
}
@Override public ServiceUsage getUsageProxy() { return new ConcreteUsageProxy();
}
@Override public void administrate(int state) { this.state = state;
}
@Override public void use() {
System.out.println("state: " + state);
} }
... continued:
Implementation
public class Application {
private static class AdminThread extends Thread { private final ServiceAdmin serviceAdmin;
public AdminThread(ServiceAdmin serviceAdmin) { this.serviceAdmin = serviceAdmin;
}
public void run() {
for ( int value = 0; true; value++ ) { serviceAdmin.administrate(value);
try {
TimeUnit.SECONDS.sleep(5);
Sample application that creates a ConcreteService, then
feeds an administration thread with an AdministrationProxy
and a usage thread with a UsageProxy.
Implementation
private static class UsageThread extends Thread { private final ServiceUsage serviceUsage;
public UsageThread(ServiceUsage serviceUsage) { this.serviceUsage = serviceUsage;
}
public void run() { while ( true ) {
serviceUsage.use();
try {
TimeUnit.SECONDS.sleep(2);
}
catch ( Exception e) {}
} }
... continued ...
Implementation
public static void main(String[] args) {
ConcreteService service = new ConcreteService();
ServiceAdmin sa = service.getAdminProxy();
ServiceUsage su = service.getUsageProxy();
new AdminThread(sa).start();
new UsageThread(su).start();
} }
... continued:
Implementation
public class ConcreteService implements Service { ...
@Override public ServiceAdmin getAdminProxy() { return (ServiceAdmin) Proxy.newProxyInstance(
ServiceAdmin.class.getClassLoader(), new Class[] {ServiceAdmin.class}, new DelegationHandler(this));
}
@Override public ServiceUsage getUsageProxy() { return (ServiceUsage) Proxy.newProxyInstance(
ServiceUsage.class.getClassLoader(), new Class[] {ServiceUsage.class}, new DelegationHandler(this));
}
Variant with dynamic proxies: Java allows to create dynamic
proxies that implement a set of interfaces specified at run-time.
Implementation
public class DelegationHandler implements InvocationHandler { private Service delegate;
public DelegationHandler(Service delegate) { this.delegate = delegate;
}
@Override public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable { return method.invoke(delegate, args);
} }
Class DelegationHandler forwards all incoming calls to delegate object:
Implementation
Java ≥ 5 uses dynamic proxies for Java RMI:
When an RMI server object is exported, the runtime system (Java virtual machine) creates a dynamic proxy (RMI stub) that implements the respective remote interface, and forwards all methods calls to the remote server object.
This obsoletes the necessity to create stub classes (rmic) beforehand.
Bridge
Purpose
Decouple abstraction (interfaces) and implementation, such that both can evolve independently of each other.
“A computer once beat me at chess, but it was no match to me at kick-boxing” - Emo Philips
Motivation
Scenario:
‣
Provide more comfortable RemoteOpComm interfacesend(op, params): void receive(): Result
Communication
send(op, params): void receive(): Result
TcpCommunication
send(op, params): void receive(): Result
UdpCommunication
Motivation
send(op, params): void receive(): Result
Communication
send(op, params): void receive(): Result
TcpCommunication
send(op, params): void receive(): Result
UdpCommunication
invoke(op, params): Result RemoteOpComm
send(op, params) return receive()
Motivation
send(op, params): void receive(): Result
Communication
send(op, params): void receive(): Result
TcpCommunication
send(op, params): void receive(): Result
UdpCommunication
invoke(op, params): Result RemoteOpComm
send(op, params) return receive()
send(op, params): void receive(): Result
TcpRemOpComm
send(op, params): void receive(): Result
UdpRemOpComm
Solution using Bridge Pattern
Bridge
send(op, params): void receive(): Result
TcpCommunication
send(op, params): void receive(): Result
UdpCommunication invoke(op, params): Result
RemoteOpComm
send(op, params) return receive()
send(op, params): void receive(): Result
CommunicationImpl send(op, params): void
receive(): Result impl
Communication Client
impl.send(op, params) return impl.receive()
General Structure
Bridge
operation()
ConcreteImplementorA
operation()
ConcreteImplementorB specialOperation()
Specialization
operation()
Implementor operation()
impl
Abstraction Client
impl.operation()
•defines interface of the abstraction
•maintains reference to implementor instance
defines interface for implementation classes (can differ from Abstraction)
Implications
‣
the abstractions (left hand side) use only the functionality provided by class Implementor. Additional functionality in the subclasses cannot be taken advantage of;‣
additional functionality in sub-interfaces must be achieved only through usage of the general functionality as contained in the root interface Abstraction.‣
Bridge leads to loose coupling of abstraction and implementation→ Implementor object can be replaced at run-time
Implementation
Who decides when and how, what particular Implementor
object to use?
‣
Option 1: Specialization object, based on the params passed in at construction time.Example: Collection object that gets initialized with small initial size uses LinearList implementation. Large
Collection object uses BTree implementation. (Note:
Implementation can be dynamically replaced as the collection grows.)
Implementation
‣
Option 1I: Others decide → creational patterns!• Specialization object gets fed with factory or prototype
•
Dependency injection → Specialization object gets passed inImplementor object by Client.