Pull to refresh

Comments 55

S — принцип единственной ответственности. Очевидно, что синглтон противоречит ему, как уже говорилось раньше.


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

Синглтону God в монотеизме не нужно управлять инстансами. Их убивают в религиозных войнах без участия God.

O — принцип открытости/закрытости: объекты должны быть открыты для расширения, но закрыты для изменения. Синглтон нарушает данный принцип, так как контролирует точку доступа и возвращает только самого себя, а не расширение.

Этот принцип не применим, так как не обязан возвращать расширение по определению.

L — принцип подстановки Барбары Лисков: объекты могут быть заменены экземплярами своих подтипов без изменения использующего их кода. Это неверно в случае с синглтоном, потому что наличие нескольких разных версий объекта означает, что это уже не синглтон.
Для таких объектов нельзя сказать, что такие объекты реализую синглтон. Если они не реализуют синглтон, то синглтон не нарушает принцип.
I — принцип разделения интерфейса: много специализированных интерфейсов лучше, чем один универсальный. Это единственный принцип, который синглтон нарушает не напрямую, но лишь потому, что он не позволяет использовать интерфейс.

В очередной раз, вы говорите что субъект нарушет принцип, только потому что принцип не применим к синглтону
.
D — принцип инверсии зависимостей: вы должны зависеть только от абстракций, а не от чего-то конкретного. Синглтон нарушает его, потому что в данном случае зависеть можно только от конкретного экземпляра синглтона.

И снова…

Ну я не знаю… Не убедили…
Может быть это проблема реализации, а не синглтона как такового?

Вы банду четырех читали? Это проблема синглтона как такового по определению. Он делает что-то полезное (1) и контролирует число своих экземпляров (2).


Ну я не знаю… Не убедили…

Это и есть главное свойство любого антипаттерна: несмотря на безусловный вред и полную бесполезность, всегда находится много тех, кого "не убедили".
Принципы SOLID применимы везде где можно говорить про ООП. И "принцип А неприменим для синглтона" — это в точности то же самое, что и "синглтон нарушет принцип А".

И «принцип А неприменим для синглтона» — это в точности то же самое, что и «синглтон нарушет принцип А».

Это совершенно разные фразы, а не «в точности то же самое».
Вы банду четырех читали? Это проблема синглтона как такового по определению. Он делает что-то полезное (1) и контролирует число своих экземпляров (2).

Это в общем случае тоже некорректно.
«Синглтон» — это, на минутку, шаблон проектирования. Шаблон по определению не может делать что-то полезное или что-то контролировать. Он лишь рамки, алгоритм, общие принципы, по которым будет написан участок кода.
Если мы хотим, чтобы некий класс X инстанцировался ровно единожды, и применяем шаблон синглтон для этого, то делать что-то полезное будет новый инстанс этого класса. А вот контроллировать число может и не класс X. Это может быть другой класс со статичным методом или вовсе функция, если язык позволяет. Заниматься «чем-то полезным» будет instance класса, создавать его (ровно единожды) — совсем другая сущность. И каждая из них будет иметь ровно single responsibility. А в целом это будет по-прежнему шаблон синглтон.
Прямо религиозный текст:
«И сказал Исаак Иакову: не используй синглтоны, они плохо тестируются! И стало так!»

Предпочитаю не слушать проповедников.

Текст очень конкретный и легко проверяемый.
А вот ваш комментарий — религиозный. В нем только оценка статьи как еретической и ни одного аргумента по существу.
Хотя было бы достаточно всего лишь одного примера синглтона, более простого и полезного, чем альтернативы.

Вы не можете передавать/внедрять аргументы в конструктор. Поскольку при первом вызове синглтона реально исполняется только конструктор и вы не можете знать заранее, какой код первым обратится к синглтону, то во всём потребляющем коде нужно использовать один и тот же набор аргументов для передачи в конструктор, что во многих случаях почти невыполнимо, а вначале вообще бессмысленно. В результате синглтон делает бесполезным основной механизм инстанцирования в ООП-языках.


