Objective C ++, 3rd edition, item 35: consider alternative methods of optional virtual functions (virtual functions) (below)

Source: Internet
Author: User

(Click here, next to the previous article)

The Strategy pattern via tr1: function (Strategy Mode Implemented through tr1: function)

Once you get used to the templates (Template) and implicit interfaces (implicit Interface) (see item 41) applications, function-pointer-based (function pointer based) the method seems a little rigid. Why must the calculation of healthy values be a function instead of a simple function object ))? If it must be a function, why cannot it be a member function )? Why must it return an int instead of a type that can be converted to an int?

If we replace a function pointer (function pointer) (such as healthfunc) with a tr1: function object, these constraints will disappear. As explained in item 54, such objects can holdAny callable entity(Any callable entity) (for example, function pointer (function pointer), function object (function object), or member function pointer (member function pointer )), the symbolic features of these entities are compatible with what they expect. We will immediately see this design, this time using tr1: function:

Class gamecharacter; // as before
Int defaulthealthcalc (const gamecharacter & GC); // as before

Class gamecharacter {
Public:
// Healthcalcfunc is any callable entity that can be called
// Anything compatible with a gamecharacter and that returns anything
// Compatible with an int; see below for details
Typedef STD: tr1: function <int (const gamecharacter &)> healthcalcfunc;
Explicit gamecharacter (healthcalcfunc HCF = defaulthealthcalc)
: Healthfunc (HCF)
{}

Int healthvalue () const
{Return healthfunc (* This );}

...

PRIVATE:
Healthcalcfunc healthfunc;
};

As you can see, healthcalcfunc is a typedef of tr1: function instantiation (instantiation. This means that its behavior is similar to a common function pointer (function pointer) type. Let's take a closer look at the typedef of what healthcalcfunc is:

STD: tr1: function <INT (const gamecharacter &)>

Here I highlight the "Target Signature (Target Recognition Feature)" of this tr1: function instantiation )". This target signature (Target Recognition Feature) is a function that "retrieves a reference to const gamecharacter and returns an int ". The tr1: function type (for example, healthcalcfunc type) objects can hold any callable entity (any callable entity) compatible with the target signature ). Compatibility means that the parameter of this entity can be implicitly transformed into a const gamecharacter &, and its return type can be implicitly transformed into an int.

Compared to the most recent design (where gamecharacter holds a pointer to a function) we see, this design is almost the same. The only difference is that the current gamecharacter holds a tr1: function object-pointing to a functionGeneralized(Generic) pointer. This change is so small that I would rather turn a blind eye to it in addition to the effect of "clients (customer) having greater flexibility in specifying healthy value calculation functions:

Short calchealth (const gamecharacter &); // health Calculation
// Function; note
// Non-int return type

Struct healthcalculator {// class for health
Int operator () (const gamecharacter &) const // calculation function
{...} // Objects
};

Class gamelevel {
Public:
Float health (const gamecharacter &) const; // health Calculation
... // Mem function; note
}; // Non-int return type

Class evilbadguy: Public gamecharacter {// as before
...
};
Class eyecandycharacter: Public gamecharacter {// another character
... // Type; assume same
}; // Constructor
// Evilbadguy

Evilbadguy ebg1 (Calchealth); // Character using
// Health Calculation
// Function

Eyecandycharacter ecc1 (Healthcalculator ()); // Character using
// Health Calculation
// Function object

Gamelevel currentlevel;
...
Evilbadguy ebg2 (// character using
STD: tr1: BIND (& gamelevel: health, // Health Calculation
Currentlevel,// Member function;
_ 1) // See below for details
);

In my personal sense, I found that tr1: function can surprise you so much. It makes me excited and abnormal. If you are not excited, it may be because you are staring at the definition of ebg2 and are confused about what will happen to the call to tr1: bind. Please be patient with my explanation.

