Интерфейсы классов: базовые или сложные? - PullRequest
3 голосов
/ 16 марта 2011

Я пишу контейнерный класс для веселья и образования.Ранее, когда я писал контейнерные классы, я ограничивал себя несколькими базовыми методами: GetValue, SetValue, GetSize и Resize.Я сделал это, чтобы избежать «спагетти кода», чтобы мой класс было легче отлаживать.

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

void Replace(const std::size_t Start, const std::size_t End, const T Value);
void Replace(const std::size_t Start, const std::size_t End, const MyClass Other);
void Insert(const std::size_t Index, const T Value);
void Insert(const std::size_t Index, const MyClass Other);
void Delete(const std::size_t Index);
void Delete(const std::size_t Start, const std::size_t End);

В целом, должны ли классы обеспечивать только самый базовый интерфейс и позволять пользователям класса создавать свои собственные функции для выполнения сложных задач?Или сложный материал должен быть встроен за счет ремонтопригодности?

Ответы [ 5 ]

2 голосов
/ 17 марта 2011

Вам следует постараться сохранить интерфейс простым, особенно если вы хотите реализовать различные типы контейнеров, например, на основе массива и связанного списка.Если вы предоставите некоторые базовые методы во всех ваших контейнерах, вы можете создать внешние алгоритмы, которые выполняют определенные задачи, но которые могут работать во всех контейнерах:

 void Replace(const std::size_t Start, const std::size_t End, const T Value);

может стать

 template<class ContainerType>
 void ReplaceAllElementsInContainer(ContainerType& Container, const std::size_t Start, const std::size_t End, const T Value);

внеклассы.Если вы этого не сделаете, вы должны написать все эти методы во всех ваших контейнерах.

Другая возможность - использовать шаблонный шаблонный метод (не связанный с шаблонами C ++) и записать все эти методы в базу.класс (который определяет базовые методы как чисто виртуальные и вызывает их из реализованных «удобных» методов).Это приводит к возможному количеству вызовов виртуальных функций, которые могут быть нежелательны в классе контейнера по соображениям производительности.

2 голосов
/ 17 марта 2011

Проблема в том, что как только вы напишите другой контейнерный класс (их много в дикой природе, вам могут понадобиться разные виды), вы обнаружите, что ваш дизайн возводится в квадрат в O (N * M), где N - это количество контейнерных классов и M количество алгоритмов.

Решение состоит в том, чтобы отделить контейнеры от алгоритмов, и поэтому в STL были введены итераторы.

Существуют альтернативы итераторам, например. полиморфизм. Вы можете выделить интерфейс обхода в абстрактном общем базовом классе и реализовать алгоритмы на его основе.

Короче говоря, держите большую часть логики вне своих контейнерных классов.

2 голосов
/ 17 марта 2011

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

Вы уже назвали основную причину этого: это значительно облегчает обслуживание класса. Кроме того, реализация вашей части метода «convienence» будет хорошим тестом, чтобы проверить, достаточно ли у вас интерфейс.

Обратите внимание, что часть функции-члена контейнера, как правило, должна быть очень общей и мощной и не заботиться о чем-то большем, чем поддержание инвариантов класса.

Это самое современное мнение по этому вопросу, насколько я знаю. Он широко пропагандируется в «Эффективном C ++» Скотта Мейера (в последнем 3-м издании) и в «Стандартах кодирования C ++» Саттера и Александреску.

1 голос
/ 17 марта 2011

У меня был похожий случай, подобный этому. Я предлагаю, чтобы у вас было 2 «базовых класса» или «суперкласса».

Первый класс, очень общий класс, представляет «концептуальный корень» для всех классов контейнеров, почти не методов, похожих на интерфейс, и должен иметь вид:


container.hpp

class Container
{
protected:
   int GetValue();
   void SetValue(int newValue);

   size_t GetSize();

   void Resize(size_t);
};

Второй класс начинает становиться чуть менее концептуальным и более "реальным миром":


mcontainers.hpp

#include "containers.hpp";

class MethodContainer: public Container
{
protected:
  void Replace(const std::size_t Start, const std::size_t End, const T Value);
  void Replace(const std::size_t Start, const std::size_t End, const MyClass Other);
  void Insert(const std::size_t Index, const T Value);
  void Insert(const std::size_t Index, const MyClass Other);
  void Delete(const std::size_t Index);
  void Delete(const std::size_t Start, const std::size_t End);

}

И, наконец, некоторые классы, которые являются конкретными:


stacks.hpp

#include "containers.hpp";
#include "mcontainers.hpp";

#define pointer void*

class Stack: public MethodContainer
{
public:
  // these methods use "mcontainer::Insert", "mcontainer::Replace", etc
  void Push(pointer Item);
  void Pop();
  pointer Extract();
}

AS @Chris упомянул, что для этого есть несколько библиотек, но из правила всегда есть исключение, и вы можете захотеть «заново изобрести колесо», если вам это нужно.

У меня было приложение с его набором библиотек, которое включало в себя некоторые контейнеры / коллекции. Это было сделано в другой прогр. Langr. и нужно его перенести на C ++. Хоть я и проверил стандартные библиотеки c ++, я прекратил миграцию своих библиотек на C ++, потому что у меня было несколько библиотек, вызывающих библиотеки контейнеров, и мне нужно было это сделать быстро.

При использовании «базовых классов» вы можете захотеть «защитить» своих членов и «донести это до общественности» в дочерних классах. Я обычно не делаю "приватные" поля или методы, если это не обязательно.

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

0 голосов
/ 16 марта 2011

Если этот контейнер используется только вами в вашем коде, а методы вашего интерфейса достаточны для конкретной цели, то это нормально.

Однако, как только кто-то собирается использовать контейнер или вы планируете использовать его в других областях, я бы рекомендовал добавить методы интерфейса, работающие с типами итераторов, тогда ваш контейнер будет гораздо более открытымиспользуется с контейнерами и алгоритмами stdlib.В качестве примера используйте интерфейсы контейнеров stdlib.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...