Это весьма странная претензия. Сиглтон используется тогда, когда объект, который им инстанцируется, максимально универсален. Если нам при вызове из точки А надо настраивать объект (передавая параметры в коснтруктор) вот так, а при вызове из точки Б — иначе, то синглтон здесь неприменим, нам надо 2 инстанса с разными «настройками».

Вы не можете имитировать (mock away) синглтон при тестировании компонентов, использующих его

Это зависит от конкретного языка и фреймворка для тестирования.

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

Потому что сиглтон-объект не должен иметь не-потокобезопасного внутреннего state'а, ни побочных эффектов.

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

Точно так же будет и с не-синглтоном: вам придется написать какой-нибудь ConnectionList или ConnectionPool, и получать из него конкретный DatabaseConnection, который будет «синглтоном», или обычным объектом в зависимости от конкретных требований.
Это опять же странная претензия — «если нам потребуется что-то сильно изменить, нам надо будет что-то сильно изменить».

Сиглтон используется тогда, когда объект, который им инстанцируется, максимально универсален

Универсальный объект, не позволяющий себя параметризовать самым простым способом? Ограничивающий число экземпляров единицей? С требованиями по отсутствию состояния и многопоточной инициализации?
Это полная противоположность универсальности.


Это опять же странная претензия — «если нам потребуется что-то сильно изменить, нам надо будет что-то сильно изменить».

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


Это зависит от конкретного языка и фреймворка для тестирования

monkey patch ради тестов — это тоже антипаттерн. С параметрами конструктора такой проблемы нет в принципе.

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

Отнюдь. Параметризация необязательно является необходимым качеством универсальности. Напротив, универсальный предмет одинаково применим к другим без дополнительного указания параметров. Например — молоток. В единственном экземпляре. Универсальный инструмент, можно применить как для забивания гвоздя, так и для отбивания пальца. При этом его не надо «параметризовать» — вот сейчас мы будем забивать гвоздь, а вот сейчас — бить по пальцу. Параметризовав его, мы создали бы не универсальный, а напротив, специализированный инструмент.

С требованиями по отсутствию состояния и многопоточной инициализации?

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

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

Это относится к разработке самого объекта, а не к синглтону как таковому.

monkey patch ради тестов — это тоже антипаттерн. С параметрами конструктора такой проблемы нет в принципе.

Во-первых, я не согласен с тем, что это антипаттерн. Во-вторых, почему monkey patch? Обычный mock можно применить и для тестирования синглтона. Зависит от конкретного языка.
Я возможно сейчас хапану негатива в свой адрес, но я уже устал молчать в неведении: в чем принципиальная разница(возьмем в пример PHP) между инстансом глобального объекта, и например классом со статическими переменными и методами?
Синглтон создает объект один раз, определенным образом инициируя внутреннее состояние объекта. Если ваш статический класс тоже позволяет настроить внутреннее состояние один единственный раз при первом обращении, то это и есть синглтон, просто чуть иначе реализованный.
Синглтон может быть реализован (условно, конечно) и в процедурном коде, без ООП.
Спасибо за ответ. Теперь у меня в голове стало немного светлее :)
В динамических языках несколько по другому, но изначально, для компилируемых языков, идея в том что ссылка на класс гарантированно существует в том месте кода где идет вызов статического метода, а ссылка на инстанс может быть не проинициализирована нужным объектом.
Ваша фабрика со статикой, по сути, тот же синглтон разделенный на два класса. Да, я понимаю, что он перестает нарушать принцип «S», но смысл? Все остальные недостатки остаются.

$result = Database::query( $query );

А тут вообще пошла процедурщина, пусть и на классах.

В вашем Service Locator просто отвратительно использование строк, да и статическая типизация отваливается напрочь. В C#, конечно, можно было бы сделать это красиво благодаря Дженерикам. Что-то вроде такого:

var conn = services.Get<IDatabaseConnection>();
conn.query()


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

