Как избежать рекурсии при использовании двух синглтонов?

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

У меня также есть класс Player (своего рода аудиоплеер), который также является синглтоном.

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

Итак, это создает бесконечную рекурсию. Я хотел бы сохранить шаблон Singletons. Есть ли способ установить для экземпляра в getInstance () значение, отличное от «null», даже если Constructur еще не завершен?

Спасибо


person user1894572    schedule 14.03.2013    source источник
comment
Если вы все еще хотите использовать синглтон, просто используйте блок синхронизации. Пример: public static Gui getInstance () {// Синхронизация стоит дорого, поэтому сделайте проверку снаружи. if (instance == null) {synchronized (Gui.class) {// После получения контроля еще раз проверьте, не обновил ли экземпляр // какой-либо другой поток. если (экземпляр == ноль) {экземпляр = новый Gui (); }}} возвращаемый экземпляр; }   -  person Shailesh Kumar Dayananda    schedule 26.03.2017


Ответы (4)


Возможно, я бы вообще избегал одноэлементного шаблона . Как вы обнаружили, это затрудняет управление жизненным циклом создания.

Вместо этого я бы создал GuiManager, который создает Gui, а затем вводит его в соответствующие компоненты, которым необходимо знать об этом? Это называется инверсией управления (или внедрение зависимости) и позволяет избежать необходимости в глобальном состоянии. Преимущества включают упрощение тестирования (поскольку окружающая среда управляет жизненным циклом объектов) и предсказуемость создания этих объектов.

person Brian Agnew    schedule 14.03.2013
comment
Разве это не плохой код, если я должен предоставить каждому классу экземпляр графического интерфейса в их конструкторе? - person user1894572; 14.03.2013
comment
@ user1894572 не обязательно. почему ты думаешь, что это плохо? - person Aboutblank; 14.03.2013
comment
Надеюсь, это не на каждом занятии. Но (скажем) объект Player может создаваться / управляться через PlayerManager, и это может ссылаться на Gui и управлять взаимодействиями Player / Gui. - person Brian Agnew; 14.03.2013
comment
Потому что это немного сложно. Если есть класс, который создается в другом классе, я должен дать этому первому классу графический интерфейс, даже если этот первый класс действительно не нуждается в графическом интерфейсе. - person user1894572; 14.03.2013
comment
Необязательно, чтобы вы выполняли инъекцию зависимостей только через конструктор. Вы также можете использовать сеттеры. - person Atul; 15.03.2013

создать функцию инициализации

public class Gui
{
    public static Gui instance;
    public static Gui getInstance()
    {
        if(instance == null)
        {    
            instance = new Gui();
            instance.initialize();
        }
        return instance;        
    }

    private Gui()
    {

    }

    private initialize()
    {
        // do constructor work here
    }
}
person x4rf41    schedule 14.03.2013
comment
Это кажется интересным, я попробую. - person user1894572; 14.03.2013

Я бы использовал статический блок инициализации. Это вызывается при создании класса. не когда объект создается. и его вызывали только один раз. подробнее

person greenkode    schedule 14.03.2013
comment
Тоже интересно ... Я это проверю. Спасибо - person user1894572; 14.03.2013

Эта рекурсия является признаком более общей проблемы проектирования. Вы, конечно, можете попытаться решить эту проблему путем «утечки» ссылки на экземпляр из конструктора или отложить «построение» на какой-либо метод инициализации, который вы вызовете позже (возможно, лениво), но я бы не рекомендовал этот 1.

Почему GUI и класс Player должны быть одиночными и должны знать друг о друге, а точнее, должны знать друг друга во время построения? Есть ли какая-то другая причина, кроме «Удобнее», иметь глобальный доступ к какому-либо объекту? Не то чтобы удобство было неважным, но обычно оно не должно быть единственной причиной дизайнерского решения.

Лучшим подходом было бы полностью избавиться от синглтонов, потому что нет реальной причины, по которой не может быть двух экземпляров Player и GUI одновременно. Конечно, у вас, вероятно, будет только по одному из них в вашей программе, но цель синглтонов не в этом.

Кроме того, у вас должна быть зависимость только в одном направлении. Попробуйте подумать о том, что может существовать без другого, а что нет. Я бы подумал, что графический интерфейс должен зависеть от проигрывателя, как вы, вероятно, могли представить себе проигрыватель без графического интерфейса, но графический интерфейс без проигрывателя вообще не имеет смысла. Итак, вы сначала создаете Player, а затем создаете графический интерфейс и передаете ему экземпляр Player, с которым он работает. Если вы думаете, что вам нужен доступ к вашему экземпляру графического интерфейса из любого места внутри Player, вам следует переосмыслить свой дизайн и попытаться сделать эти объекты более независимыми. Обычно это можно сделать с помощью шаблона Observer и позволить графическому интерфейсу отслеживать изменения / действия / прочее, что происходит. Таким образом, код вашего приложения вообще не знает о графическом интерфейсе пользователя. Однако внутри вашего графического интерфейса вы можете передавать экземпляры графического интерфейса сколько угодно.

Что касается идеи введения объекта «Менеджер» или паттерна «Фабрика»: это может быть полезно, но вы определенно можете сделать все, о чем я сказал выше, без этого и при этом иметь разумный дизайн. Я бы вообще был против классов «Менеджер», так как они, как правило, делают слишком много вещей и, как правило, являются своего рода замаскированным синглтоном. Фабрики, однако, могут быть полезны, но только в том случае, если вы хотите отделить фактическое построение вещей от их использования, например, чтобы позволить позже реализовать различные типы графических интерфейсов.

Итак, простая реализация будет выглядеть так:

public static void main(...) {
    Player player = new Player();
    // maybe some other stuff to configure/set up the player

    GUI gui = new GUI(player);
    gui.show(); // or something similar
}

Если вы хотите использовать фабрики, вы, конечно, можете заменить выражения new ... обращениями к фабрикам, которые вам нужно было получить раньше.

1 Почему? Ну, например, что произойдет, если вы сконструируете графический интерфейс успешно, но позже не сможете его инициализировать, например выброшено исключение? Вам понадобится какой-то способ передать это «недействительное» состояние всем объектам, которые уже получили экземпляр GUI. Кроме того, вам нужно будет убедиться, что никто не использует экземпляр GUI до его инициализации.

person Simon Lehmann    schedule 14.03.2013
comment
Это очень хорошая идея. Мне действительно следует избегать двойной зависимости. Я также должен использовать какой-то Manager, который вызывается в самом начале, который вызывает все конструкторы и передает экземпляры. Думаю, это лучший ответ. - person user1894572; 14.03.2013