Класс C ++ Singleton - хорошая практика наследования - PullRequest
7 голосов
/ 21 февраля 2012

В существующем проекте я должен унаследовать класс Controller (MVC), объявленный как Singleton, чтобы определить мою собственную обработку. Как правильно получить этот класс Singleton?

Во-первых, я расширяю контекст и нуждаюсь в этом наследовании.

Приложение, которое я добавил к существующему программному обеспечению, хочет использовать модуль MVC, который выполняет почти ту же задачу, что и модуль, который я готов выполнить. Он использует те же методы, вплоть до подписи и небольших модификаций. Переписать мой собственный модуль MVC, безусловно, будет дублирование кода. Существующий модуль изначально ориентирован на его применение в другой части программного обеспечения, и я не могу просто использовать тот же модуль. Но записывается как шаблон Model-View-Controller, где Controller - это Singleton. Я получил View уже.

Во-вторых, я сомневаюсь, что смогу классически получить класс Синглтона.

Вызов конструктора из унаследованного класса просто вызовет getinstance () для родительского класса и не сможет вернуть объект из производного класса (?).

В-третьих, это то, как я вижу какой-то способ справиться. Пожалуйста, прокомментируйте / помогите мне улучшить!

Я копирую весь класс Singleton в класс, который я мог бы назвать AbstractController. Я получаю этот класс дважды. Первый ребенок - одиночка и принимает весь курс родительского класса. Второй дочерний элемент - это Контроллер для моей части приложения, с собственной переопределенной обработкой.

Спасибо!

Ответы [ 2 ]

26 голосов
/ 21 февраля 2012

Правда в том, что синглтоны и наследование не очень хорошо сочетаются друг с другом.

Да, да, любители синглтона и культ GoF будут повсюду за меня, говоря: «Хорошо, если вы сделаете свой конструктор защищенным... "и" у вас нет метода getInstance для класса, вы можете поместить его ... ", но они просто доказывают мою точку зрения.Синглтоны должны перепрыгнуть через несколько обручей, чтобы быть и одиночным, и базовым классом.

Но просто чтобы ответить на вопрос, скажем, у нас есть базовый класс синглтона.Он может даже в какой-то степени усилить свою единственность посредством наследования.(Конструктор делает одну из немногих вещей, которые могут работать, когда он больше не может быть закрытым: он выдает исключение, если другой Base уже существует.) Скажем, у нас также есть класс Derived, который наследуется от Base.Поскольку мы разрешаем наследование, давайте также скажем, что может быть любое количество других подклассов Base, которые могут или не могут наследоваться от Derived.

Но есть проблема - та, которую вылибо уже сталкиваешься, либо скоро.Если мы вызовем Base::getInstance без создания объекта, мы получим нулевой указатель.Мы бы хотели вернуть любой существующий объект-одиночка (это может быть Base, и / или Derived, и / или Other).Но это трудно сделать и все же следовать всем правилам, потому что есть только несколько способов сделать это - и у всех них есть некоторые недостатки.

  • Мы могли бы просто создатьBase и верните его.Винт Derived и Other.Конечный результат: Base::getInstance() всегда возвращает ровно Base.Детские классы никогда не играют.Кинда побеждает цель, ИМО.

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

  • Мы могли бы сделать вариант этого последнегоодин - но вместо того, чтобы получить экземпляр, функция просто создает один.(Пока мы находимся, давайте переименуем функцию в initInstance, так как нам не особо важно, что она получит - мы просто вызываем ее, чтобы она создала новый Derived и установила его как единое целое.True Instance.)

Итак, (за исключением каких-либо странностей, еще не учтенных), это работает примерно так ...

class Base {
    static Base * theOneTrueInstance;

  public:
    static Base & getInstance() {
        if (!theOneTrueInstance) initInstance();
        return *theOneTrueInstance;
    }
    static void initInstance() { new Base; }

  protected:
    Base() {
         if (theOneTrueInstance) throw std::logic_error("Instance already exists");
         theOneTrueInstance = this;
    }

    virtual ~Base() { } // so random strangers can't delete me
};

Base* Base::theOneTrueInstance = 0;


class Derived : public Base {
  public:
    static void initInstance() {
        new Derived;  // Derived() calls Base(), which sets this as "the instance"
    }

  protected:
    Derived() { }   // so we can't be instantiated by outsiders
    ~Derived() { }  // so random strangers can't delete me
};

И в вашем коде инициализации,Вы говорите Base::initInstance(); или Derived::initInstance();, в зависимости от того, какой тип вы хотите, чтобы синглтон был.Вам, конечно, придется приводить возвращаемое значение из Base::getInstance(), чтобы использовать любые Derived -специфичные функции, но без приведения вы можете использовать любые функции, определенные в Base, включая виртуальные функции, переопределенные Derived.

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

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

  • Базовый класс не может взять все ответственности, хотя - каждый класс должен объявить защищенный деструктор, или кто-то может прийти и удалить один экземпляр после приведения его (в) соответствующим образом, и все это пойдет в ад.Что еще хуже, это не может быть применено компилятором.

  • Поскольку мы используем защищенные деструкторы, чтобы предотвратить случайное чмо от удаления нашего экземпляра, если компилятор не умнее, чем я боюсь, даже среда выполнения не сможет правильно удалитьВаш экземпляр, когда программа заканчивается.До свидания, RAII ... привет "предупреждения об обнаружении утечки памяти".(Конечно, память в конечном итоге будет восстановлена ​​любой приличной ОС. Но если деструктор не запустится, вы не можете полагаться на то, что он выполнит за вас очистку. Перед тем, как вывыход, и это не даст вам никаких гарантий, которые может дать RAII.)

  • Он предоставляет метод initInstance, который, IMO, на самом деле не принадлежит API, который может видеть каждый.Если вы хотите, вы можете сделать initInstance закрытым и позволить вашей функции инициализации быть friend, но тогда ваш класс делает предположения о коде вне себя, и связующее средство возвращается в игру.

Также обратите внимание, что приведенный выше код вовсе не безопасен для потоков.Если вам это нужно, вы сами по себе.

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

3 голосов
/ 21 февраля 2012

Я не уверен, что полностью понимаю ситуацию, с которой вы имеете дело, и от того, как реализован синглтон, в значительной степени зависит то, возможно ли или целесообразно получить его из синглтона.

Но поскольку вы упомянули "хорошую практику", при чтении вопроса приходят на ум общие моменты:

  1. Наследование обычно не лучший инструмент для повторного использования кода. См .: Предпочитаете ли композицию наследованию?

  2. Использование синглтона и «хорошей практики», как правило, не идут вместе! Смотри: Что плохого в синглетах?

Надеюсь, это поможет.

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