Внедрение зависимостей — уже ближе к хорошей альтернативе, чем к плохой альтернативе, но непонятно, что делать в сложных приложениях с сильными зависимостями (в бизнес-логике). Вот представьте игрушку, вроде RimWorld. Активируешь какую-то абилку, а она одна влияет на настроение всех жителей мужского пола, на рост растений, а также на вероятность заспаунится металлических жуков, если прошло больше 3 лет с начала игры и на складе лежит не меньше, чем 1000 единиц металла и сложность игры на ниже «легкой». Как должен выглядеть конструктор такой абилки?

class AwesomeAbility extends Ability {
  
  public CrewContainer crew;
  public PlantsManager plants;
  public EnemySpawner spawner;
  public GameTime time;
  public PlayerStorage storage;
  public GameConfig config;
  
  public AwesomeAbility (CrewContainer crew, PlantsManager plants, EnemySpawner spawner, GameTime time, PlayerStorage storage, GameConfig config) {
    this.crew = crew;
    this.plants = plants;
    this.spawner = spawner;
    this.time = time;
    this.storage = storage;
    this.config = config;
  }
}


А где все эти значения возьмет AwesomeAbilityFactory? Ее ведь тоже кто-то должен создать. А если вдруг еще понадобится влиять на животных? Во всей иерархии добавлять зависимость?

Я пока решил для себя создавать контейнер, который имеет иерархию и каждая абилка через DI получает этот контейнер и может по этой иерархии ходить. Что-то вроде такого:

class AwesomeAbility extends Ability {
  
  public CrewContainer crew;
  public PlantsManager plants;
  public EnemySpawner spawner;
  public GameTime time;
  public PlayerStorage storage;
  public GameConfig config;
  
  public AwesomeAbility (Game game) {
    this.crew = game.crew;
    this.plants = game.objects.plants;
    this.spawner = game.enemy.spawner;
    this.time = game.time;
    this.storage = game.player.storage;
    this.config = game.config;
  }
}


Это все-равно нарушение кучи принципов, но зато довольно юзабельно и статически типизированно.

Я бы через Event Dispatcher сделал:


Как-то так
<?php

abstract class AbstractAwesomeAbilityListener
{
    public function __construct(
        GameTime $time, 
        PlayerStorage $storage, 
        GameConfig $config
    )
    {
        // ...
    }

    abstract protected function doActivate();

    public function onActivate()
    {
        if (!$this->isAllowed()) {
            return;
        }

        $this->doActivate();
    }

    /**
     * @return bool
     */
    protected function isAllowed()
    {
        // ...
    }
}

class CrewListener extends AbstractAwesomeAbilityListener
{
    public function __construct(
        CrewContainer $container, 
        GameTime $time, 
        PlayerStorage $storage, 
        GameConfig $config
    )
    {
        parent::__construct($time, $storage, $config);

        // ...
    }

    protected function doActivate()
    {
        // ...
    }
}

class PlantsListener extends AbstractAwesomeAbilityListener
{
    public function __construct(
        CrewContainer $container, 
        GameTime $time, 
        PlayerStorage $storage, 
        GameConfig $config
    )
    {
        parent::__construct($time, $storage, $config);

        // ...
    }

    protected function doActivate()
    {
        // ...
    }
}

class EnemyListener extends AbstractAwesomeAbilityListener
{
    public function __construct(
        CrewContainer $container, 
        GameTime $time, 
        PlayerStorage $storage, 
        GameConfig $config
    )
    {
        parent::__construct($time, $storage, $config);

        // ...
    }

    protected function doActivate()
    {
        // ...
    }
}
Я не совсем понял, как вы предлагаете создавать эту комплексную абилку? Вместо того, чтобы создать одну AwesomeAbility — создать 5 подабилок? А что это даст?
Я не совсем понял, как вы предлагаете создавать эту комплексную абилку?

$this->getEventDispatcher()->dispatch(Ability::AWESOME, new AwesomeAbilityEvent($subject, $target));

Вместо того, чтобы создать одну AwesomeAbility — создать 5 подабилок? А что это даст?

