Derive & Destroy Encapsulation, or Violate DRY?
I have two C++ classes: Sequence
, which is just like std::vector
, and File
, which is a Sequence
of strings that represents a file on the machine.
开发者_StackOverflow社区Deriving File
from Sequence
is a no-brainer. Its behavior is exactly the same, but with the added functionality of reading and writing files. The File
-specific functionality is implemented easily, without the need for Sequence
's data members to be marked as protected. Instead, they can be private, and File
can use Sequence
's public interface. Happy times all around.
I want to make an Array
class that internally manages dynamically-allocated memory. An Array
object cannot be resized; the size is specified in the constructor.*
Here's where things get arguable.
Concept-wise, it makes sense to derive Sequence
from Array
. Just as a File
is a Sequence
with the added functionality of reading and writing files, Sequence
is an Array
with the added functionality of resizing on-demand.
But there's a key difference: The resizing functions require direct access to the memory Array
is managing. In other words, the previously-private members must now be protected.
Using protected members instead of private ones destroys encapsulation. The link between Array
and Sequence
is the only one that requires it; other classes in the works can just use their parents' public interfaces. In this sense, it's a bad idea to derive.
You could argue that people who want arrays can just use Sequence
and ignore the resizing functionality. But then again, you could just use File
and ignore the read/write functionality. It would be like buying a laptop but never moving it from your desk. It simply doesn't make sense.
What's the best move: To derive, and potentially destroy encapsulation; to make Array
a completely free-standing class, and have to pointlessly re-implement a lot of functionality; or to forget about Array
completely and just make people use Sequence
?
*Note that this is a project for fun and education, so the practicality of having a non-resizable dynamically-allocated array is beside the point.
You might consider slicing the problem in a slightly different direction. Instead of inheritance, perhaps this problem could be solved with a template -- specifically, a policy template that manages a buffer for a collection. You'd have (at least) two implementations of that: one for fixed allocation, the other for automatically resizing.
That wouldn't break encapsulation at all, and nearly the only overlap I can see between the two would be that the initial allocation is probably about the same whether fixed or variable. Given how trivial that is, I doubt it's worth spending much time or effort on trying to factor it out. In theory it could be, but at least in a typical case we're talking about one line of code, and a pretty simple one at that.
Going back to the inheritance question for a moment, it comes down to this: this is very much like the standard circle vs. oval situation, where there's enough similarity for one to seem like the other, but ultimately neither satisfies the LSP -- you can't treat either one as the other safely, so (at least public) inheritance isn't suitable. Private inheritance doesn't require following LSP, but is generally only useful when/if you need/want to override a base class' virtual function, which seems unlikely here as well.
I would not use derivation here.
A Sequence
is not really an Array
. While practically they appear to have a number of common methods, from a design point of view they have very different uses and guarantees.
It would make sense, though, to use an Array
within the Sequence
, and for Sequence
to forward a number of calls (inline) to the Array
directly:
template <typename T>
class Sequence
{
public:
Sequence(): _array(10) {}
explicit Sequence(size_t n): _array(n) {}
bool empty() const { return _size == 0; }
size_t size() const { return _size; }
size_t capacity() const { return _array.size(); }
private:
size_t _size; // current size
Array<T> _array;
}; // class Sequence
Note: I assumed here that the Array was built with all its elements at once, while the sequence will add them one at a time
Similarly, does it make sense for a File
to derive from a Sequence
? Don't you have implementation issues, like sync'ing the content of Sequence
with the on-disk representation ?
Well, deriving Sequence
from Array
with public inheritance in your case is definitely bad idea (as deriving square from rectangle). In the terms of Object-Oriented Programming, Sequence IS NOT an Array, since Array
has a property that Sequence
does not have, and it's: An Array object cannot be resized
. If you make a derivation, it will break the Liskov substitution principle.
In your case, as you want to implement some functionality, already existing in another class, I would suggest you to use either private inheritance (which means inheritance of implementation), or composition, e.g. storing an instance of Array
in private zone of Sequence
and using it for inner implementation.
UPD: However, implementing Sequence
with usage of an Array
also seems to me quite problematic. Maybe it would by much better to create some abstract base class Container
that would implement the common functionality of Sequence
and Array
, and then derive both these classes from it.
精彩评论