C++ Singleton class-наследование хорошей практики

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

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

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

во-вторых, я сомневаюсь, что могу классически вывести одноэлементный класс.

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

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

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

спасибо!

2 ответов


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

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

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

  2. использование singleton и" хорошая практика " обычно не идут вместе! См.: что плохого в синглетах?

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


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

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

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

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

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

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

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

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 может дать вам.)

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

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

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