Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Let's say I have a class called Country and two subclasses called AI and Player. The Country class has a number of virtual methods to allow player-specific or AI-specific behavior.

I want to make it possible for the player to switch countries during a game. To this end, I am refactoring the inheritance relation to composition. Then I will be able to switch out a Player's internal instance of Country for another at any time.

But this brings up a bit of a problem: When an external object attempts to call one of these virtual methods on a Player, it is delegated correctly to the Country method after any appropriate processing has been done. But when an internal Country method calls one of the virtual methods, the code in the Player or AI version no longer has a chance to run.

So now I'm left with updating every one of those methods to make sure both versions are called, which in itself is not a trivial task (infinite loops, anyone?).

This seems much harder than it ought to be. I've done similar refactorings before and never run into this issue. Why? Am I missing some blindingly obvious observation?


Edit for a concrete example now that I have access to the code again:

class Country
{
public:
    virtual void AddMoney(float money)
    {
        _vMoney += money;
    }
    void TakeLoan()
    {
        float loan = ...;
        AddMoney(loan); // <-- does not route through AI or Player implementation
    }
}

 

class AI  // : public Country
{
private:
    Country *pCountry;

public:
    virtual void AddMoney(float x)
    {
        pCountry->AddMoney(x); // formerly Country::AddMoney(x)
        AllocateMoney(x);
    }
}
share|improve this question
    
It sounds like your design has hopelessly entangled game logic (Country) with interaction (Player and AI). You probably need an event/callback system to disentangle the two. –  Sebastian Redl May 21 at 13:27

2 Answers 2

So let's make this concrete-ish.

class Country {
  virtual void foo();
  void bar() {
    foo();
  }
}

Moving toward:

class Country {
  private thingWhichWasSubclass;
  void bar() {
    thingWhichWasSubclass.foo();
  }
}

Is that right? How about as an intermediate step:

class Country {
  private thingWhichWasSubclass;
  void foo() {
    thingWhichWasSubclass.foo();
  }
  void bar() {
    foo();
  }
}

?

Would that do it? Then (of course) inline Country.foo().

share|improve this answer
    
(+1) For: "So let's make this concrete-ish." @mmyers, its ok to describe the problem, but if you do a small code example, along, it will be more clear to you and the readers. –  umlcat Jun 6 '11 at 23:02

I'd got pretty much the same question, but this time in Java. I found one solution that uses non-static nested classes in java - which in C++ would translate to something like:

class AI  // : public Country
{
private:
    Country *pCountry; 

public:
    class Super : public Country {
        AI* outerThis;

    public:
        Super(AI* outer, ...)
        {
            outerThis = outer;
        }


        virtual void AddMoney(float x)
        {
            Country::AddMoney(x);
            outerThis->AllocateMoney(x);
        }
    } 

    // Just delegating calls to pCountry...
    virtual void AddMoney(float x)
    {
        pCountry->AddMoney(x);
    }
}

Then of course you need to instantiate AI::Super in order to get a Country associated with a specific AI object. Perhaps instantiate it from the AI object itself (which removes the need for AI:Super to be public).

Basically replacing inheritance with composition is doing what's being done behind the scene explicitely. Behind the scene it's composition plus extending the virtual function dispatch table. In case of no virtual functions it's straight forward, but in the case of virtual functions you will have to take these into account. Normal way of doing this (behind the scenes) is to have the virtual table consisting of base class' entries (with overriden function replaced) followed by the derived class' entries, but it could also be implemented by having separate vtables for the base object (with overridden functions replaced) and one for the new virtual method's from the derived class (as in my example). In the latter case you could have the base object separately allocated and store just the pointer to it.

The only circular reference her is the AI <-> AI::Super which might result in circular calls. However the only calls from AI to AI::Super should be to wrap the methods from the base class, and calls from AI::Super to AI is analogous to calling methods in the Derived class no infinite recursions should arise.

share|improve this answer
    
It's not clear how your answer addresses the OP's concerns regarding long term maintenance and updating the code. Please consider editing your answer further to address those points. Programmers.SE is more focused on the why rather than the how within answers. –  GlenH7 May 8 at 11:08
    
I'm not sure I understand what you try to say Glen. I can identify two concerns from OP. First is that calls to virtual method in the base class should behave as before, that is taken care of by introducing a derived class that performes the overriden function in AI. The second is the concern of infinite loops and I think that won't happen with this setup. –  skyking May 21 at 9:33
    
the edit helps. Previously, you had a block of code with the equivalent explanation of "just use this." That didn't address why the OP would want to use that code. Until the why is explained, the OP can't see the value of the code. After the explanation, the value becomes much more clear. Broadly speaking, Programmers.SE focuses on teaching how to solve the problem as opposed to just solving the problem. It's the depth of understanding that we strive for. –  GlenH7 May 21 at 11:43

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.