Это не пять абилок, это пять обработчиков одной абилки. Таким образом решаем проблему Single Responsibility. В вашем варианте у вас получается God-object

AwesomeAbility в моем примере далека до God-object, хотя она и правда имеет многовато обязаностей. Но слишком много обязаностей не всегда означатает God-object. Божественный объект — это что-то вроде класса GameController, где описаны и создание цветов и все абилки и так далее.

Декомпозировать можно по разному, в том числе полегче:
class AwesomeAbility extends ComplexAbility {

  public Ability[] CreateParts () {
    return {
      new CrewSubAbility(),
      new PlantsSubAbility(),
      // ...
    };
  }
  
}


Но мы ведь говорим сейчас не так о декомпозиции, как о работе с зависимостями. У вас вручную создаются громоздкие конструкторы в вашем классе. И непонятно, кто именно будет создавать все эти листенеры и в них передавать зависимости. Так же непонятно, где именно вы хотите хранить стейт для абилки, если у вас она столь разбита. Например, если отключение абилки завязано на время или какой-то тригер. Скажем, абилка работает три дня, или пока не нажмут рычаг. А если абилка может навешиваться несколько раз? Ну классическая баф эффект с рандомным значением (помните, как в СтарКрафте маринерам много раз нажимали шприц, чтобы повысить статы?):

Вот представьте, вы юнитам добавляете
unit.AddAbility( new AttackIncrease(Random(3, 10), Seconds(Random(10, 20))) )
unit.AddAbility( new AttackIncrease(Random(3, 10), Seconds(Random(10, 20))) )
unit.AddAbility( new AttackIncrease(Random(3, 10), Seconds(Random(10, 20))) )


И вот, через разное время должна слететь одна, потом вторая, потом третья абилка.

Кстати, еще абстрактный эвент плохое решение, потому что, к примеру, необходимо отображать, какие же абилки висят на юните. И, чем меньше времени ей осталось действовать — тем более прозрачной она должна рендерится. У вас вместо одной супер-абилити будет множество эвентов, которые просто влияют на юнит. Даже в клиент-серверной архитектуре — серверу нужно будет отдавать что-то вроде

{
  unitId: 123,
  effects: [
    { ability: "AwesomeAbility", timeLeft: 24, power: 12 },
    { ability: "AwesomeAbility", timeLeft: 14, power: 7  },
    { ability: "AwesomeAbility", timeLeft: 28, power: 29 },
  ]
}


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

Кстати, иногда абилки могут складываться из других абилок, которые могут работать сами по себе. Скажем, абилка «Увеличьте атаку на 2 за счет уменьшения прочности в два раза» может складываться из двух уже существующих абилок «Увеличение атаки на Х» и «Уменьшение прочности в Х раз», тут тоже удобен подход с ComplexAbility
AwesomeAbility в моем примере далека до God-object, хотя она и правда имеет многовато обязаностей. Но слишком много обязаностей не всегда означатает God-object.

Если сейчас он таким не является, то в будущем — весьма вероятно, "если вдруг еще понадобится влиять на животных"


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

DI контейнер. Если говорить о PHP и Symfony в частности, то даже конфигурировать почти не придется. Думаю в других языках есть аналоги.


Стейт и отображение решил бы так:


Код
class Unit
{
    private $abilites;

    public function activateStimPack(EventDispatcher $dispatcher)
    {
        $ability = new StimPackAbility($this, 10 /* seconds */);
        $dispatcher->dispatch(Abilities::STIM_PACK_ACTIVATE, $ability);
    }

    public function deactivateStimPacks(EventDispatcher $dispatcher)
    {
        $abilities = $this->getAbilities();
        foreach ($abilities as $ability) {
            $dispatcher->dispatch(Abilities::STIM_PACK_DEACTIVATE, $ability);
        }
    }
}

class StimPackListener
{
    public function __construct(EventDispatcher $dispatcher, GameTimer $timer)
    {

    }

