Извлечь и уничтожить инкапсуляцию или нарушить СУХОЙ? - PullRequest
4 голосов
/ 02 июля 2011

У меня есть два класса C ++: Sequence, который похож на std::vector, и File, который представляет собой Sequence строк, представляющих файл на компьютере.

Получение File от Sequence - это легко. Его поведение точно такое же, но с добавленной функциональностью чтения и записи файлов. Специфичные для File функциональные возможности реализуются легко, без необходимости помечать элементы данных Sequence как защищенные. Вместо этого они могут быть частными, и File может использовать открытый интерфейс Sequence. Счастливые времена вокруг.

Я хочу создать класс Array, который внутренне управляет динамически выделяемой памятью. * Array объект не может быть изменен; размер указан в конструкторе. *

Здесь все становится спорным.

Концептуально, имеет смысл вывести Sequence из Array. Так же, как File - это Sequence с добавленной функциональностью чтения и записи файлов, Sequence - это Array с дополнительной функциональностью изменения размера по требованию.

Но есть ключевое отличие: для функций изменения размера требуется прямой доступ к памяти Array управляет. Другими словами, ранее закрытые члены теперь должны быть защищены.

Использование защищенных членов вместо личных разрушает инкапсуляцию. Связь между Array и Sequence является единственной, которая требует этого; другие классы в работах могут просто использовать общедоступные интерфейсы своих родителей. В этом смысле вывод плохой идеи.

Можно утверждать, что люди, которым нужны массивы, могут просто использовать Sequence и игнорировать функцию изменения размера. Но опять же, вы можете просто использовать File и игнорировать функции чтения / записи. Это все равно что покупать ноутбук, но никогда не сдвигать его со стола. Это просто не имеет смысла.

Какой лучший ход: извлекать и потенциально разрушать инкапсуляцию; сделать Array полностью автономным классом и бессмысленно повторно реализовывать множество функциональных возможностей; или полностью забыть о Array и просто заставить людей использовать Sequence?

* Обратите внимание, что это проект для развлечения и обучения, поэтому практичность наличия динамически выделяемого массива с неизменяемым размером не имеет значения.

Ответы [ 3 ]

4 голосов
/ 02 июля 2011

Вы можете рассмотреть проблему в несколько ином направлении. Вместо наследования, возможно, эту проблему можно решить с помощью шаблона, в частности, шаблона политики, который управляет буфером для коллекции. У вас будет (как минимум) две реализации этого: одно для фиксированного выделения, другое для автоматического изменения размера.

Это не нарушит инкапсуляцию вообще, и почти единственное совпадение, которое я вижу между двумя, будет то, что начальное распределение, вероятно, примерно одинаково, фиксированное или переменное. Учитывая, насколько это тривиально, я сомневаюсь, что стоит потратить много времени или усилий, чтобы попытаться это выяснить. Теоретически это может быть, но, по крайней мере, в типичном случае речь идет об одной строке кода, причем довольно простой.

Возвращаясь к вопросу о наследовании на мгновение, все сводится к следующему: это очень похоже на ситуацию со стандартным кругом и овалом, где достаточно сходства, чтобы один казался другим, но в конечном итоге ни один из них не удовлетворяет LSP - вы не можете относиться к одному из них как к безопасному, так что (по крайней мере, публичное) наследование не подходит. Частное наследование не требует следования LSP, но обычно полезно, только когда / если вам нужно / хотите переопределить виртуальную функцию базового класса, что и здесь маловероятно.

4 голосов
/ 02 июля 2011

Я бы здесь не использовал деривацию.

A Sequence на самом деле не Array. Хотя практически у них, похоже, есть ряд общих методов, с точки зрения дизайна они имеют очень разные применения и гарантии.

Однако, имеет смысл использовать Array в Sequence и Sequence для прямой переадресации нескольких вызовов (inline) на Array:

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

Примечание: здесь я предположил, что массив был построен со всеми его элементами одновременно, в то время как последовательность будет добавлять их по одному

Аналогично, имеет ли смысл File образоваться от Sequence? У вас нет проблем с реализацией, таких как синхронизация содержимого Sequence с представлением на диске?

3 голосов
/ 02 июля 2011

Что ж, получение Sequence из Array с публичным наследованием в вашем случае - определенно плохая идея (как получение квадрата из прямоугольника).В терминах объектно-ориентированного программирования последовательность НЕ является массивом, поскольку Array имеет свойство, которого у Sequence нет, и это: An Array object cannot be resized.Если вы сделаете деривацию, она нарушит принцип подстановки Liskov .

В вашем случае, поскольку вы хотите реализовать некоторые функции, уже существующие в другом классе, я бы предложил вам использовать либо личное наследование (что означает наследование реализации), либо состав , например, хранение экземпляра Array в частной зоне Sequence и использование его для внутренней реализации.

UPD: Однако,реализация Sequence с использованием Array также кажется мне довольно проблематичной.Возможно, было бы намного лучше создать некоторый абстрактный базовый класс Container, который бы реализовывал общую функциональность Sequence и Array, а затем вывести из него оба этих класса.

...