CS 246: Software Abstraction and Specification Lecture 17 Design Patterns (Composite, Iterator) Reading: Head First Design Patterns U Waterloo CS246se (Spring 2011) p.1/30
Today's Agenda Design patterns: codified solutions that put design principles into practice, to improve the modularity of our code. OO Basics Favour Composition over Inheritance Model-View-Controller (MVC) Separation of Concerns Single Responsibility Principle Composite Loose Coupling Principle of Least Knowledge (Law Iterator of Demeter) Information Hiding Avoid duplicate code Encapsulate what is likely to change OO Principles Strategy Program to an Interface, not an Factory Implementation Method Liskov Substitutability PrincipleObserver Design Patterns Template Method U Waterloo CS246se (Spring 2011) p.2/30
Review: Object Composition A compound object represents a composition of heterogeneous, possibly recursive, component objects Law of Demeter: client code interacts with compound object Document 1 * 0..1 Section * Transcript 1 * Term Record 1 1 * Page 1..* Course Enrollment * Player * 0..1 Team * subteam 1 Course number description credits U Waterloo CS246se (Spring 2011) p.3/30
Composite Pattern Idea The Composite Pattern takes a different approach: gives the client access to all members in a compound object via a uniform interface. Client Code TeamMember * Player Team Client Code 2 Expr value() print() BinaryExpr Variable name value Plus Minus Multiply Divide U Waterloo CS246se (Spring 2011) p.4/30
Composite Pattern Problem: composite object consists of several heterogenous parts Client code is complicated by knowledge of object structure Client must change if data structure changes Solution: create a uniform interface for the object's components Interface advertises all operations that components offer Client deals only with the new uniform interface Client Code Component Operation add(component) Remove(Component) GetChild(int) * part Leaf Operation Composite Operation add(component) Remove(Component) GetChild(int) U Waterloo CS246se (Spring 2011) p.5/30
Example: Expressions 2 BinaryExpr left() : Expr right() : Expr print() Expression value() print() Variable name value name() value() print() Expressions: a+b a*b+c-d a Plus Minus Multiply Divide U Waterloo CS246se (Spring 2011) p.6/30
Implementing the Component Interface Because the interface of the Component Class is to be uniform, it comprises the union of all component classes. class Expression { public: // leaf-only operations virtual string name() const; // composite-only operations virtual const Expression* left() const; virtual const Expression* right() const; //shared operations virtual int value() const; virtual void print() const; private: Expression(); ; U Waterloo CS246se (Spring 2011) p.7/30
Uniformity vs. Safety Whether to include component-specific operations in the component interface involves a trade-off between uniformity - preserving the illusion that component objects can be treated the same way - promoted by the Composite Pattern safety - avoiding cases where the client attempts to do something meaningless, like adding components to Leaf objects - promoted by Liskov Substitutability Principle U Waterloo CS246se (Spring 2011) p.8/30
Implementing the Component Interface What default implementation makes sense? class Expression { public: // leaf-only operations virtual string name() const {return ""; // composite-only operations virtual const Expression* left() const {return 0; virtual const Expression* right() const {return 0; //shared operations virtual int value() const {return 0; virtual void print() const { private: Expression(); ; U Waterloo CS246se (Spring 2011) p.9/30
Implementing the Leaf Class The leaf classes override the behaviour of leaf-object operations. class Variable : public Expression { public: Variable (std::string, int); // redefine leaf-only operations virtual std::string name() const {return name_; // inherit default behaviour of composite-only operations // redefine shared operations virtual int value() const {return value_; virtual void print() const {std::cout << name(); private: std::string name_; int value_; ; U Waterloo CS246se (Spring 2011) p.10/30
Implementing the Composite The composite classes override composite-object operations. class BinaryExpr : public Expression { public: ~BinaryExpr(); // inherit default behaviour of leaf-only operations // redefine composite operations virtual Expression* left() const {return leftchild_; virtual Expression* right() const {return rightchild_; private: Expression* leftchild_; Expression* rightchild_; protected: BinaryExpr(Expression* a, Expression *b); ; U Waterloo CS246se (Spring 2011) p.11/30
Implementing the Composite The (concrete) composite classes specialize the composite methods that are operator specific. class Plus : public BinaryExpr { public: Plus(Expression*, Expression*); // redefine shared operations int value() const; void print() const; ; int Plus::value() const { return left()->value() + right()->value(); void Plus::print() const { left()->print(); std::cout << " + "; right()->print(); U Waterloo CS246se (Spring 2011) p.12/30
Another Example 0..1 Developer * Project Team * subteam TeamMember name : string name() : string salary() print() add(teammember) remove(teammember) getmember(int) * member Developer salary: float salary() print() ProjectTeam print() add(teammember) remove(teammember) getmember(int) U Waterloo CS246se (Spring 2011) p.13/30
Component Interface class TeamMember { public: // leaf-only operations virtual int salary() const; // component-only operations virtual void add(teammember*); virtual void remove(teammember*); virtual TeamMember* getmember(int) const; // shared operations std::string name() const; virtual void print() const; protected: TeamMember( std::string name ); private: std::string name_; ; U Waterloo CS246se (Spring 2011) p.14/30
Composite Pattern 2 Expression value() print() Client BinaryExpr left() : Expr right() : Expr print() Variable name value name() value() print() Plus Minus Multiply Divide Consequences: + Client deals only with the new uniform interface + New leafs and composite types are easy to add New operations are harder to add (Visitor Pattern) How can client code iterate through a composite object without knowing the composite's structure? U Waterloo CS246se (Spring 2011) p.15/30
Iterator Pattern Goals: (1) To encapsulate the strategy for iterating through a composite (so that it can be changed, at run-time). (2) Allow the client to iterate through a composite without exposing the composite's representation. Collection createiterator() size() getelem(int) Client Iterator first() hasnext() next() ConcreteCollection createiterator() size() getelem(int) <<create>> ConcreteIterator first() hasnext() next() U Waterloo CS246se (Spring 2011) p.16/30
Collection createiterator() size() getelem(int) Department createiterator() size() getelem(int) Operation() * Prof Simple Iteration <<create>> Operation to retrieve specific element Client Iterator first() hasnext() next() DepartmentIterator first() hasnext() next() Operations to retrieve all elements, systematically // client code Department* dept; DepartmentIterator* iter = dept->createiterator(); iter->first(); while ( iter->hasnext() ) { Prof* p = iter->next(); // process p U Waterloo CS246se (Spring 2011) p.17/30
Department Composite class is augmented with operations to support the Iterator Pattern. class Department { public: virtual int size(); virtual Prof* getelem(int); virtual Iterator* createiterator(); // other operations private: std::vector<prof*> dept_; ; Iterator* Department::createIterator() { return new DepartmentIterator(this); U Waterloo CS246se (Spring 2011) p.18/30
Department Iterator class DepartmentIterator { // retrieves elements in vector order private: Department* dept_; int cursor_; public: DepartmentIterator(Department* dept) : dept_(dept), cursor_(0) { virtual void first() { cursor_ = 0; virtual bool hasnext(); virtual Prof* next(); ; bool DepartmentIterator::hasNext() { return (cursor_ < dept_->size()); Prof* DepartmentIterator::next() { Prof* result = (dept_->getelem(cursor_)); cursor_ += 1; return result; U Waterloo CS246se (Spring 2011) p.19/30
Iteration over a Composite Object The more interesting case is when the aggregate is a composite object, in which case we need to construct an Iterator that understands and navigates the composite. Company Division A Division B Tom Tiger Team Special Ops Team Alice Ajit Sandy Ali U Waterloo CS246se (Spring 2011) p.20/30
Composite Iteration collection TeamMember name() salary() add (TeamMember) remove (TeamMember) createiterator() size() getchild(int) * Client Iterator first() hasnext() next() iterator Developer salary() createiterator() Team add (TeamMember) remove (TeamMember) createiterator() size() getchild(int) <<create>> <<create>> TeamIterator TeamIterator(Team) first() hasnext() next() DevIterator DevIterator(Developer) first() hasnext() next() U Waterloo CS246se (Spring 2011) p.21/30
Client Code Iterate through all members in the composite. TeamMember* employees;... Iterator* iter = employees->createiterator(); iter->first(); while ( iter->hasnext() ) { TeamMember* m = iter->next(); // process member m Different iterator can iterate through all leaf objects in the composite. TeamMember* employees;... Iterator* iter = employees->createdevonlyiterator(); iter->first(); while ( iter->hasnext() ) { TeamMember* m = iter->next(); RequestPayCheck(m->name(), m->salary()); U Waterloo CS246se (Spring 2011) p.22/30
Create Iterator Each concrete subclass in the composite knows how to create its own corresponding Iterator. Iterator* Developer::createIterator() { return new DevIterator(this); Iterator* Team::createIterator() { return new TeamIterator(this); U Waterloo CS246se (Spring 2011) p.23/30
Developer Iterator class DevIterator : public Iterator { private: Developer* dev_; Developer* cursor_; public: DevIterator(Developer* dev) : dev_(dev), cursor_(dev) { virtual void first() { cursor_ = dev_; virtual bool hasnext() { return (cursor_!= NULL); virtual TeamMember* next(); ; TeamMember* DevIterator::next() { if ( hasnext() ) { cursor = NULL; return dev_; return NULL; U Waterloo CS246se (Spring 2011) p.24/30
Team Behaviour The Composite objects contribute to iteration with operations to retrieve specific elements. class Team : public TeamMember { private: std::vector<teammember*> members_;... public:... virtual int size() { return members_->size(); virtual TeamMember* getchild(int i) { return members_->at(i); ; U Waterloo CS246se (Spring 2011) p.25/30
Team Iterator The composite iterator needs to iterate over the composite object, including several subobjects. We achieve this by keeping an iterator (cursor) for each collection in composite putting iterators on stack as the collections are encountered class TeamIterator : public Iterator { private: TeamMember* members_; // pointer to composite struct Iter; // < collection, cursor> std::stack<iter*> istack; // stack of iterators public: TeamIterator(TeamMember* m) : members_(m) { first(); virtual void first(); // initialize Iterator stack virtual bool hasnext(); virtual TeamMember* next(); ; U Waterloo CS246se (Spring 2011) p.26/30
TeamIterator::first() Initialize the iterator stack with a cursor for the whole composite. struct TeamIterator::Iter { TeamMember *collect_; int cursor_; // ranges from -1.. collect_->size() Iter(TeamMember *m) : collect_(m), cursor_(-1) { ; void TeamIterator::first() { while (!istack.empty() ) { istack.pop(); istack.push(new Iter(members_)); U Waterloo CS246se (Spring 2011) p.27/30
TeamIterator::next() TeamMember* TeamIterator::next() { // preorder iteration if ( hasnext() ) { // have cursors reached their limit? Iter* top = istack.top(); istack.pop(); // cursor points to member (could be Developer or Team) if (top->cursor_==-1) { top->cursor_ += 1; istack.push(top); // advance cursor to first element return top->collect_;// return member // cursor points to elem within collection member TeamMember *elem = top->collect_->getchild(top->cursor_); top->cursor_ += 1; istack.push(top); // advance cursor to next element istack.push(new Iter(elem)); // push new element on stack return next(); // recurse else return 0; U Waterloo CS246se (Spring 2011) p.28/30
TeamIterator::hasNext() Check if stack contains an iterator that has not retrieved all elements of its respective collection bool TeamIterator::hasNext() { while (!istack.empty() ) { Iter *top = istack.top(); if (top->collect_->size() > top->cursor_) { return true; istack.pop(); delete top; return false; U Waterloo CS246se (Spring 2011) p.29/30
Summary The goal of design patterns is to encapsulate change Composite Pattern: encapsulates the structure of a heterogeneous, possibly recursive data structure Iterator Pattern: encapsulates the iteration of a heterogeneous, possibly recursive data structure U Waterloo CS246se (Spring 2011) p.30/30