    public function onActivate(StimPackAbility $ability)
    {
        $ability->getUnit()->addAbility($ability);
        // Add other effects
        $timer = $this->timer->tick(function() use ($ability){
            $ability->decSeconds();

            if ($ability->getSeconds() == 0) {
                $this->dispatcher->dispatch(Abilities::STIM_PACK_DEACTIVATE, $ability);
            }
        });

        $ability->setTimer($timer);
    }

    public function onDeactivate(StimPackAbility $ability)
    {
        $ability->getUnit()->removeAbility($ability);
        // Remove other effects
        $ability->getTimer()->destroy();
    }
}

У вас вместо одной супер-абилити будет множество эвентов, которые просто влияют на юнит

В моем подходе эвент = абилке. Я ее рассматриваю не как объект, а как процесс. В текущем варианте вполне можно их сохранять и выводить.


Кто отвечает за применение эффектов в вашем подходе? Сама абилка или юнит к которому она добавлена?
И как активируются AwesomeAbility из вашего предыдущего примера? Ведь по условию ее эффекты распространяются не только на того, кто ее использовал


На самом деле не претендую на правильность моего подхода. Просто решил посмотреть как он ляжет на вашу задачу. И вроде пока узких мест не вижу.

Если сейчас он таким не является, то в будущем — весьма вероятно, «если вдруг еще понадобится влиять на животных»

Если это все реально одна абилка по бизнес-требованиям, то нет. А вот если «а давай я сюда еще пару абилок зафигачу и буду их ифом переключать» — вот тут уже становится божественным.

На самом деле в вашем коде недостатки в том, что юнит должен иметь описание каждой абилки. Т.К. юнит — это просто айдишник + совокупность всех абилок (если передвижение — тоже абилка), то значительно выгоднее логику абилки хранить в коде абилки и, соответсвенно методы а-ля activateStimPack — лишние. Представьте сколько методов в юните должно быть для сотен абилок?

Я тоже знаю, что мой подход не идеален. У меня логика абилок описывается в абилках (так как их очень много), а некоторые core-вещи даже дешевле описать в самих системах.

Вот допустим, у нас есть HitPointsSystem, у нее есть что-то вроде API, которое позволяет влиять на распределение демеджа, а абилки уже пользуются этим API. HitPointsSystem слегка разрастается, но зато абилки значительно сокращаются и поток кода становится значительно более явным.

Это плохой дизайн.
Абилку и ее связи с конкретными характеристиками надо разделять.
И сами характеристики вычислять по привязанным абилкам (калькуляторы само собой тоже выделить) вместо введения изменяемого состояния.

А где все эти значения возьмет AwesomeAbilityFactory? Ее ведь тоже кто-то должен создать.

Фабрика в своем конструкторе примет либо эти значения, либо фабрики для них.

А где возьмет их тот, кто создает фабрику?

Фабрика создается в Composition Root либо через IoC-контейнер, либо вручную. В первом случае детали реализации контейнера нам не интересны, во втором случае все зависимости доступны в виде локальных переменных.

Фабрика создается в Composition Root

Не многовато ли ответственности для Composition Root — создать по фабрике на каждую абилку и не только?

Нет, не многовато. Это одна ответственность — создание фабрик.

Создание всех фабрик? Похоже на «у моего класса Game одна ответственность — чтобы игра работала».

Меня всегда в Composition Root смущала перегруженность корня.

В чем перегруженность?
Ответственность у точки сборки ровно одна: композиция объектов в контексте.
Контейнеры позволяют все настроить выразительно и гибко, сводя вопрос "созданря фабрики" к одной декларативной строке кода. Получается своего рода сборочный чертеж и автосборщик в одном флаконе.
Если точка сбоки становится слишком велика — выделяются вспомогательные контексты со своими точками сбоки и так далее.

Не правильно такими тяжелыми (специфическими) примерами иллюстрировать такой сильный (общий) тезис.
Чем сингелтон отличается от «ссылки на функцию»? Ситуация: вы используете динамическую функцию зачем ее пересобирать каждый раз при переиспользовании?

