CS247 Lecture 15
Last Time: Shared_ptr, Decorator pattern
This Time: Observer Pattern, Casting
Design Patterns: Effective solutions to common problems.
Observer pattern: Publisher / Subscriber relationship.
Publisher: Generates some data, change in state Subscriber: Can dynamically subscribe or unsubscribe for various publishers
Should react when data is changed.
Example: Spreadsheet application.
Publishers: Cells. (Subjects) Subscribers: Charts (Observers)
When a cell changes, charts that are observing that cell must re-render, display new information.
May have different types of publishers / subscribers, e.g. - different charts require different logic for re-rendering.
- Subject has Observers
- Observer has a reference to subject
Steps:
ConcreteSubject
has its state updatednotifyObservers
is called - either byConcreteSubject
or some controller (like themain
function)- notify is called on each observer in Subject’s observer list
- This calls
ConcreteSubject::notify
, by the assumption it is pure virtual ConcreteObserver
callsgetState
onConcreteSubject
(notify observers), uses info as necessary
Example: Twitter clone. Tweeters are subjects. Followers which are observers, can only follow one Tweeter.
class Subject {
vector<Observer*> observers;
public:
void attach(Observer* o){observers.push_back(o);}
void detach(Observer* o){//remove from observers;}
void notifyObservers() {
for(observer* o: observers) o->update();
}
virtual ~Subject() = 0; // need to provide impl. outside of the class
};
subject::~Subject(){}
class Observer {
public:
virtual void update()=0;
virtual ~Observer(){}
};
class Tweeter: public Subject {
ifstream in;
string tweet;
public:
Tweeter(const string& fileName): in{fileName}{}
bool tweet() {
getline(in tweet);
return in.good();
}
string getState(){return tweet;}
};
class Follower: public Observer {
Tweeter* iFollow;
string myName;
public:
Follower(Tweeter* t, string s):iFollow{t}, myName{s} {
iFollow->attach(this); // put in the Observers in the vector?
}
~Follower(){
iFollow->detach(this);
}
void update(){
string tweet = iFollow->getState();
if(tweet.find(myName)!= string::npos){
cout << "They said my name!"
}else {
cout << ":(" << endl;
}
}
};
int main() {
Tweeter elon{"elon.txt"};
Follow joe{&elon, "joe"};
Follower jane{&elon, "jane"};
while(elon.tweet()){
elon.notifyObservers();
}
}
Note: this only works because destruction happens in reverse order!
Also Note: Design patterns we demonstrate - simplistic implementations. Only get value at scale - multiple types of subjects/observers.
Other variants also exists - for example: passing a Subject&
via notify method. (LOOK INTO THAT)
That’s all you need to complete assignment 3. Review assignment 3 question 3, with the song library. Implemented with Observer pattern.
Casting
Casting allows us to take one type, treat as another.
In C:
Node n;
int* ip = (int*) &n;
(int*)
is a casting operator - treat this as anint*
This is dangerous., generally avoid casting unless necessary, subverts type system.
If necessary, use one of the 4 C++ casts instead of C-style casting.
static_cast
- “well-defined” conversions between two types e.g:
void g(float f);
void g(int n);
float f;
g(static_cast<int>(f));
g
callsint
version ofg
Another example:
Book* b = ...;
Text* t = static_cast<Text*>(b);
- Trust me bro, I know it’s a
Text
. - Undefined behaviour if it’s not actually pointing at a
Text
reinterpret_cast
- allows for poorly-defined, implementation dependent casts. Most uses ofreinterpret_cast
are undefined behaviour
Rational r;
Node* n = reinterpret_cast<Node*> (&r);
-
const_cast
-
dynamic_cast
Next: CS247 Lecture 16