Objective C ++ (20) Inheritance and object-oriented design

Source: Internet
Author: User

This article mainly refers to Chapter 6 in Objective C ++ 3rd.

The focus is on inheritance, derivation, and virtual functions. For example:

  • Virtual? Non-virtual? Pure virtual?
  • What is the interaction between the default parameter value and the virtual function?
  • How does inheritance affect the name search rules of C ++?
  • Under what circumstances is there a better choice than virtual?

These are what we will learn from this chapter.

1. Make sure that your public inheritance can shape the is-a relationship.

Remember the meaning of public inheritance:

If class D inherits class B in the form of public, the object of each type D is also an object of type B, and vice versa.

That is, B is more general than D, and D is more special than B.

For example:

class Person { ... };class Student : public Person { ... };

This system tells us that every student is a person, but not everyone is a student.

From the perspective of C ++, if any function is expected to obtain a Person (or a pointer or reference to the Person object) of the type ), you are also willing to accept a Student object (or pointer or reference ).

Note that:

It is sometimes wrong to model the is-a relationship based on our intuition in our life. It can be said that we have made an "empirical error ".

For example:

Should class Square inherit class Rectangle in the form of public?

Is a square a (is-a) rectangle?

At least we learned this in School: A Square is a rectangle, but a rectangle is not necessarily a square.

So let's write some inheritance.

Class Rectangle {public: virtual void setHeight (int newHeight); virtual void setWidth (int newWidth); virtual int height () const; virtual int width () const ;......}; void makeBigger (Rectangle & r) {int oldHeght = r. height (); r. setWidth (r. width () + 10); assert (r. heght () = oldHeght); // determines whether the r height has changed. It is always true .}

A square is derived from the rectangle.

class Square : public Rectangle { ... };Square s;...assert( s.width() == s.height() );makeBigger(s);assert( s.widht() == s.height() );

Obviously, makeBigger only changes the width of the rectangle without changing the length of the rectangle. This is in conflict with s.

The meaning of public is: everything that can be used on the base class object should also be used on the derived class object.

It can be seen that the intuition we acquire in other fields or in our lives is not always correct in the software field.

Therefore, in addition to the is-a relationship, we also need to think more about and use has-a and is-implemented-in-terms-of (based on something) in appropriate cases)

Summary:

"Public inheritance" means is-. Everything that applies to base classes must also apply to derived classes. Each derived class object is also a base class object.

2. Avoid hiding the inherited name

Keyword: scope.

Let's take a look at a simple example:

int x;void someFunc() {    double x;    std::cin >> x;}

The statement for reading data uses the local variable x instead of the global variable x. Because the name of the inner scope masks the name of the peripheral scope.

Add the Inheritance Mechanism with the following code:

class Base {private:    int x;public:    virtual void mf1() = 0;    virtual void mf2() ;    void mf3();    ....};class Derived : public Base {public:    virtual void mf1();    void mf4();    ....};

The mf4 function has the following implementation:

void Derived::mf4() {    ...    mf2();    ...}

Query scope sequence of the compiler:

Local scope ---> scope covered by class Derived

---> Scope of class coverage (this case stops here)

---> The namespace scope of the Base

---> Global scope.

Add several member functions for the above two classes:

class Base {private:    int x;pubic:    virtual void mf1() = 0;    virtual void mf1( int );    virtual void mf2() ;    void mf3();    viod mf3( double );    ....};class Derived : public Base {public:    virtual void mf1();    void mf3();    void mf4();    ....};

What is the result of this operation?

Derived d;int x;......d.mf1();d.mf1(x);    //errord.mf2();d.mf3();d.mf3(x);    //error

It can be seen that the name masking rule based on the role is not specially processed due to the overload function, and the overload functions with the same name are also masked.

If we want to inherit the overload functions in the subclass and override some of them (such as mf1 and mf3 in this example), we can use the using statement.

Make everything in the Base class named mf1 and mf3 (all overloaded functions) visible in the Derived scope.

