design patternshaase/lehre/patterns/... · use the observer pattern when ‣the abstraction has two...

44
Oliver Haase Design Patterns Observer 1

Upload: others

Post on 16-Aug-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Oliver Haase

Design PatternsObserver

1

Page 2: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Description

2

‣Object based behavioral pattern

‣ Purpose: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

‣Also Known As: Publish-Subscribe

Page 3: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Model

View 1

View 2

View 3

Observer

Subject,Observable

notifyget update

Motivating Example

3

‣ keep consistency between decoupled objects

‣ often used in the context of the MVC architectural pattern, more general, related to event listeners

‣ helps build a layered architecture

Page 4: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

update()Observer

concreteSubject.get()

attach(Observer)detach(Observer)notify()

Subject

update()ConcreteObserver

observers

get()set(s)

stateConcreteSubject concreteSubject

state = snotify()

return state

for each o in observers o.update()

General Structure

4

‣knows its observers‣provides interface for attaching and detaching observers

‣concrete observed subject‣maintains state ‣notifies observers about change of state

defines call-back operation to get notified

uses concrete subject’s get

operations to pull updated state

Page 5: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Applicability

Use the Observer pattern when

‣ the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate objects will increase the chance to reuse them independently.

‣ the subject doesn't know in advance how many observer objects it will have.

‣ the subject should be able to notify its observer objects without knowing them.

5

Page 6: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Interactions

‣ ConcreteSubject notifies its observers whenever a change to its internal state happens.

‣ After a ConcreteObserver gets notified, it may query the subject state by using the get() method. ConcreteObserver uses this information to change its own internal state.

6

Page 7: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Pros & Cons

‣ Pros:• Decoupling of subject and observer, each can be extended

and reused individually.

• Dynamic addition and removal of observers at runtime.

• Subject broadcasts notification automatically to all interested objects, no matter how many or which kind of observers are registered.

‣ Cons:• May result in many notifications the observers are not

interested in

• Potentially difficult for the observers to figure out the specific state change of the subject.

7

Page 8: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Push vs. Pull Model

‣ In its purest form, notification does not carry updated state information → Pull Model

‣ Variant: notification contains updated state → Push Model

8

Pull Model Push Model

signature of notify operation

number of operation calls

unnecessarily exchanged state

information

independent of subject’s state fields

subject to modification if subject’s state fields are changed

higher lower

fewer more

Page 9: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Observer in Java

9

Call of notifyObservers without prior call of setChanged has no effect!

update(Observable, Object)

<<interface>>Observer+ addObserver(Observer)

+ deleteObserver(Observer)+ notifyObservers()# setChanged()

Observable

update(Observable, Object)MyObserver

observers

getState()setState(s)

stateMyObservable

state = ssetChanged()notifyObservers()

Page 10: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Observer in Java‣ Observable is a class.

‣What if our observable subject already has a super-class?

‣What aspects of inheritance are needed?

10

→ implementation inheritance, and

→ interface inheritance

⇒ SmartAdapter!

‣ Can class Observable be delegated to?

→ No, because setChanged is protected.

‣ Adapter & Delegation (see Implementation Reuse patterns)

Page 11: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Observer in Java: SmartAdapter

11

update(Observable, Object)

<<interface>>Observer+ addObserver(Observer)

+ deleteObserver(Observer)+ notifyObservers()# setChanged()

Observable

update(Observable, Object)MyObserver

observers

getState()setState(s)

statedelegate

MyObservable

MySuper

state = sdelegate.setChanged()delegate.notify()

+ setChanged()+ getState()+ setState(s)

delegateSmartObservableAdapter

delegate.setState(s)

return delegate.getState()

super.setChanged()

Page 12: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Observer and ConcurrencyNaive implementation of attach(), detach(), and notify()

12

public final class NaiveSubject {private final Vector<Observer> observers;

...

public final void attach(Observer o) {observers.addElement(o);

}

public final void detach(Observer o) {observers.removeElement(o);

}

public final void notifyObservers() {for(Observer o : observers) {

o.update();}

}}

