Re: polymorphic design approach

From: Anthony Borla (ajborla_at_bigpond.com)
Date: 12/20/03


Date: Sat, 20 Dec 2003 09:08:08 GMT


"ma740988" <ma740988@pegasus.cc.ucf.edu> wrote in message
news:a5ae824.0312191228.51a3ebb3@posting.google.com...
>
> Bear with me as I move deeper into OO style programming
> from a C world. Consider
>
<SNIP CODE>
>
> Now that was great 'stuff' in our C code (old design) but
> I realize after reading and reading .. that said approach in
> a C++ land - is bad programmign style. So now why undermine
> C++. In essence, I'm using one feature of C++ (switch) to
> 'cheat' another feature of C++ (polymorphism). Just 'use'
> polymorphism. The question. Could I garner some guidance
> on a design approach in a polymorphic sense that'll simplify
> the above?
>

I don't think a 'polymorphic' approach is warranted to this problem because
you are not performing a switch on object type, but on object identity. As
such, you have to 'switch' at some point, whether at a high level
[application object level], or at a lower level. Any benefit polymoprhism
could offer here is, IMO, quite minimal.

>
> An aside: I'd like to start without the use of STL maps,
> etc (dont really want to bite off more than I can chew
> right now). The point being. I suspect an even better solution
> lies in the use of the STL maps or perhaps using a approach
> call 'factory' which i perused in one my new texts.
>

The example code makes use of 'std::vector'. I hardly think you'll
experience any difficulty understanding it.

The sample code solves your 'routing' problem by using a highly simplified
'Chain of Responsibility' pattern. In essence:

* A circular chain of processors is set up; each is in a
   sibling relationship with its immediate neighbour

* Each processor has a collection zero or more devices;
   each device has a unique identifier

* A message intended for a specific device is sent
   to a processor; it will then determine whether:

   - The message is intended for it; if so, it passes it
     to the device collection for possible matching

   - The message is not for it; if so, it passes it on
      to its sibling effectively 'handing off ' responsibility
      for the message handling

* Once the intended device has been located it fires off
   its 'handleMessage' function to, as its name implies,
   handle the message !

Some polymorphic behaviour has been included, namely in the overriding of
the 'handleMessage' function for different devices. I believe it [with
suitable modification], pretty well, meets the requirements outlined in your
sample code.

Miscellaneous Code Comments:

* The bulk of the code exists to handle STL container
    requirements; a linked list of devices could have been
    implemented, simplifying the code

* 'Device' pointers are stored so as to allow polymorphic
   behaviour. Storing raw pointers requires great care, and
   is also cumbersome as you need to consider ownership
   issues carefully [i.e. who owns, therefore gets to delete,
   the pointers]

* While the sample code has made extensive use of
   iterators, much more elegant code is possible by
   using the STL algorithms [e.g. 'for_each', 'transform',
   etc]. However, these require a little more setting up -
   not shown to keep the code 'simple' [though it *is*
   cumbersome as is]

* A much better way of storing pointers is via Smart Pointer
   objects [i.e. the pointer is a data member of such objects].
   Safer, more elegant and robust code can result. Of course,
   it is a little more work to set up :) !

* Forgive the inlined code. What I'd intended as a short post
   took a little longer than expected [isn't that *always* the
   way, but I keep firing up my editor despite this :)]

Hopefully this code helps deepen your understanding of C++.

Anthony Borla

// Sample Code ------------------------------------------------

#include <cstdlib>
#include <cassert>
#include <iostream>
#include <ostream>
#include <string>
#include <vector>

class Message
{
public:
  explicit Message(const std::string& procID, const std::string& devID,
                            const std::string& payload)
    : procID_(procID), devID_(devID), payload_(payload)
  {}

  std::string getProcID() const { return procID_; }
  std::string getDevID() const { return devID_; }
  std::string getPayload() const { return payload_; }

private:
  std::string procID_, devID_;
  std::string payload_;
};

class Device
{
public:
  explicit Device(const std::string& id) : id_(id) {}
  virtual ~Device() {}

  std::string getID() const { return id_; }

  // Overrride this to do your 'device stuff'
  virtual void handleMessage(const Message& message)
  {
    std::cout << "Default Device: " << message.getPayload() << std::endl;
  }

  bool canHandleMessage(const Message& message)
  {
    return (message.getDevID() == getID()) ? (handleMessage(message), true)
: false;
  }

  friend bool operator<(const Device& left, const Device& right)
  {
    return left.id_ < right.id_;
  }

  friend bool operator==(const Device& left, const Device& right)
  {
    return left.id_ == right.id_;
  }

  friend bool operator!=(const Device& left, const Device& right)
  {
    return left.id_ != right.id_;
  }

private:
  std::string id_;
};

class D1 : public Device
{
public:
  explicit D1(const std::string& id) : Device(id) {}

  virtual void handleMessage(const Message& message)
  {
    std::cout << "D1 Type handling: " << message.getPayload() << std::endl;
  }
};

