Где разместить методы util в иерархии C ++ - PullRequest
1 голос
/ 07 марта 2019

У меня есть базовый класс в качестве интерфейса и несколько производных классов.У меня есть 2 функции утилит, которые должны использоваться методами каждого производного класса.Таким образом, я не могу поместить утилиты в производные классы, так как это будет дублирование кода.Таким образом, я могу поместить их в отдельное пространство имен util, но я не хочу, чтобы он предоставлялся клиентам.Так что эти методы нужно где-то спрятать.Каким должен быть мой дизайн или как мне использовать эти функции?

1 Ответ

2 голосов
/ 07 марта 2019

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

Abstract pure virtual interface base class
        ^
        |
        |
Implementation base class: common things, including your utility functions
  ^            ^             ^
  |            |             |
  |            |             |
Impl1         Impl2         Impl3

-------- ...in code: --------

class IThing
{
public:
   virtual void runAround();
   virtual void makeAMess();
};

class ThingBase : public IThing
{
public:
   // runAround() is not implemented here; implement it in derived classes
   virtual void makeAMess()
   {
      // ...default implementation of makeAMess(), which a derived class
      // can override if necessary...
   }
private:
   int utilFunc1(int a) { return ...whatever...; } // not virtual! (or, it could be, if necessary)
   int utilFunc2(int a) { return ...whatever...; } // not virtual! (or, it could be, if necessary)
}

class Thing1 : public ThingBase
{
public:
   virtual void runAround() { /* ...special code for Thing1... */ }
   // Thing1 uses the default implementation of makeAMess()
}

class Thing2 : public ThingBase
{
public:
   virtual void runAround() { /* ...special code for Thing2... */ }
   // Thing2 uses the default implementation of makeAMess()
}

class Thing3 : public ThingBase
{
public:
   virtual void runAround() { /* ...special code for Thing3... */ }
   virtual void makeAMess() { /* ...special code for Thing3... */ }
}

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

В каждой таблице производных будет указатель на дополнительную функцию для этой общей функции, который должен вызываться полиморфно при вызове из указателей / ссылок интерфейса, даже если существует только одна реализация этого метода. Каждый такой вызов потребует дополнительных двух или трех инструкций и дополнительного извлечения памяти, потому что это полиморфно. Дополнительные издержки обычно не считаются слишком большими, но это дополнительные издержки, и в некоторых ситуациях они могут быть значительными (например, полиморфный operator[]() для класса «Array», когда тысячи раз используется в узком цикле). : Если все деривации будут иметь указатель private, куда бы ни были распределены его данные, вы можете просто поместить этот указатель в базовый класс и реализовать единственную неполиморфную версию operator[](), которая обращается к соответствующему элементу на основе этого указателя ).

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

Not-fully-abstract "interface" base class
  ^            ^             ^
  |            |             |
  |            |             |
Impl1         Impl2         Impl3

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

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