views:

190

answers:

4

Say if I want to extend the functionality of a range of objects that I cannot change - for instance for adding formatting, and don't want to derive a huge amount of classes to extend the functionality - would the following considered bad? (I'm using int and float as an example, but in my program I have about 20 or 30 classes that will end up in a tree that will contain a generic type).

class ITreeNode
{
public:
 virtual ~ITreeNode() {}
 virtual void print() = 0;
};

template <class T>
class ObjectNode : public virtual ITreeNode
{
public:
 virtual ~ObjectNode() {}
 ObjectNode(T v)
 {
  m_var = v;
 }
 void print()
 {
  print(m_var);
 }
protected:
 void print(int i)
 {
  printf("int (%d)\r\n", m_var);
 }
 void print(float f)
 {
  printf("float (%f)\r\n", m_var);
 }
 T m_var;
};

int _tmain(int argc, _TCHAR* argv[])
{
 ObjectNode<int> tInt(5);
 ObjectNode<float> tFloat(5.5);
 tInt.print();
 tFloat.print();
 getchar();
 return 0;
}

Basically I need a tree of objects (all of the same base type) that I can call with these extended functions. Originally I was using the visitor pattern and creating a visitor class for every one extended bit of functionality I needed. This seemed very cumbersome and thought maybe the above would be better.

I thought I'd post here first as a sanity check..

Thanks in advance.

A: 

If you end up having one or two implementations of print for these 20-30 classes, yes, I would consider it ok. But if you end up with an ObjectNode class with 20-30 implementations of print I would say the class has dependencies more than is healthy.

Peter Lillevold
How about if one of the functions is to fetch the icon for each particular type? Basically it's a client server system, and the data objects that get passed around between client and server should not know about the icons I want to use in my particular client user interface implementation, so they cannot be put in those classes. If I could, I could just do something like: object.getIcon(). At the moment I'm using a visitor class (the objects have an "accept" function that accepts a abstract visitor class).
Mark
@marksim - sure, the data (your `ints` and `floats` :), should not care about the icons. And again, if the sole purpose of the class is to figure out icons for a range of types (maybe using a type-to-icon lookup map) I would consider it ok. Consider the Single Responsibility Prinsiple: http://en.wikipedia.org/wiki/Single_responsibility_principle
Peter Lillevold
@Peter Lillevold: Thanks, I'll go take a look at that page.
Mark
+1  A: 

Have you considered specializing your template for each of the types you wish to print? This should work without changing the way you're calling the code - but it'll also provide some compile-time type checking to ensure that your generic type has a valid print() method.

template<>
class ObjectNode<int> : public virtual ITreeNode {
    ObjectNode(int v) : m_var(v) {}
    void print() {
        printf("int (%d)\r\n", m_var);
    }
protected:
    int m_var;
};

template<>
class ObjectNode<float> : public virtual ITreeNode {
    ObjectNode(float v) : m_var(v) {}
    void print() {
        printf("float (%f)\r\n", m_var);
    }
protected:
    float m_var;
};
Mark H
@Mark H: Thanks Mark I like that, it feels cleaner than mine. I'm going to go away and experiment with that for a while. If you look at my first comment in reply to Peter Lillevold's answer, you'll see what I'm basically trying to acheive; I need to get the icon for each object, plus open the appropriate dialog if the user clicks an "edit" button with a particular object selected. I realise now I should probably have been more specific in my original post in case it makes any difference..
Mark
+1  A: 

It's not uncommon, no, but you might ask yourself why you're using a class at all. Why not define the additional functionality as free (non-member) functions? Then they work directly on int and float, without you needing to wrap the objects. And your code would become a lot simpler, more maintainable and readable:

void print(int i)
{
  printf("int (%d)\r\n", m_var);
}
void print(float f)
{
  printf("float (%f)\r\n", m_var);
}

int _tmain(int argc, _TCHAR* argv[])
{
  tInt = 5;
  tFloat = 5.5;
  print(tInt);
  print(tFloat);
 getchar();
 return 0;
}

There is no law that states that "your code is only object-oriented if everything is inside a class". Relying on non-member functions as much as possible might even be more object-oriented.

jalf
A: 

This may work in your simple example, but it does not work polymorphically. That is, if you have some reference to an A, but it's actually to a derived type B, you'll still construct an ObjectNode<A> and print the thing as though it's an A, not a B. (Worse, because ObjectNode takes a T by value, it will be sliced. But that's another story.)

For polymorphism to work, you'll need the visitor pattern. But I see in your other question that you're on to that.

Thomas