Page 13: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Observer and Concurrency

‣ Vector is thread-safe, but NaiveSubject is not.

‣ NaiveSubject will throw ConcurrentModificationException if a thread adds or removes an observer while another notifies the observers.

‣ Possible solutions: • Java Monitor Pattern (risky because of alien method call)

• Use CopyOnWriteArrayList• Copy vector before iteration (this is how JDK Observable

does it)

13

Page 14: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Observer and Concurrency

‣ Do not attach observer to subject inside observer’s constructor!

14

→ Otherwise the observer’s this reference escapes before the subject is fully constructed.

Reminder: In general, do not register a listener at an event source within listener’s constructor!

Page 15: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Relationship with other Patterns

15

MVC architectural pattern almost always uses Observer pattern.

Page 16: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Mediator

16

Page 17: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Motivation

17

‣ Complex systems often require that the participating objects know each other.

‣ This might end up in a nontransparent situation which is hard to understand.

‣ The Mediator now supervises this communication.

‣ It knows all the objects taking part and the objects are only aware of the mediator.

Page 18: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Description

18

‣Object based behavioral pattern

‣ Purpose: Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

‣Also Known As: Broker

Page 19: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Mediator Colleague

ConcreteMediator ConcreteColleague2ConcreteColleague1

mediator

General Structure

19

‣Each colleague-class knows its broker-class‣Each colleague-object works with its broker and not with the other colleague-objects

‣implements the general behavior by coordinating the colleague-objects‣knows and manages its colleague-objects

defines an interface for the interaction with

colleague-objects

Page 20: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Applicability

Use the Mediator pattern when

‣ there are many objects that have to work with each other, but the dependencies are unstructured and hard to understand.

‣ reusability of object is hard, because it has dependencies to many other objects.

‣ a behavior that's distributed between several classes should be customizable without a lot of subclassing

20

Page 21: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Pros & Cons

‣ Pro:• Limits subclassing

• Decoupled colleague-objects

• n:n-relations converted to 1:n-relations

• Abstracts how the colleague-objects work with each other

• Centralized control

‣ Con:• Broker class might turn into a hard to maintain monolith

21

Page 22: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Relationship with other Patterns

‣ Facade: Subsystem-classes don‘t know about the facade; colleague-classes know their broker.

‣ Colleague objects can notify the broker using the Observer pattern.

‣ If the Mediator alters message then it is an Adapter pattern.

22

Page 23: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Memento

23

Page 24: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Description

24

‣Object based behavioral pattern

‣ Purpose: Capture the internal state of an object without violating encapsulation and thus providing a mean for restoring the object into initial state when needed.

‣Also Known As: Token

Page 25: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Description

25

‣ Used for: Undo functionality

‣ A Memento is an object which saves a snapshot of an originator’s internal state.

‣Only the originator has access to the state of the memento object.

Page 26: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

return new Memento(state)

CareTakergetState()setState(state)

stateMemento

createMemento()setMemento(Memento m)

stateOriginator

memento

state = m.getState()

General Structure

26

‣Creates a memento object capturing the originator’s internal state.‣Use the memento object to restore its previous state.

The memento is opaque to the caretaker, and the caretaker

must not operate on it.

‣Saves the state information‣Full access only from the originator

Page 27: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Interactions

27

:Caretaker :Originator aMemento:Memento

createMemento() new Memento()

setState()

setMemento(aMemento) getState()

Page 28: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Applicability

Use the Memento pattern if

‣ an object's state must be captured so that it can be restored later on, and if

‣ explicitly passing the state of the object would violate encapsulation.

28

Page 29: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Consequences

‣ Preserves encapsulation.

‣ CareTaker simplifies originator code.

‣ Using the memento pattern can be expensive.

‣ In some languages it is hard to ensure that only the Originator can access the Memento’s state.

• How can we ensure this in Java?

29

→ Make Memento an inner class of Originator

Page 30: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Ensure encapsulation in Java

30