Ссылка на функцию, в отличие от синглтона, подменяется и параметризуется легко и приятно

Не понял про «параметризируется», поскольку в моем понимании «ссылка на функцию» и «ссылка на объект» это одно и тоже. Объект т.е. синглетон-объект это такая функция. Разве нет? Ну есть у объекта три метода, значит это функция возвращающая три метода.

Не понял про «подменяется», поскольку синглетон можно передавать в функцию и явно, через параметры.
Вообще это две разные проблемы: неявная передача параметров (может быть и не static индентификаторов даже) и объекты создаваемые при первом обращении и далее существующие в единственном числе в течении жизни аппликации (опять же могут быть и не через static определенные).
Не понял про «параметризируется», поскольку в моем понимании «ссылка на функцию» и «ссылка на объект» это одно и тоже.

У синглтона есть глобальная точка доступа по определению, а не просто какая-то "ссылка на объект". В результате синглтон отличается от ссылки на функцию чуть менее чем полностью.
И само собой, функция может при каждом вызове возвращать ссылки на разные объекты.


Не понял про «подменяется», поскольку синглетон можно передавать в функцию и явно, через параметры

Вообще-то нельзя, точнее, бесполезно. Ибо помимо передачи через параметры вам придется запретить использование глобальной точки доступа к синглтону (по крайней мере там, куда передаете через параметр), иначе у подмены будет немного странный вкус (и запах).
А передача через параметр без глобальной точки доступа это уже не синглтон, а DI.

Вы говорите «У синглтона есть глобальная точка доступа по определению, а не просто какая-то ссылка на объект». Не понимаю, что это значит, и что из этого следует, ведь ссылка на статический метод (или если хотите идентификатор статического метода, или идентификатор ссылки на метод) это тоже «глобальная точка доступа», да и не статический метод это тоже «глобальная точка доступа» (напр. в таблице виртуальных методов).

Но в результате надеюсь я вас понял, вы против неявных параметров и за DI. Однако и тут мне не понятно противопоставление синглетона и DI, ведь любой IoC контейнер (т.е. реализация DI) можно сконфигурировать на возвращение синглетона.

Не подумайте что я цепляюсь. Просто я стал понимать синглетон диалектически («нет ничего неизменяемого, но когда всё кругом переменные — очень не уютно»). И потому категорические суждения мне интересны, как возможность проверить собственную правоту.

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

Ссылка на статический (и экземплярный тоже) метод — это указатель на функцию или любой его эквивалент. Ее можно передать как параметр. И что именно будет вызываться по этой ссылке, определяется тем, кто передал параметр — т.е. это не глобальная точка доступа, а разрешенные кем-то другим зависимости.
Глобальная точка доступа к синглтону — это возможность достать его откуда угодно, просто написав SomeSingleton.GetInstance()
Эта точка доступа прямо прописана в определении паттерна "Синглтон" у банды четырех. И без запрета ее использования ни о какой подмене синглтона на что-то свое речь идти не может по определению. А с запретом — синглтона уже нет, тоже по определению.


Однако и тут мне не понятно противопоставление синглетона и DI

Синглтон самостоятельно ограничивает количество своих экземпляров.
В DI количество экземпляров классов-компонентов ограничивает специальный объект: точка сборки (Composition Root)
Как результат паттерн сигнлтон и DI несовместимы: чтобы использовать одно, придется отказаться от другого (в одном и том же контексте).


ведь любой IoC контейнер (т.е. реализация DI) можно сконфигурировать на возвращение синглетона

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


Просто я стал понимать синглетон диалектически

А тут нечего понимать диалектически — это достаточно простая штука с однозначным определением.


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

Вы пишите: «цикл жизни „синглтон“ — не путать с паттерном!»… Такая позиция — позиция глухая. Одна сторона будет и далее использовать немутабильные, объекты/функции с циклом жизни синглтон (иногда даже давая им идентификаторы глобальной видимости времени компиляции), и называть их «синглтонами», а вы будете критиковать свои другие «синглтоны» (данные в определениях «банды четырех») и стороны не сойдутся.
Одна сторона будет и далее использовать немутабильные, объекты/функции с циклом жизни синглтон (иногда даже давая им идентификаторы глобальной видимости времени компиляции), и называть их «синглтонами»,

