11 EECS 280 Subtypes and Subclasses


EECS 280 Programming and Introductory Data Structures Subtypes and Subclasses Slides by Andrew DeOrio 1 Flatland and Triangles Recall Flatland, where the characters are geometric shapes Characters with similar social status have similar shapes – remember, Flatland is a satire Soldiers are isosceles triangles with sharp points Craftsmen are equilateral triangles Recall that we used the C++ class mechanism to represent a triangle, creating a new type class Triangle {/*...*/}; 2craftsman soldier Recap: Triangle ADT class Triangle { private: double a,b,c; //edge lengths represent a triangle double acute; //measure of smallest angle public: //EFFECTS: creates a zero size Triangle Triangle(); //REQUIRES: a,b,c are non-negative and form a triangle //EFFECTS: creates a Triangle with given edge lengths Triangle(double a_in, double b_in, double c_in); //... 3 Recap: Triangle ADT class Triangle { //... //EFFECTS: returns the area double area() const; //EFFECTS: prints edge lengths void print() const; //EFFECTS: returns smallest angle, in radians double get_acute(); //... 4 Recap: Triangle ADT class Triangle { //... //EFFECTS: sets edge lengths void set_a(double a_in); void set_b(double b_in); void set_c(double c_in); //EFFECTS: returns edge length double get_a() const; double get_b() const; double get_c() const; }; 5 Recap: Triangle ADT Recall our program to count the craftsmen in Flatland 6 Triangle census[SIZE]; //fill with Flatland’s inhabitants int num_craftsmen = 0; for (Triangle *t=census; tget_acute() > 1.0384709) num_craftsmen += 1; cout << num_craftsmen << endl; Flatland.cpp Flatland and Triangles In Flatland, soldiers and craftsmen have something in common: they are both workers, represented by triangles. We can represent this kind of relationship between two C++ classes with a derived class Other terms for a derived class (type) are inherited class or subclass Let’s create derived classes for an isosceles  triangle and an equilateral triangle 7craftsman soldier Derived classes class Isosceles : public Triangle { //OVERVIEW: a geometric representation of an //isosceles triangle with edge a representing //the base, and b=c the legs //... }; This creates a new type called Isosceles that contains all of the Triangle member functions and member data Think of this as an “is a” relationship an Isosceles is a Triangle 8 Class hierarchy Derivation is often represented by a graph, where each vertex is a class, and each edge shows derivation 9 Triangle Isosceles base class AKA parent class AKA superclass derived class AKA child class AKA inherited class AKA subclass derives from AKA inherits from Using derived classes Now we can define an Isosceles object int main() { Isosceles soldier; } Because member variables are inherited, the compiler allocates memory for each one 10 a b c Isosceles acute class Triangle { private: double a,b,c; double acute; //... Using derived classes We call member functions just like we did with the base class Because member functions are inherited, we do not need to rewrite them (copy paste avoided!) int main() { Isosceles soldier; soldier.set_a(1); soldier.set_b(11); soldier.set_c(11); soldier.print(); cout << "area=" << soldier.area() << endl; } 11 $ ./a.out a=1 b=11 c=11 area=5.49432 Adding member variables In addition to the inherited member variables, we can extra member variables Let's add extra member variables to Isosceles to store the base and leg edge lengths class Isosceles : public Triangle { private: double base, leg; //new member variables }; 12 Adding member variables Now, we get memory for the member variables inherited from Triangle, plus the two added member variables int main() { Isosceles i; } 13 inherited from Triangle added by Isosceles class Isosceles : public Triangle { private: double base, leg; }; class Triangle { private: double a,b,c; double acute; }; a b c Isosceles acute base leg Adding member functions In our example, this seems wasteful, since we already have a, b and c to store the edge lengths Instead, let's add member functions to change the base and legs class Isosceles : public Triangle { private: double base, leg; public: //EFFECTS: sets base (edge a) void set_base(double base); //EFFECTS: sets legs (edges b and c) void set_leg(double leg); }; 14 Exercise: implement these two functions Adding member functions Solution 1: set the member variables directly class Isosceles : public Triangle { public: //EFFECTS: sets base (edge a) void set_base(double base) { a = base; } //EFFECTS: sets legs (edges b and c) void set_leg(double leg) { b = c = leg; } }; 15 Adding member functions Problem: a, b and c are private members of Triangle, and derived classes cannot access private member variables of a base class class Isosceles : public Triangle { public: //EFFECTS: sets base (edge a) void set_base(double base) { a = base; //compile error } //EFFECTS: sets legs (edges b and c) void set_leg(double leg) { b = c = leg; //compile error } }; 16 class Triangle { //... private: double a,b,c; }; Adding member functions Solution 2: use set_*() functions inherited from Triangle class Isosceles : public Triangle { public: //EFFECTS: sets base (edge a) void set_base(double base) { set_a(base); } //EFFECTS: sets legs (edges b and c) void set_leg(double leg) { set_b(leg); set_c(leg); } }; 17 Adding member functions Now, we can call our new Isosceles member functions, in addition to the inherited member functions int main() { Isosceles soldier; solider.set_base(1); //additional member function soldier.set_leg(11); //additional member function soldier.print(); //inherited member function } 18 $ ./a.out a=1 b=11 c=11 Derived class constructors Constructors are not inherited, so let’s add one class Isosceles : public Triangle { //... //EFFECTS: creates a zero size Isosceles triangle Isosceles(); }; 19 Exercise: derived class ctors What is wrong with these implementations? Hint: think like a compiler, think about efficiency 20 Isosceles() { a = b = c = 0; acute = -1; } Isosceles() { set_a(0); set_b(0); set_c(0); } Isosceles() { Triangle(); } Derived class constructors class Isosceles : public Triangle { //... //EFFECTS: creates a zero size Isosceles triangle Isosceles() {} }; Solution: do nothing! Constructors run automatically, starting with the base class 22 Derived class constructors int main() { Isosceles i; } First, Triangle constructor runs Second, Isosceles constructor runs In the end, we get an initialized chunk of memory like this: 23 a b c 0 0 0 Isosceles acute -1 Triangle() : a(0), b(0), c(0), acute(-1.0) {} Isosceles() {} Derived class constructors Next, let's add a custom constructor to set the base and legs class Isosceles : public Triangle { //... //REQUIRES: base and leg are non-negative and // form an isosceles triangle //EFFECTS: creates an Isosceles triangle with // given edge lengths Isosceles(double base, double leg); }; 24 Derived class constructors Next, let's add a custom constructor to set the base and legs class Isosceles : public Triangle { //... //REQUIRES: base and leg are non-negative and // form an isosceles triangle //EFFECTS: creates an Isosceles triangle with // given edge lengths Isosceles(double base, double leg) : Triangle(base, leg, leg) {} }; Solution: reuse constructor from base class Initializer lists are the only way to call a base class constructor from a derived class constructor 25 Constructors: common pitfall class Isosceles : public Triangle { //... Isosceles(double base, double leg) { Triangle(base, leg, leg);//bad } }; Pitfall: calling the base class constructor inside the derived class constructor, but outside the initializer list This creates a new, anonymous Triangle object, which is a local variable without a name inside the Isosceles constructor Usually not what you intended! 26 Exercise: constructors int main { Isosceles worker(0.9, 8); } Which constructors run? Specify the exact constructors, arguments, and order 27 Member functions from base class What is wrong with this code? Isosceles soldier(1,11); soldier.print(); soldier.set_b(11.1); soldier.print(); 29 Member functions from base class What is wrong with this code? Isosceles soldier(1,11); soldier.print(); soldier.set_b(11.1); soldier.print(); Problem: soldier is no longer an isosceles triangle! 30 1 1111.1 Representation invariant The Isosceles representation invariant has been broken The representation invariant constrains the member variables You can think of the representation invariant as a sanity-check for the class Triangle invariant: a, b, and c form a triangle Long edge is less than the sum of both short edges Isosceles invariant: base and 2 leg edges form an isosceles triangle Base is less than sum of two legs Legs are equal 31 Member functions from base class What is wrong with this code? Isosceles soldier(1,11); soldier.print(); soldier.set_b(11.1); soldier.print(); Solution: change set_b() implementation only in Isosceles derived class This is called a function override 32 1 1111.1 Override vs. Overload A function override is where a derived class has a function with the same name and prototype as the parent Triangle::set_b(double b_in); Isosceles::set_b(double b_in); A function overload is where a single class has two different functions with the same name, but different prototypes Triangle::Triangle(); Triangle::Triangle(double a_in, double b_in, double c_in); 33 Overriding member functions Override set_b() and set_c() to set both legs of the triangle, maintaining the Isosceles representation invariant class Isosceles : public Triangle { //... //REQUIRES: a, b, c, are non-negative and form // an isosceles triangle //MODIFIES: this //EFFECTS: set edge lengths void set_b(double b_in) { b = c = b_in; } void set_c(double c_in) { b = c = c_in; } }; 34 Overriding member functions Problem: Isosceles can’t modify a, b or c, because they are private members of Triangle We have seen this before Bad solution: protected members class Isosceles : public Triangle { //... void set_b(double b_in) { b = c = b_in; } void set_c(double c_in) { b = c = c_in; } }; 35 class Triangle { //... private: double a,b,c; }; protected members class Triangle { public: //member functions ... protected: //edge lengths represent a triangle double a,b,c; }; protected members can be seen by all members of this class and any derived classes 36 public vs. private vs. protected public Any code inside the class (member functions) or outside the class can access public members private Only code inside the class (member functions) can access private members protected Code inside the class (member functions) as well as derived classes (member functions of inherited classes) can access protected members 37 protected members class Isosceles : public Triangle { //... void set_b(double b_in) { b = c = b_in; } void set_c(double c_in) { b = c = c_in; } }; Now, Isosceles member functions can modify a, b and c because they are protected member variables of Triangle, and Isosceles is derived from Triangle 38 class Triangle { //... protected: double a,b,c; }; Overriding member functions When a class overrides a function, the function in the derived class is called, not the base class Isosceles soldier(1,11); soldier.print(); soldier.set_b(11.1); soldier.print(); 39 $ ./a.out a=1 b=11 c=11 a=1 b=11.1 c=11.1 Isosceles::set_b() runs, not Triangle::set_b() This is what we want Problems with protected Problem: Triangle::get_acute() caches the acute angle, computing only once, and using the cached value many times Isosceles soldier(1,11); soldier.print(); cout << "acute=" << soldier.get_acute() << endl; soldier.set_b(11.1); soldier.print(); cout << "acute=" << soldier.get_acute() << endl; 40 $ ./a.out a=1 b=11 c=11 acute=0.0909404 a=1 b=11.1 c=11.1 acute=0.0909404 Edge length changed, angle should have changed too! Problems with protected Problem: Triangle::get_acute() caches the acute angle, computing only once, and using the cached value many times Triangle::set_b() flagged the cached acute member variable to be recomputed void Triangle::set_b(double b_in) { b = b_in; acute = -1.0; } Isosceles::set_b() does not void Isosceles::set_b(double b_in) { b = c = b_in; } 41 Problems with protected Solution: forget protected member variables class Triangle { private: protected: double a,b,c; }; Let's just reuse Triangle::set_b(), which correctly flags the cached acute member variable to be recomputed class Isosceles : public Triangle { //... void set_b(double b_in) { Triangle::set_b(b_in); Triangle::set_c(b_in); } }; 42 Scope resolution operator (::) Use the scope resolution operator (::) to call the set_b() and set_c() functions inherited from Triangle instead of Isosceles class Isosceles : public Triangle { //... void set_b(double b_in) { Triangle::set_b(b_in); Triangle::set_c(b_in); } }; 43 Subtypes: Introduction Isosceles has a special property: any code that expects a Triangle will work correctly with an Isosceles object Put another way: we can replace any Triangle object in a program with an Isosceles object and the program will still work 44 Subtypes: Introduction Recall our program to count the craftsmen in Flatland We can substitute our new data type Isosceles in place of Triangle in our old program, and it still works 45 Isosceles Triangle census[SIZE]; //fill with Flatland’s inhabitants int num_craftsmen = 0; for (Isosceles *t=census; tget_acute() > 1.0384709) num_craftsmen += 1; cout << num_craftsmen << endl; Flatland.cpp Liskov Substitution Principle If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program (correctness) In other words: For any instance where an object of type T is expected, an object of type S can be supplied without changing the correctness of the original computation This is called the Liskov substitution principle 46 Barbara Liskov, MIT Liskov Substitution Principle The C++, subtypes can be created with derived classes However, not all derived types (classes) are subtypes! In our Flatland example, Isosceles is a derived type (class) because it inherits from Triangle class Isosceles : public Triangle { //... Isosceles is also a subtype because it fulfills the Liskov Substitution Principle 47 Triangle Isosceles base class derived class supertype subtype C++ inheritance fulfills Liskov substitution principle Liskov Substitution Principle For a derived type to also be a subtype, code written to correctly use the supertype must still be correct if it uses the subtype This is true of our Flatland example It is helpful to remember that Isosceles is a Triangle, so we can use an Isosceles in place of a Triangle 48 Liskov Substitution Principle For a derived type to also be a subtype, code written to correctly use the supertype must still be correct if it uses the subtype 49 Isosceles Triangle census[SIZE]; //fill with Flatland’s inhabitants int num_craftsmen = 0; for (Isosceles *t=census; tget_acute() > 1.0384709) num_craftsmen += 1; cout << num_craftsmen << endl; Flatland.cpp How to create a subtype With Abstract Data Types, there are three ways to create a subtype from a derived type 1. Weaken the precondition of one or more operations 2. Strengthen the postcondition of one or more operations 3. Add one or more operations 50 How to create a subtype #1 and #2 apply to overridden functions 1. Weaken the precondition of one or more operations The overridden member function must require no more of the caller than the old method did, but it can require less 2. Strengthen the postcondition of one or more operations The overridden member function must do everything the old function did, but it is allowed to do more as well Think of this as doing more with less 51 Weaken precondition 1. Weaken the precondition of one or more operations. The overridden member function must require no more of the caller than the old method did, but it can require less The preconditions of a method are formed by two things: Its argument type signature The REQUIRES clause //REQUIRES: b_in is non-negative and forms a // triangle with existing edges //MODIFIES: this //EFFECTS: sets edges b and c void Isosceles::set_b(double b_in) { Triangle::set_b(b_in); Triangle::set_c(b_in); } 52 Weaken precondition We can weaken the preconditions by requiring less For example, allowing negative inputs Take absolute value of any negative input //REQUIRES: b_in is non-negative and forms a // triangle with existing edges //MODIFIES: this //EFFECTS: sets edges b and c void Isosceles::set_b(double b_in) { b_in = abs(b_in); Triangle::set_b(b_in); Triangle::set_c(b_in); } 53 Strengthen postcondition 1. Strengthen the postcondition of one or more operations The overridden member function must do everything the old function did, but it is allowed to do more as well The postconditions of a method are formed by two things: Its return type signature The EFFECTS clause //REQUIRES: b_in is non-negative and forms a // triangle with existing edges //MODIFIES: this //EFFECTS: sets edges b and c void Isosceles::set_b(double b_in) { Triangle::set_b(b_in); Triangle::set_c(b_in); } 54 Strengthen postcondition We can strengthen the EFFECTS clause by promising everything we used to, plus extra For example, Isosceles overrode the Triangle::set_b() function, to not only set b, but also c void Triangle::set_b(double b_in) { b = b_in; } void Isosceles::set_b(double b_in) { Triangle::set_b(b_in); //does everything Triangle did Triangle::set_c(b_in); //plus more } 55 Add an operation The final way of creating a subtype is to add a member function Any code expecting only the old function will still see all of them, so the new function won't break old code For example: class Isosceles : public Triangle { public: //... void set_base(double base) {/*...*/} void set_leg(double leg) {/*...*/} }; 56 Subtype vs. subclass We get a derived type (subclass) any time we use inheritance class Isosceles : public Triangle {/*...*/}; A derived type is a subtype only if it fulfills the Liskov Substitution Principle The only reason we had to worry about the substitution principle was because we could change the base class using its set functions Isosceles soldier(1,11); soldier.set_b(11.1); 571 1111.1 Exercise: equilateral triangle Code an Equilateral class representing an equilateral triangle Declare a derived class Draw the new class hierarchy Write two constructors: one default, one with input Override any necessary member functions 58 a aa
还剩56页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 3 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

qiuhuaiyao

贡献于2014-10-27

下载需要 3 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf