• Keine Ergebnisse gefunden

Design Patterns

N/A
N/A
Protected

Academic year: 2022

Aktie "Design Patterns"

Copied!
62
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Oliver Haase

Design Patterns

Structural Patterns

(2)

Purpose

Structural patterns describe how to compose classes (incl.

interfaces) and objects to get larger structures.

Class based structural patterns use interface and

implementation inheritance to compose classes and interfaces.

Object based structural patterns use aggregation, composition, and associations to compose objects, in order to gain new functionality.

(3)

Adapter

(4)

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.

(5)

Idea

Write an adapter that adapts the custom class' interface to the required framework interface.

(6)

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)

?

(7)

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

(8)

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;

} }

(9)

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

(10)

Class Adapter - Code

public class ToyAdapter extends Toy implements Colored { public ToyAdapter() {

super();

}

@Override public boolean isRed() { return getColor() == RED;

} }

(11)

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.

(12)

Class Adapter - Structure

Client

operation() Target

operation() Adapter

similarOperation() AdaptedClass

similarOperation()

(13)

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 (⇒ subtype

(14)

Discussion

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.

(15)

Implementation Reuse

Patterns

(16)

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.

(17)

Situation

framework operation()

BaseService

OwnSuper

otherOperation()

CustomService2

?

...

operation() ...

...

call of operation() ...

someOperation()

CustomService1

(18)

Idea

Use composition and delegation instead of inheritance.

(19)

Solution

framework operation()

BaseService

OwnSuper

...

...

delegate.operation() ...

otherOperation() delegate

CustomService2

someOperation()

CustomService1

Case 1: BaseService is a concrete class

(20)

Solution

Case 2: BaseService is an abstract class

framework operation()

BaseService

OwnSuper

...

operation() ...

...

delegate.operation() ...

otherOperation() delegate

CustomService2 operation()

SimpleBaseService someOperation()

CustomService1

(21)

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 that

subtype 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

(22)

Idea

Combine Adapter and Delegation patterns, i.e.

use an adapter for interface inheritance and

delegation for implementation inheritance.

(23)

Case 1: Concrete Base Class

adapt ConcreteService2 to BaseService interface

framework

operation1() operation2()

BaseService OwnSuper

operation1'()

ConcreteService2

call of operation2()

?

operation1()

ConcreteService1

operation2()

(24)

Case 1: Concrete Base Class

framework

operation1() operation2()

BaseService OwnSuper

operation1'() delegate

ConcreteService2 operation1()

delegate

Adapter

delegate.operation1'()

delegate.operation2() operation1()

ConcreteService1

operation2()

(25)

Case 2: Abstract Base Class

adapt ConcreteService2 to BaseService interface

framework

operation1() operation2()

BaseService OwnSuper

operation1'() delegate

ConcreteService2

delegate.operation2()

?

operation1()

ConcreteService1

operation2()

(26)

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()

(27)

How is it Done Right?

Providing a base class that needs to be extended for

interface 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 interface

(28)

Interface & 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!

(29)

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)

(30)

Interface & Implementation

Pure interface inheritance

operation1() operation2()

ConcreteService OwnSuper operation1()

operation2()

<<interface>>

Service

operation1() operation2() operation3()

AbstractService

(31)

Interface & Implementation

Implementation inheritance thru delegation:

1) using simple implementation

operation1() delegate

ConcreteService

delegate.operation3()

operation1() operation2()

<<interface>>

Service

operation1() operation2() operation3()

SimpleService

(32)

Interface & Implementation

Implementation inheritance thru delegation:

1) using skeletal implementation

operation2() delegate

ConcreteService operation1()

operation2()

<<interface>>

Service

operation1() operation2() operation3()

AbstractService

operation1()

SimpleService

(33)

Adapting Legacy Code, I

Class adapter:

similarOperation() LegacyCode

operation1() Adapter operation1()

operation2()

<<interface>>

Service

operation1() operation2() operation3()

AbstractService

(34)

Adapting Legacy Code, II

similarOperation() LegacyCode

operation1() delegate

Adapter

delegate.similarOperation() operation1()

operation2()

<<interface>>

Service

operation1() operation2() operation3()

AbstractService

Object adapter:

(35)

Adapting Legacy Code, III

similarOperation() LegacyCode

operation1() delegate

Adapter operation1() operation2()

<<interface>>

Service

operation1() operation2() operation3()

SimpleService

If implementation inheritance helps adapting:

(36)

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

(37)

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

(38)

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

(39)

Proxy

(40)

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.

(41)

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, virtual

proxy might only know size (bounding box) and position.

(42)

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 Reference

(43)

Structure

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

(44)

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();

}

(45)

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 ...

(46)

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:

(47)

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.

(48)

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 ...

(49)

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:

(50)

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.

(51)

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:

(52)

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.

(53)

Bridge

(54)

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

(55)

Motivation

Scenario:

Provide more comfortable RemoteOpComm interface

send(op, params): void receive(): Result

Communication

send(op, params): void receive(): Result

TcpCommunication

send(op, params): void receive(): Result

UdpCommunication

(56)

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()

(57)

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

(58)

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()

(59)

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)

(60)

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

(61)

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.)

(62)

Implementation

Option 1I: Others decide → creational patterns!

Specialization object gets fed with factory or prototype

Dependency injection Specialization object gets passed in

Implementor object by Client.

Referenzen

ÄHNLICHE DOKUMENTE

Class Design Principles: Single Responsibility Principle (SRP) - Introduction to SRP by Example.. 2.2.3 The Employee Example.. „  Consider the class Employee which has

Eine Analyse sozialer Klassen und Milieus kann nicht für sich in Anspruch nehmen, Klassen &#34;an sich&#34; zu erfassen, sondern sie muss mit einer Analyse der

diverges from typical qualitative approaches to migration; whilst it may overlook cultural specificities, it is able to engage in theory building and enhance our

Another finding regarding the Indian new middle class from this manuscript is that despite the class-distinctive aspects of their food practices, they also try to overcome

Just to make sure we still use the same definition of household: By household I mean all the people who permanently live in this house and eat from the same kitchen as you do... 3

Especially with re- gards to the stability of the people’s position in the middle of society, empirical social science studies challenge the middle class narrative and

The Circular Dichroism (CD) spectrum of -lactamase from Escherichia coli (TEM-1) has been calculated with the matrix method on the basis of the x-ray diffraction structure.. All

• Overlapping between the two wings of the MC (neo new and classic) renders distinguishing between them practically impossible. For it is well-known that many public and