public final class Originator {private int state;

public Memento createMemento() {

return new Memento(state);}

public void setMemento(Memento memento) {

state = memento.getState();}

public static final class Memento {

private final int state;

private Memento(int state) {this.state = state;

}

private int getState() {return state;

}}

}

private method of static inner class can be called by outer class

Page 31: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Relationship with other Patterns

‣ Command objects can use mementos to maintain state for undoable operations.

‣ Iterator can use memento objects to save state of iteration.

31

Page 32: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Strategy

32

Page 33: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Description

33

‣Object based behavioral pattern

‣ Purpose: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independent of clients that use it.

‣Also Known As: Policy

Page 34: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Motivating Example

34

@Immutablepublic final class OriginalAIOpponent { private final Level skill;

public OriginalAIOpponent(Level skill) { this.skill = skill; } public void repelMove(int posX, int posY) { switch (skill) { case LOUSY: // do something break; case ... } } public void printCoolSlogan() { switch (skill) { case LOUSY: // print something break; case ... } }}

public enum Level { LOUSY, STRONG;}

Page 35: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Motivating Example

‣Options if we need to add a new skill level?• add case to Opponent class (easy to forget an occurrence)

• create subclass of Opponent• outsource different behavior

35

Page 36: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Motivating ExampleDefine strategy interfaces:

36

public interface RepelStrategy { void repelMove(int posX, int posY);}

public interface SloganStrategy { void printCoolSlogan();}

Sample implementation:public class LousySloganStrategy implements SloganStrategy { @Override public void printCoolSlogan() { System.out.println("Please don't hurt me!"); }}

Page 37: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Motivating Example

37

public class AIOpponent { private final RepelStrategy repelStrategy; private final SloganStrategy sloganStrategy;

public AIOpponent(RepelStrategy repelStrategy, SloganStrategy sloganStrategy) {

this.repelStrategy = repelStrategy; this.sloganStrategy = sloganStrategy; }

public void repelMove(int posX, int posY) { repelStrategy.repelMove(posX, posY); } public void printCoolSlogan() { sloganStrategy.printCoolSlogan(); }}

AIOpponent o = new AIOpponent(new LousyRepelStrategy(), new LousySloganStrategy());

Page 38: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Sample Structure

38

printCoolSlogan()

<<interface>>SloganStrategy

sloganStrategy.printCoolSlogan()

printCoolSlogan()LousySloganStrategy

ContextInterface()AIOpponent

printCoolSlogan()StrongSloganStrategy

sloganStrategy

Page 39: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

AlgorithmInterface()Strategy

strategy.AlgorithmInterface()

AlgorithmInterface()ConcreteStrategyA

ContextInterface()Context

AlgorithmInterface()ConcreteStrategyB

strategy

General Structure

39

‣gets configured with ConcreteStrategy object‣may define interface that lets Strategy access its data

implements the algorithm using the Strategy interface

interface common to all supported algorithms

Page 40: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Applicability

Use the Strategy pattern when

‣ many related classes differ only in their behavior. Strategies provide a way to configure a class with one of many behaviors.

‣ need of different variants of algorithms.

‣ an algorithm uses data that clients shouldn’t know about

‣ A class defines many behaviors (use of multiple conditional statements).

‣ Remark: be aware of switch-case-statements, they have a smell

40

Page 41: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Interactions

‣ A context forwards requests from its clients to its strategy.

‣ Data handover:• A context may hand over all data required by the algorithm

to the strategy method.

• The context can pass itself as an argument to Strategy method.

41

Page 42: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Pros & Cons‣ Pro:

• Re-usability of algorithms

• An alternative to sub-classing of the context class

• Choice of different implementations

• Elimination of conditional statements

• clean separation of different algorithms ➔ easier to test

‣ Con:• Clients must be aware of different strategies

• Communication overhead

• Increased number of objects

42

Page 44: Design Patternshaase/lehre/patterns/... · Use the Observer pattern when ‣the abstraction has two aspects where one depends on the other. Encapsulating these aspects in separate

Relationship with other Patterns

‣ Flyweight can be used to implement strategy objects.

44