Reputation: 3482
I am trying to create an inheritance hierarchy in C++ using the following simple model: courses, modules, and lessons, in which courses consist of zero or more modules and modules consist of zero or more lessons. Each of these classes also contains other information:
class course
{
private:
const std:string & name_;
const std::duration duration_;
const difficulty difficulty_;
...
};
class module
{
private:
const std:string & name;
const std::duration duration;
...
};
class lesson
{
private:
const std:string & name;
const std::duration duration;
...
};
My questions:
Personally, I think it would be more correct for them to inherit from Vector in the following fashion:
class course : public std::vector<module>
{
private:
const std:string & name_;
const std::duration duration_;
const difficulty difficulty_;
...
};
class module : public std::vector<lesson>
{
private:
const std:string & name;
const std::duration duration;
...
};
class lesson
{
private:
const std:string & name;
const std::duration duration;
...
};
since courses are a collection of modules, and modules are a collection of lessons with some additional properties, and inheriting from vector would provide all of the standard functionality needed to add, remove, etc. modules and lessons.
The other possible way would be to have:
class course : public namedentity
{
private:
std::vector<module> modules_;
...
const difficulty difficulty_;
...
};
class module : public namedentity
{
private:
std::vector<lesson> lesons_;
...
};
class lesson : public namedentity
{
private:
...
};
Where namedentity contains all of the common properties, and these private vectors contain their children.
I guess what it boils down to is, are courses really vector<module>
, and modules really vector<lesson>
? Does this violate LSP or other OO principles? Would it be better served by having their children as a property?
Upvotes: 2
Views: 126
Reputation: 73446
It's not advisable to inherit publicly from a vector, because a course, a lesson, or a module are not vectors.
It just happens that you use vectors to implement them, because a module can have several courses. If tommorow you'd use a set, a map, or a linked list, or a database mapper to implement your objects, your'd have to rewrite your whole code if you base yourself on vector inheritance. And don't forget that , the standard vector emplace(), push_back(), erase(), ... would need to be overloaded, because all these operations on courses in a module would need to adapt the course duration.
By the way, a good rule of the thumb for class design is to use inheritance for "is-a" relationships and composition for "has-a".
More arguments for and against vector inheritance are in this SO question.
The second alternative looks much better, because a course, a lesson, a module really is a named entity. The relationship between the objects is also much clearer with this approach
Both approaches could be compatible with the Liskov Subsitution Principle:
namedentity
, the same code could be used replacing the named entity with any of these subclasses (provided that they all implement constructors that can can be used with the same arguments than the base class). This makes sense whatever evolution you make on your datastructure in the future. vector<module>
, the same code could work with a course
instead. However, there are chances that you'd use a different semantics in the constructors. LSP will backfire if you change your implementation. Upvotes: 3
Reputation: 1584
As a general rule, it's a bad idea to inherit from a standard library container. One compelling reason is that in use cases like the one you describe, it is hard not to provide the extra functionality offered by the derived class without requiring some constraints on the operations performed on the container, which breaks substitutability.
Instead, if you think the API of your class should expose container-operations on the collection of children, it's both cleaner and easier to provide a method that gives a reference to the container, stored as a member of the class. Better still would be to only expose the functionality of the enclosed container that you need, so that you have a place to enforce any invariants your class requires now, or might in the near future.
Regarding inheriting common properties: generally this is a good idea, but there are two motivations for doing so, namely code reuse and abstraction, and they don't necessarily always align.
Inheriting from an purely abstract class is more or less equivalent to implementing an interface in Java, and performance considerations aside, there's little reason to avoid multiple inheritance from such classes providing a little care is taken. On the other hand, the use of inheritance as a mechanism for code reuse allows you to use what is essentially a composition without all the forwarding boiler-plate code.
To be more concrete:
I'd advise against inheriting from std::vector
. Provide methods that expose the required manipulations on the contained vector
instead. This allows you both the option of changing your container implementation, and imposing any class invariants you may need.
If any other part of the code only cares about the namedness of these objects, then inheriting from a namedentity
is good design.
If all these classes need the functionality of a non-purely abstract namedentity
, then this is a different but also valid reason to inherit from it.
Upvotes: 2