class D2 : public Device
{
public:
  explicit D2(const std::string& id) : Device(id) {}

  virtual void handleMessage(const Message& message)
  {
    std::cout << "D2 Type handling: " << message.getPayload() << std::endl;
  }
};

class D3 : public Device
{
public:
  explicit D3(const std::string& id) : Device(id) {}

  virtual void handleMessage(const Message& message)
  {
    std::cout << "D3 Type handling: " << message.getPayload() << std::endl;
  }
};

class Processor
{
  typedef std::vector<Device*> Devices;

public:
  explicit Processor(const std::string& id)
   : id_(id), sib_(NULL)
  {}

  ~Processor() { sib_ = NULL; removeAllDevices(); }

  bool isLinked() const { return sib_ != NULL; }
  Processor* getSibling() const { return sib_; }

  void linkToSibling(Processor* sib)
  {
    if (isLinked()) return;
    assert(this != sib && this != sib->getSibling());
    sib_ = sib;
  }

  std::string getID() const { return id_; }
  int getCurrentDevices() const { return dev_.size(); }

  void addDevice(Device* dev)
  {
    dev_.push_back(dev);
  }

  void removeDevice(std::string id)
  {
    Devices::iterator i;

    for (i = dev_.begin(); i != dev_.end(); ++i)
    {
      if ((*i)->getID() == id)
      {
        delete *i; dev_.erase(i); break;
      }
    }
  }

  void routeToProcessor(const Message& message)
  {
    if (getID() == message.getProcID())
      routeToDevice(message);
    else
      getSibling()->routeToProcessor(message);
  }

private:
  void routeToDevice(const Message& message)
  {
    Devices::iterator i;

    for (i = dev_.begin(); i != dev_.end(); ++i)
      if ((*i)->canHandleMessage(message))
        break;
  }

  void removeAllDevices()
  {
    Devices::iterator i;

    for (i = dev_.begin(); i != dev_.end(); ++i)
      delete *i;

    dev_.clear();
  }

private:
  std::string id_;

  Processor* sib_;
  Devices dev_;
};

// Driver
int main()
{
  {
    Processor p1("p1");
    Processor p2("p2");
    Processor p3("p3");
    Processor p4("p4");

    p1.addDevice(new Device("d11"));
    p1.addDevice(new Device("d12"));
    p1.addDevice(new Device("d13"));

    p2.addDevice(new D2("d21"));
    p2.addDevice(new D1("d22"));
    p2.addDevice(new D3("d23"));

    p3.addDevice(new D3("d31"));
    p3.addDevice(new D2("d32"));
    p3.addDevice(new D3("d33"));

    std::cout << p1.getCurrentDevices() << std::endl;
    std::cout << p2.getCurrentDevices() << std::endl;
    std::cout << p3.getCurrentDevices() << std::endl;
    std::cout << p4.getCurrentDevices() << std::endl;

    // Setup circular chain of processors: p1 ... p4 ... p1
    p1.linkToSibling(&p2);
    p2.linkToSibling(&p3);
    p3.linkToSibling(&p4);
    p4.linkToSibling(&p1);

    p1.routeToProcessor(Message("p1", "d13", "abc"));
    p1.routeToProcessor(Message("p2", "d21", "123"));
    p1.routeToProcessor(Message("p3", "d33", "DEF"));

    p4.routeToProcessor(Message("p1", "d14", "abc"));
    p4.routeToProcessor(Message("p2", "d22", "123"));
    p4.routeToProcessor(Message("p3", "d31", "DEF"));
  }

  return EXIT_SUCCESS;
}



Relevant Pages

  • Re: sizeof(ptr) = ?
    ... A void * has the same representation as a char *. ... Char pointers which require additional data ... iff the default offset is 0. ...
    (comp.lang.c)
  • Re: Malloc code
    ... int xxx; ... As for not using the void pointer, I will have to do some further testing ... I just needed some insight on passing arrays of pointers. ... struct MCB *r1; ...
    (microsoft.public.vc.language)
  • Re: MFC Flicker and the Application Framework
    ... You may find this to be much easier and you don't have to worry about the flickering stuff so much. ... // FlickeringCounterView.cpp: implementation of the CFlickeringCounterView ... void CFlickeringCounterView::AssertValidconst ... virtual void OnBeginPrinting; ...
    (microsoft.public.vc.mfc)
  • Re: On the creation of an API for containers in C
    ... pointers, and these dynamic vectors. ... where 'dyt' was basically a wrapper for an anonymous void pointer. ... cost of this approach), although, if one makes use of the dynamic-type ... typed void pointers' and 'tagged references' (where the value is essentially ...
    (comp.lang.c)
  • Re: Realloc and pointer arithmetics
    ... through an AVL tree that managed void* data. ... the C standard does not. ... My frist thought was actually to store into the tree pointers to array ...
    (comp.lang.c)