В обсуждаемой статье предмет указан явно:


Синглтон — это шаблон проектирования в разработке программного обеспечения, описанный в книге Design Patterns: Elements of Reusable Object-Oriented Software (авторы — Банда четырёх), благодаря которой о шаблонах проектирования заговорили как об инструменте разработки ПО.

Так что "одной стороне" достаточно уметь и желать прочитать ровно то, что написано.

Да как бы это ссылка на источник, а не определение. И определение у GoF если я правильно понял дается через описание. А в таком случае будут точки зрения что «плохой» синглетон в описании GoF является частным случаем «приличного» немутабильного, объекта/функции с циклом жизни «синглтон», доступного через глобальную область видимости во время компиляции (для удобства).
Если ваш синглтон можно назвать антипаттерном, значит, вы неправильно применили синглтон или выбрали не тот паттерн. Проблема именно в том, что изучив синглтон, «программист» ставит галочку «паттерны» в резюме и начинает лепить синглтон везде, даже там, где ему не место. Возможно, стоит сказать «спасибо» HR'ам за то, что первый вопрос на собеседовании: «Что такое синглтон?».

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

>В данной статье я попытаюсь раскрыть тему того, почему синглтоны никогда не должны использоваться в коде ТАКОГО ТО ЯЗЫКА ДЛЯ РЕШЕНИЯ ТАКИХ ТО ПРОБЛЕМ и какие есть альтернативы для решения ЭТИХ проблем.

исправите на это — и да, у статьи появится смысл. В противном случае возникает вопрос — если по логике приложения у нее ДОЛЖЕН быть один глобальный объект — то что? Принципиально лепить его не синглтоном? Менять на логику в которой не нужен такой объект? Зачем?

Простейший пример — в java enum реализованы через классы-синглтоны. Убрать такую реализацию? Зачем? Оно работает, быстро, шустро, с огоньком. Отказаться от перечислений и изменить синтаксис языка на такой в котором перечисления не нужны? Реализовать перечисления по другому? Зачем? Из принципа? Хорошо, тогда как? Если синглтон нехорош, то как реализовать без синглтона что бы в итоге было так же быстро и безопасно?

ЛЮБОЙ из распространенных паттернов — это квинтэссенция чьего то s/кода/опыта/, и когда мы говорим об антипаттернах — надо понимать что это чей то опыт решения каких то задач и если мы считаем это антипаттерном, значит наши задачи другие, значит этот опыт нам не подходит. Но для этого надо видеть не только свой опыт и задачи но и опыт с задачами людей притащивших этот паттерн.

Всё верно. Проблемы появляются тогда, когда программист делает синглтоном сущность, которая, вообще говоря, может быть не единственной.


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


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

Интересно, как автор подошёл к критике использования синглтона с немного другой стороны.

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

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

Это сильно завязано на особенности того или иного языка. А синглтон как паттерн — «интернационален».

Это очень плохая статья.


Можно дом простроить без молотка, но зачем? Синглтон — это точно такой же инструмент проектирование, как и другие. Если его использовать неправильно — будет нехорошо. Ну так это как молотком по пальцу заехать в первый раз и после этого обвинить инструмент в том, что это он такой плохой и лучше его никогда не использовать.


Аргумент про SOLID не особо уместен, как уже написали выше. Про разные источники данных — в моделе я вообще не хочу знать, откуда у меня берутся данные, persistence разруливается на более низком уровне, поэтому это неудачный аргумент, если предположить, что у проекта нормальная архитектура. С тестированием проблема выглядит сильно надуманной, по крайней мере у нас нет проблем с тестированием синглтонов. Может быть это потому, что мы их проектируем соответствующе.


