Embedded C++ - polymorphism and inheritance

房东的猫 提交于 2019-12-24 09:48:12

问题


I'm writing a game (space invaders) on an Arduino which is connected to a graphical LCD, and I have a sprite class. This class has attributes such as Player/Alien, a bitmap object, location(x,y), and a how to move function.

I want each instance to have a missile and I think this may be done by inheritance and polymorphism, though I'm unsure how -- my streamlined code is below and to give a better idea as to shapes I've included a glyph image. I'd like Missile to derive location(x,y) from the sprite class, but it'll have its own bitmap and method of movement, something like(?)

Class Missile: public Sprite{
  Missile();   // create shape here
  void Move(); // has its own method of moving, but starts from Sprite(x,y)  
};  

[Regardless of how to do this, I'd like to use inheritance and polymorphism for my C++ practice please]

Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);
unsigned char spaceShip[5]    PROGMEM = {0x3c, 0x1e, 0x1f, 0x1e, 0x3c};
unsigned char spaceAlien[5]   PROGMEM = {0x1e, 0x0f, 0x1f, 0x0f, 0x1e};
unsigned char spaceMissile[5] PROGMEM = {0x00, 0x00, 0x1f, 0x00, 0x00}; 

enum TYPES {ALIEN = 0, PLAYER = 1 };
class Sprite
{
  public:
    Sprite(TYPES Type);
    void Move();
    void Render()      { display.drawBitmap(x,y, spacePtr, 5, 6, BLACK); }
  private:
    unsigned char *spacePtr;
    unsigned int x, y;
    TYPES Type;
};

Sprite::Sprite(TYPES theType)
{
  Type   = theType;
  switch( Type )
  {
      case( PLAYER ):
        spacePtr = &spaceShip[0];
        x = xPlayer(); // get x from xfunction
        y = yPlayer(); // get y from yfunction
        break;      
      case( ALIEN ):
        spacePtr = &spaceAlien[0];
        x = random(0, 82);
        y = random(10, 20);
        break;
      default: 
        break;
   }
}


回答1:


Before getting the missile involved, you should realize that your current implementation of sprite could in fact be split into (at least) three classes: A Player, an Alien and a Sprite from which the former two derive.

The point of inheritance is that it represents a "is a" relationship. I.e.: a Player IS A Sprite, an Alien IS A Sprite. In this case, a sprite is a class that can be moved, rendered, has a position and a bitmap. As you show it there, the things that distinguish an Alien from a Player is its initial position the location of its bitmap data and presumably the way it moves.

class Sprite
{
  public:
    virtual ~Sprite();
    Sprite(unsigned char * const spacePtrIn, unsigned int xInit, unsigned int yInit)
        : spacePtr(spacePtrIn)
        , x(xInit)
        , y(yInit)
    {}
    virtual void Move() = 0;
    void Render(Display &display) const { display.drawBitmap(x,y, spacePtr, 5, 6, BLACK); }
    unsigned int X() const {return x;} 
    unsigned int Y() const {return y;}
  protected:
    void X(int newX) { x = newX; }
    void Y(int newY) { y = newY; }
  private:
    unsigned char * const spacePtr;
    unsigned int x, y;
};

class Alien : public Sprite
{
public:
   Alien()
     : Sprite(spaceAlien, random(0, 82), random(10, 20))
   {}
   virtual void Move();
};

class Player : public Sprite
{
public:
   Player()
     : Sprite(spaceShip, xPlayer(), yPlayer())
   {}
   virtual void Move();
};

Once we have separated the specialized properties of players and aliens from the general properties of a Sprite, it should make it clearer how a missile relates to a sprite: It IS one.

class Missile : public Sprite
{
public:
   Missile(Sprite const &launchPoint)
     : Sprite(spaceMissile, launchPoint.X(), launchPoint.Y())
   {}
   virtual void Move();
};

The assumption is that once the missile originates from the specified Sprite it has nothing further to do with it. Note that there is no dependency between Missiles and Aliens/Players.

Other points to note is that the x and y attributes of the Sprite can only be modified by the sub-classes through protected setter functions, leaving the Sprite class to range check and/or store the values in a different format if it desires - this is the principle of encapsulation at work. Also, I have removed the reference to a presumably global display object - instead it is passed into the now const Render function when required. This is good for a raft of reasons, not least the general evilness of globals and the hidden dependencies they introduce. By making the function const, the caller can more readily assume that once the function returns, the display will not be touched by that object until the next call. The overhead of doing this is likely to be very low so the fact you are doing it on an Arduino shouldn't deter you, likewise with getter/setter functions as the compiler will most likely optimize them away.

Polymorphism comes into this because the code invoking the Render and Move methods doesn't need to know what the actual type of the objects it's dealing are in order to do so - all it needs to know about are sprites.

void MoveAndRender(Sprite **begin, Sprite **end, Display &display) 
{
   for(; begin != end; ++begin)
   {
      (*begin)->Move();
      (*begin)->Render(display);
   }
}

Of course there are an infinite number of ways of tackling this problem and the hardest bit is defining the problem in the first place. This is just a demonstration of how inheritance might fit your scenario.

Inheritance is only one of a number of OO relationships and is often overused or misused, resulting some truly hideous code. Familiarize yourself with composition ("has a"), aggregation ("shares a"), and association ("knows a"?) as well. Often a mix of inheritance and composition is far more effective than inheritance alone (see Bridge pattern, Proxy pattern and friends.)

Edit Removed the Type attribute since the actual object type should be all that is needed to characterize its behaviour - this is point, really. Added MoveAndRender example.



来源:https://stackoverflow.com/questions/19191438/embedded-c-polymorphism-and-inheritance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!