For example, to calculate the health grade of ebg2, you should use the health member function (member function) in gamelevel class (class ). Now, gamelevel: Health is a function declared to get a parameter (a reference to gamecharacter), but it actually gets two parameters, because it also gets an implicit gamelevel parameter -- pointing to this. However, the health value calculation function of gamecharacters only obtains a single parameter: The gamecharacter that calculates the health value. If we want to use gamelevel: Health to calculate the health value of ebg2, we must "modify" it in some way to adapt it to obtaining only the unique parameter (A gamecharacter ), instead of two (one gamecharacter and one gamelevel ). In this example, we always use currentlevel as the gamelevel object to calculate the health value of ebg2. Therefore, Every time gamelevel: Health is called to calculate the health value of ebg2, we need "bind" (solidification) currentlevel to use as the gamelevel object. This is what the tr1: bind Call does: it specifies that the health value calculation function of ebg2 should always use currentlevel as the gamelevel object.

We skipped a lot of details, such as why "_ 1" means "using currentlevel as the gamelevel object when gamelevel: Health is called for ebg2 ". Such details are not enlightening, and they will move away from the basic point of my attention: when calculating a role's health value, by using tr1 :: function instead of a function pointer (function pointer), we will allow the customer to useAny compatible callable entity(Any compatible callable entity ). Cool, right?

The "classic" strategy pattern ("classic" policy Mode)

If you enter design patterns more deeply than C ++, a more common practice of strategy is to use health-calculation function) create a virtual member function (Virtual member function) independent health-calculation hierarchy (health value calculation inheritance system ). The hierarchy design looks like this:

If you are not familiar with the UML notation, this means that when evilbadguy and eyecandycharacter are used as derived classes (derived classes), gamecharacter is the root of this inheritance hierarchy (inheritance system; healthcalcfunc is the root of another inheritance hierarchy (inheritance system) with derived classes (derived class) slowhealthloser and fasthealthloser; each gamecharacter type object contains a pointer to an object derived from healthcalcfunc.

This is the corresponding framework code:

Class gamecharacter; // Forward Declaration

Class healthcalcfunc {
Public:

...
Virtual int calc (const gamecharacter & GC) const
{...}
...

};

Healthcalcfunc defaulthealthcalc;

Class gamecharacter {
Public:
Explicit gamecharacter (Healthcalcfunc * phcf = & defaulthealthcalc)
: Phealthcalc (phcf)
{}

Int healthvalue () const
{ReturnPhealthcalc-> calc (* This);}

...

PRIVATE:
Healthcalcfunc * phealthcalc;
};

This method is attractive because people familiar with the "standard" strategy pattern implementation can quickly identify it, plus it provides the inheritance system through the healthcalcfunc hierarchy) add a derived class (derived class) to fine-tune the possibility of an existing healthy value calculation algorithm.

Summary (Summary)

The basic recommendation of this item is to consider alternative virtual functions when you are looking for a design to solve the problem. The following is a brief review of the optional methods we have examined:

  • UseNon-virtual interface idiom(Nvi idiom) (non-Virtual Interface Usage), which uses public non-virtual member functions (Public Non-virtual member functions) to package virtual functions (virtual functions) with lower Access Permissions) template Method Design Pattern (template method mode.
  • UseFunction pointer data members(Function pointer data member) replaces virtual functions, an obvious form of strategy design pattern.
  • UseTr1: function data members(Data member) instead of virtual functions (virtual functions), this allows the use of any callable entity (any callable entity) compatible with what you need ). This is also a form of strategy design pattern.
  • UseVirtual functions in another hierarchy(Virtual functions in another inheritance system) replace virtual functions in one hierarchy (virtual functions in a single inheritance system ). This is a common implementation of strategy design pattern.

This is not an exhaustive list of alternative designs for optional virtual functions, but it is sufficient to convince you that these are optional methods. In addition, the advantages and disadvantages of mutual comparison between them should make it clearer for you to consider them.

In order to avoid getting stuck in the habitual path of object-oriented design (Object-Oriented Design), it is helpful to the wheel from time to time. There are many other ways. It is worth some time to think about them.

Things to remember

  • Alternative methods for optional virtual functions include nvi usage and various forms of change in strategy design pattern. Nvi is an instance of template method design pattern.
  • One hazard in moving a function from a member function (member function) to a function other than Class is non-member function (non-member function) there is no non-public members (non-public member) path for the members class.
  • Tr1: the behavior of the function object is similar to generalized function pointers (generic function pointer ). Such an object supports all callable entities (callable entities) compatible with a given target feature ).

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.