Class Base {private: int x; public: Base () {}; virtual void mf1 () = 0; virtual void mf1 (int m) {std :: cout <"Base mf1 int:" <m <std: endl ;}; virtual void mf2 () {std :: cout <"Base mf2" <std: endl ;}; void mf3 () {std: cout <"Base mf3" <std: endl ;}; void mf3 (double m) {std: cout <"Base mf3 double:" <m <std: endl ;}; class Derived: public Base {public: using Base: mf1 ;// Make all the things in the Base class named mf1 and mf3 (all overloaded functions) using Base: mf3; // visible in the Derived scope. Virtual void mf1 () {std: cout <"Derived mf1" <std: endl ;}; void mf3 () {std :: cout <"Derived mf3" <std: endl ;}; void mf4 () {std: cout <"Derived mf4" <std :: endl ;};};

Call:

Derived* d = new Derived();d->mf1();d->mf1(1);d->mf2();d->mf3();d->mf3(1);d->mf4();

Run:

Class Base {public: virtual void mf1 () = 0; virtual void mf1 (int );....}; class Derived: private Base {public: virtual void mf1 () {Base: mf1 () ;}// Transfer Function ......};

Summary:

The names in derived classes mask all overload functions with the same name in base classes. This mechanism does not need to be used in public inheritance.

You can use the using declarative or transfer function to call the hidden overload function.

3. differentiate between interface inheritance and implementation inheritance

Select the inheritance set:

  • A: You want derived classes to inherit only the interfaces of member functions.
  • B: I want derived classes to inherit both the interface and implementation of functions, and rewrite the implementation they inherit.
  • C: You want derived classes to inherit both the excuses and implementations of functions, and do not allow rewriting of anything.

Let's look at a geometric example:

class Shape {public:    virtual void draw() const = 0;    virtual void error(const std::string& msg);    int objectID() const;};class Rectangle: public Shape { ...... };class Ellipse: public Shape { ...... };

First consider the pure virtual function draw

The pure virtual function has two outstanding features:

  • They must be re-declared by any subclass that "inherits them ".
  • They are generally not defined in abstract classes.

Combining the above two features: the purpose of declaring a pure virtual function is to allow the derived class to inherit only the function interface

It satisfies Scenario a at the beginning of this section.

Consider the virtual function error.

The purpose of a virtual function is to allow derived classes to inherit the interface and default Implementation of the function. Meets scenario B.

Finally, consider the non-virtual function objectID.

The purpose of declaring a non-virtual function is to make derived classes inherit the function interface and a mandatory implementation.

Corresponds to Scenario c.

Pure virtual functions, virtual functions, and non-virtual functions allow you to specify exactly what you want to inherit from derived classes.

Summary:

Interface inheritance and implementation inheritance are different. Under public inheritance, derived classes always inherits the base class interface.

The pure virtual function only specifies the interface inheritance.

Virtual functions specify interface inheritance and default implementation inheritance.

Non-virtual functions specify interface inheritance and mandatory implementation inheritance.

4. consider other options than virtual functions.

Consider designing an inheritance system for in-game characters.

Class GameCharacter {public: virtual int healthValue () const; // returns the body's health index. ......};

Sometimes, conventional object-oriented design methods often seem so natural that we have never considered other solutions.

This section reminds us to jump out of the conventional design thinking and consider some less conventional design methods.

Method 1: use non-virtual interface to implement the Template Method mode

class GameCharacter {public:    int healthValue() const  {        ...        int retVal = doHealthValue();        ...        return retVal;    }    ....private:    virtual int doHealthValue() const {        ...    }};

The customer indirectly calls the private virtual function through the public non-virtual member function, which is called the non-virtual interface (NVI) method.

This non-virtual function (healthValue) is called the virtual function wrapper ).

From the perspective of program execution, derived classes redefined virtual functions to give them "How to Implement functions" control capabilities. base classes reserves the right to control "when a function is called.

Method 2: implement the Strategy mode by using Function Pointer

The Code is as follows:

class GameCharacter:;int defaultHealthCalc(const GameCharacter& gc);class GameCharacter {public:    typedef int ( *HealthCalcFunc ) ( const GameCharacter& );    explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc ) : healthFunc(hcf) { }    int healthValue() const {        return healthFunc(*this);    }    ...private:    HealthCalcFunc healthFunc;};

Other methods are not discussed here. For details, see Objective C ++.

 

5. Do not redefine the inherited non-virtual function

Redefining the inherited non-virtual function in the subclass will lead to a conflict in your design.

For example, if there is a non-virtual function setBigger in class Base, and all subclasses that inherit the Base can execute larger actions, this action is immutable (common ).

If the setBigger function is rewritten in the class Derived: public Base subclass, the class Derived cannot reflect the property of "immutability over feature.

On the other hand, if the setBigger operation needs to be redefined in the subclass, it should not be set as a non-virtual operation ).

Therefore, redefining the inherited non-virtual function may not cause too much trouble to the running of your program, but as mentioned above, this is a contradiction or defect in design.

 

6. Do not redefine the inherited default parameter values.

The discussion in this section is limited to "inheriting a virtual function with default parameter values ".

Reason: virtual functions are dynamically bound. default parameter values are statically bound.

class Shape {public:    Shape() {};    enum ShapeColor { Red = "red", Green = "green" , Blue = "blue"};    virtual void draw(ShapeColor color = Red) const = 0    {        std::cout << "This shape is " << color << std::endl;    }};class Rectangle : public Shape {public:    Rectangle() {};    virtual void draw ( ShapeColor color = Green ) const;};class Circle : public Shape {public:    virtual void draw(ShapeColor color) const;};

Consider the following pointer first

Shape* ps;Shape* pc = new Circle;Shape* pr = new Rectangle;

The static ps, pc, and pr types are Shape *

The so-called dynamic type is "the type of the object currently referred ". That is to say, the dynamic type can show the behavior of an object.

In this example, there is no dynamic ps type, the dynamic pc type is Circle *, and the dynamic pr type is Rectangle *.

The dynamic type can be changed during program execution (usually through the assignment action ). For example

Ps = pc; // The dynamic type of ps is now Circleps = pr; // The dynamic type of ps is now Rectangle

The above is a simple review of dynamic binding and static binding.

Now, consider virtual functions with default parameter values.

In the preceding example, the color parameter of the draw function in Shape is Red by default, and the color parameter of the draw function in the subclass is Green.

Shape* shape = new Rectangle();shape->draw();

Based on Dynamic binding rules, the output of the above Code should be: This shape is 1

However, after running the code, we will find that the result is not what we think.

References:

Objective C ++ 3rd

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.