Assume that you are writing a video game software. Since different people may calculate their health indexes in different ways, it seems that you can't understand how to declare healthvalue as virtual:
Class gamecharacter {
Public:
Virtual int healthvalue () const;
...
};
Because this design is so obvious, you may not seriously consider other alternatives. To help you skip the common path of object-oriented design, let's consider other solutions:
Implement the template method mode using the non-Virtual Interface Method
One idea school advocates that virtual functions should be almost always private. They suggested that the better design is to keep healthvalue as a public member function, but make it non-virtual and call a private virtual function (such as dohealthvalue) for actual work:
Class gamecharacter {
Public:
Int healthvalue () const
{
... // Do some work beforehand
Int retval = dohealthvalue ();
... // Do some post-event work
Return retval;
}
PRIVATE:
Virtual int dohealthvalue () const // derived class can be redefined.
{
... // Calculate the health index by default.
}
};
This basic design allows the customer to indirectly call the private virtual function through the Public Non-virtual member function, which is called the non-Virtual Interface (nvi) method. It is a unique form of template method design patterns. This non-virtual function (healthvalue) is called the virtual function's overlay (wrapper ).
The advantage of nvi lies in the above Code comment "Do some work beforehand" and "Do some work afterwards. This means that the wrapper ensures that appropriate scenarios can be set before a virtual function is called and the scenario can be cleared after the call is completed. "Work beforehand" can include locking mutex, manufacturing operation log entries, verifying class constraints, and verifying function prerequisites.
Nvi involves redefining the private virtual function in the derived class. Redefine several functions that are not called by derived class! There is no conflict here. "Redefining a virtual function" indicates how something is completed, and "calling a virtual function" indicates when it is completed. Nvi allows the derived class to redefine virtual functions to give them "How to Implement functions" control, but the base class reserves the power to tell when a function is called.
The nvi method does not need to make the virtual function private. Sometimes it must be protected. Sometimes it is even public, so nvi cannot be implemented.
Implement the Strategy mode through function pointers
Another design proposition is that "the calculation of the person health index has nothing to do with the person type". Such calculation does not require the "person" component. For example, we may require each character's constructor to receive a pointer pointing to a healthy computing function, and we can call this function for actual computation:
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;
};
This is a simple application of the common strategy design pattern. Compared with the "virtual functions in the gamecharacter inheritance system" approach, it provides some interesting Elasticity:
Different entities of the same character type can have different health computing functions. For example:
Class evilbadguy: Public gamecharacter {
Public:
Explicit evilbadguy (healthcalcfunc HCF = defaulthealthcalc)
: Gamecharacter (HCF)
{...}
...
};
Int losehealthquickly (const gamecharacter &);
Int losehealthslowly (const gamecharacter &);
Evilbadguy ebg1 (losehealthslowly); // matches people of the same type
Evilbadguy ebg2 (losehealthquickly); // different health computing methods
The health index calculation function of a known person can be changed at runtime. For example, gamecharacter can provide a member function sethealthcalculator to replace the current health index calculation function.
These computing functions do not specifically access the internal components of the object "to be computed Health Index. For example, defaulthealthcalc does not access the Non-Public component of evilbadguy. If you need non-public information for exact calculation, the problem arises. The only solution to "non-public components that need to access the class using the non-member function" is to weaken the class encapsulation. For example, the class can declare the non-member function as friends or provide public access functions for a part of its implementation. Using function pointers to replace virtual functions (for example, "each object can have its own healthy computing functions" and "changing computing functions during running ") is enough to make up for the disadvantages (for example, it may be necessary to reduce the gamecharacter encapsulation ).
Use tr1: function to complete the Strategy Mode
Instead of using function pointers, we use an object of the tr1: function type. Such an object can hold (SAVE) Any callable entity, that is, the function pointer, function object, or number of member function pointers), as long as the signature is compatible with the demand side.
Class gamecharacter;
Int defaulthealthcalc (const gamecharacter & GC );
Class gamecharacter {
Public:
// Healthcalcfunc can be any "callable object" and can be called and accepted
// Anything compatible with gamecharacter returns anything compatible with Int.
Typedef STD: tr1: function <int (const gamecharacter &)> healthcalcfunc;
Explicit gamecharacter (healthcalcfunc HCF = defaulthealthcalc)
: Healthfunc (HCF)
{}
Int healthvalue () const
{
Return healthfunc (* This );
}
...
PRIVATE:
Healthcalcfunc healthfunc;
};
Here, we emphasize the specific target signature of TR: function in different colors. The signature indicates the function "accepting a reference pointing to const gamecharacter and returning int"
STD: tr1: function <int (const gamecharacter &)>
The so-called compatibility means that the parameter of this callable object can be implicitly converted to const gamecharacter &, and its return type can be implicitly converted to int.
The customer has more amazing elasticity in the case of "specifying healthy computing functions:
Short calchealth (const gamecharacter &); // function return non-int
Struct healthcalculator {// function object designed for computing health
Int operator () (const gamecharacter &) const
{
...
}
};
Class gamelevel {
Public:
Float health (const gamecharacter &) const; // member function, used to calculate health
...
};
Class evilbadguy: Public gamecharacter {
...
};
Class eyecandycharacter: Public gamecharacter {
...
};
Evilbadguy ebg1 (calchealth); // Function
Eyecandycharacter ecc1 (healthcalculator (); // function object
Gamelevel currentlevel;
...
Evilbadguy ebg2 (STD: tr1: BIND (& gamelevel: health, currentlevel, _ 1); // member function
Gamelevel: health claims that it accepts two parameters, but actually accepts two parameters because it also obtains an implicit parameter gamelevel, which is the one referred to by this. However, gamecharacter's health computing function only accepts a single parameter: gamecharacter. If we use gamelevel: health as the Health computing function of ebg2, We must convert it in some way so that it no longer accepts two parameters (one gamecharacter and one gamelevel ), instead, it accepts a single parameter (gamecharacter ). So we bind currentlevel to the gamelevel object so that it is used when "every time gamelevel: Health is called to calculate the health of ebg2. That is exactly the act of tr1: bind.
Classical Strategy Mode
Create a healthy computing function as a virtual member function in a separate inheritance system.
Class gamecharacter;
Class healthcalcfunc {
...
Virtual int calc (const gamecharacter & GC) const
{...}
...
};
Healthcalcfunc defaulthealthcalc;
Class gamecharacter {
Public:
Explicit gamecharacter (healthcalcfunc * phcf = & defaulthealthcalc)
: Phealthcalc (phcf );
{}
Int healthvalue () const
{
Return phealthcalc-> calc (* This );
}
...
PRIVATE:
Healthcalcfunc * phealthcalc;
};
Each gamecharacter object contains a pointer pointing to an object from the healthcalcfunc inheritance system.
You can also provide the possibility of "incorporating an existing health computing algorithm into use" as long as you add a derived class to the healthcalcfunc inheritance system.