То есть получается, что в самом синглтоне ничего плохого нет, но если этот паттерн использовать неверно, то можно усложнить себе жизнь, но ведь это справедливо для всего чего угодно. Заменить сингтон фабрикой в общем случае звучит как шизофрения.


Не хочется вешать ярлыки, но разработчик WordPress этой статьей лишний раз подтвердил, что там все очень плохо :/

Ну в общем-то автор пишет явно на определенную тематику (WP) и соответственно про определенный язык и определенные с этим всем причины.
Хм… стандартный шаблон препарирования любого… гхм… шаблона проектирования в ООП:-)
1. Выбираем любой рэндомный паттерн.
2. Обнаруживаем, что он нарушает все возможные принципы ООП
3. Пытаемся подобрать альтернативу
4. Обнаруживаем что альтернатива нарушает все возможные принципы ООП
5. Повторяем до тех пор пока не обнаружим серебряную пулю…

Вообще наверное уже давно пора смирится с мыслью, шо все эти ваши ООП — просто удачная мистификация, а SOLID — просто неудачная сферовакуумная академическая абстракция натянутая Маритном на глобус. Не, невареное на рубеже 60-70 годов это действительно было революционно и окрыляющее на фон Алгола и Фортрана, но в академ-среде уже давно разобрались что к чему и забыли, после чего продолжили дальше писать свои STL и прочие BLAS, а вот прикладные программисты вот уже 30 лет продолжают жрать кактус, колоться, плакать, но кактус не бросают. Забавно еще и то, что в итоге все равно получается код такой же фортрановский (ох ты же приподобный Котэ, первый язык программирования круче ассемблера), разве что почти всю математику выкинули — ибо на курсах двухнелельных погромистов некогда этим заниматься и перфокарты можно увидеть разве что в музее.
А это уже (приводя аналогию с молотком выше) — ровно также, как если при строительстве дома использовать не победитовые свёрла, а камень и заточенный пруток. Зачем использовать то, что уже давно неэффективно(!) в данном моменте времени. Любой инструмент приносит свой профит на данном этапе развития… и нелогично использовать инструменты из прошлого, строя в настоящем дом. Меняются не только инструменты строительства, но и технология домов.

По поводу синглтонов — уверен, что использовать этот паттерн нужно. Но подходить к нему с умом и понимать, когда и где его лучше применять. И никто не вправе указать вам СТРОГОЕ использование того или иного паттерна… Ровно как и того или иного инструмента при строительстве дома. Напомню, что паттерн — это всего лишь выделенный в границах шаблон, который повторяется у многих программистов для решения определённой задачи. Более того, большинство программистов, не знающих паттерны — используют их (или придут к их использованию) зачастую не зная, как они называются.
Вот только Service Locator — это тоже антипаттерн и тоже синглтон со всеми его проблемами. Шел 2017 год…
DI-контейнеры полностью заменяют синглтоны, фабрики и локаторы
Статья хорошая, жаль не все понятно.
Что можно почитать, что бы подтянуть свои знания в шаблонах проектирования?

Странно, что так много комментирующих столь яро критикуют статью. Статья наглядно демонстрирует, что способов избежать использования синглтона более чем полно, особенно если заранее продумать всю архитектуру компонентов.
Хотелось бы сказать в защиту статьи, что синглтон считается антипаттерном не потому, что он ломает SOLID или ещё какие принципы, не дающие спать занудам-перфекционистам, а потому что даёт возможность обращаться к этому единичному инстансу из любой точки кода. Это и создаёт неприятные моменты, как минимум в коде начинающих программистов: вместо минимизации обращений к объекту через метод MySingleton::getInstance() и передачи MySingleton $instance в качестве параметра в востребованный конструктор/метод, его сразу вызывают напрямую из любого места в коде, ведь так можно — это вроде как статика, да и паттерн это позволяет делать, — отсюда и вытекают основные сложности с тестированием, с тем, что это трудно "замокать"/подменить.

Проблема статьи — в том, что именно этот аргумент в ней так и не был высказан.

Sign up to leave a comment.