=== What's Inheritance Revisited === * Consider the class below: what can you do with it? template class Buffer { T buf[N]; public: void write(T e, unsigned i) { assert(i >= 0 && i < N); buf[i] = e; } T read(unsigned i) { assert(i >= 0 && i < N); return buf[i]; } void start() { std::cout << "$> "; } void stop() { std::cout << std::endl; } void eval(T& e) { std::cout << e << std::endl; } void process() { start(); for (int i = 0; i < N; i++) { eval(buf[i]); } stop(); } }; * Can you produce a main routine that uses it? int main() { const unsigned N = 10; Buffer b; for (int i = 0; i < N; i++) { b.write(1.0/(i + 1), i); } b.process(); } * Consider the method 'process()'. Can you think about other things that you could do, if the operations start, stop and eval were different? * And how can you change these operations? * Is this class closed for reuse? * Is this class below a correct specialization of Buffer? template class BufferAdder : public Buffer { T sum; public: void start() { sum = 0; } void stop() { } void eval(T& e) { sum += e; } T getSum() const { return sum; } }; * And how can you use this specialization? TEST_CASE("Summing up the buffer") { const unsigned N = 5; BufferAdder b; for (int i = 0; i < N; i++) { b.write(i * i, i); } b.process(); for (int i = 0; i < N; i++) { CHECK(b.getSum() == 30); } } * What's wrong? - We need to make the methods start, stop and eval in Buffer virtual! * Why it does not work as intended, when the methods are not virtual? - Because in the scope of process() the methods of Buffer are visible. * How can we make the methods of Buffer virtual? template class Buffer { T buf[N]; public: virtual ~Buffer() {} void write(T e, unsigned i) { assert(i >= 0 && i < N); buf[i] = e; } T read(unsigned i) { assert(i >= 0 && i < N); return buf[i]; } virtual void start() { std::cout << "$> "; } virtual void stop() { std::cout << std::endl; } virtual void eval(T& e) { std::cout << e << std::endl; } void process() { start(); for (T e: buf) { eval(e); } stop(); } }; * Can you think about other ways to specialize Buffer? * For instance, can you write an extension that increments the elements? template class BufferIncrementer : public Buffer { public: void start() { } void stop() { } void eval(T& e) { e++; } }; * And how can you check if this extension is correct? TEST_CASE("Incrementing the buffer") { const unsigned N = 5; Buffer* b = new BufferIncrementer(); for (int i = 0; i < N; i++) { b->write(1.0/(i + 1), i); } b->process(); for (int i = 0; i < N; i++) { CHECK(1.0 + 1.0/(i + 1) == b->read(i)); } delete b; } === Basic notions === * What is inheritance? * What is composition? * Can they be used for the same things? * Can you give an example of a chain of inheritance? class Movable { public: virtual ~Movable() {} virtual double getSpeed() const = 0; }; class Vehicle: public Movable { public: Vehicle(unsigned p): _num_passangers(p) {} virtual ~Vehicle() {} virtual double getSpeed() const = 0; int getNumPassangers() const { return _num_passangers; } private: unsigned _num_passangers; }; class Bus: public Vehicle { public: Bus(unsigned p): Vehicle(p) {} double getSpeed() const { return 120.0 + (32.0/getNumPassangers()); } }; class Bike: public Vehicle { public: Bike(): Vehicle(1) {} double getSpeed() const { return 50.0; } }; * Can you instantiate objects of type Movable? * What about Vehicle? * And how can you instantiate objects of type Bus or Bike? int main() { Movable *m0 = new Bus(20); Movable *m1 = new Bike(); Vehicle *v0 = new Bus(30); Vehicle *v1 = new Bike(); Bus *bus = new Bus(30); Bike *bike = new Bike(); } * And composition, Can you show an example? class Person { public: Person(double w): weight(w) {} const double weight; }; class Bike: public Vehicle { public: Bike(Person* owner): Vehicle(1), _owner(owner) {} double getSpeed() const { return 40.0 + (140.0/_owner->weight); } private: Person* _owner; }; * Can you transform the design above into an inheritance-based one? class Person { public: Person(double w): weight(w) {} const double weight; }; class Bike: public Vehicle { public: Bike(): Vehicle(1) {} virtual double getSpeed() const { return 50.0; } }; class BikeOwner: public Person, Bike { public: BikeOwner(double w): Person(w) {} double getSpeed() const { return 40.0 + (140.0/weight); } }; * What about that model with Movable<-Vehicle<-Bike, can you transform it into a composition-based model? - This is tricky, but it can be done. * Let's try first a bad design. Tell me what's the problem with it? class Movable { public: virtual ~Movable() {} virtual double getSpeed() const = 0; }; class Vehicle { public: Vehicle(unsigned p, Movable* m): _num_passangers(p), _movable_features(m) {} virtual ~Vehicle() { delete _movable_features; } virtual double getSpeed() const = 0; int getNumPassangers() const { return _num_passangers; } private: unsigned _num_passangers; Movable* _movable_features; }; class Bus { public: Bus(Vehicle* v): _vehicle_features(v) {} ~Bus() { delete _vehicle_features; } double getSpeed() const { return 120.0 + (32.0/_vehicle_features->getNumPassangers()); } int getNumPassangers() const { return _vehicle_features->getNumPassangers(); } private: Vehicle* _vehicle_features; }; class Bike { public: Bike() {} double getSpeed() const { return 50.0; } int getNumPassangers() const { return 1; } }; * Can we have a vector of Buses and Bikes, and treat it in the same way? * How can we fix it? class MovableFeatures { public: virtual ~MovableFeatures() {} virtual double getSpeed() const = 0; }; class PassangerFeatures { public: virtual ~PassangerFeatures() {} virtual unsigned getNumPassangers() const = 0; }; class Vehicle: public MovableFeatures, PassangerFeatures { public: Vehicle(MovableFeatures *f, PassangerFeatures *p): _mf(f), _pf(p) {} virtual ~Vehicle() { delete _pf; delete _mf; } virtual double getSpeed() const { return _mf->getSpeed(); } unsigned getNumPassangers() const { return _pf->getNumPassangers(); } private: MovableFeatures* _mf; PassangerFeatures* _pf; }; * And what would be concrete instantiations of PassangerFeatures? class SingleSeat: public PassangerFeatures { public: unsigned getNumPassangers() const { return 1; } }; struct FixedSeat: public PassangerFeatures { const unsigned _num_seats; public: FixedSeat(unsigned num_seats): _num_seats(num_seats) {} unsigned getNumPassangers() const { return 1; } }; * What about concrete instantiations of MovableFeatures? struct FixedSpeed: public MovableFeatures { const double _speed; public: FixedSpeed(double speed): _speed(speed) {} double getSpeed() const { return _speed; } }; struct VariableSpeed: public MovableFeatures { const double _speed; PassangerFeatures *_pf; public: VariableSpeed(double speed, PassangerFeatures *pf): _speed(speed), _pf(pf) { } double getSpeed() const { return _speed + (32.0/_pf->getNumPassangers()); } }; * And how can we build buses and bikes? Vehicle* buildBus(unsigned passangers, double speed) { PassangerFeatures *pf = new FixedSeat(passangers); MovableFeatures *mf = new VariableSpeed(speed, pf); return new Vehicle(mf, pf); } Vehicle* buildBike(double speed) { return new Vehicle(new FixedSpeed(speed), new SingleSeat()); } int main() { Vehicle* bus = buildBus(20, 120.0); Vehicle* bike = buildBike(50.0); return 0; } * We just saw the builder design pattern. Have you ever heard about it?