понедельник, 8 мая 2017 г.

Комментарии и phpDoc. PHP 7.1

Комментарий - всегда признак неудачи
Р. Мартин "Чистый код"

Больше и лучше, чем сказано Р. Мартина в 4-й главе "Чистого кода", не скажешь. От себя добавлю: комментарии нужно поддерживать в актуальном состоянии. Код развивается, написанное в комментарии теряет свою актуальность, если комментарий не править вместе с кодом. А последнее делают далеко не всегда. Как правило из-за спешки. В одном из проектов, где я работал, видел и такое:

    // TODO: Actualize comment above

То есть с комментариями ситуация выглядит немного странно: сперва мы пишем код, требующий комментирования. Затем мы правим этот код и старые комментарии превращаются в "технический долг". Потом мы актуализируем комментарии. Если успеваем или не забываем. А не проще ли писать код, не требующий комментирования? Код, который легко читается на языке, на котором он написан?

Теперь о phpDoc-ах. По сути - особый комментарий, "новое прочтение" javaDoc. Изначальная цель - быстрое создание актуальной документации по коду. Точнее - по особым образом оформленным комментариям в коде. Но за "время жизни" phpDoc приобрёл ещё несколько дополнительных функций:

  1. Аннотации. В java они часть языка, здесь же под них приспособили phpDoc.
  2. Компенсация особенностей языка. (Типизация и т.п)

Давайте вспомним, когда вы (или в вашем проекте) последний раз генерировали документацию к коду? А когда последний раз ее читали? А когда нашли там что-то для себя полезное? Думаю, большинство ответит отрицательно как минимум на 2 из 3-х вопросов. Все по той же причине, что изложена у Р. Мартина - код является истиной в последней инстанции. И надёжней прочитать код, чем надеяться на то, что написано в комментарии. Лично я, работая в проприетарных проектах, чаще слышал: "мы будем генерировать доку", чем у кого-то доходили руки до этого. Получалось почти как в старой восточной притче: или проект умирал, так и не увидев автоматически сгенерированную документацию, или я из него уходил, так документацию и не дождавшись. То есть phpDoc ради документации в проприетарных проектах скорее миф, чем нечто реальное.

Тем не менее doc-блоки я использовал активно и регулярно. Именно ради типизации. И те, чей код я ревьювил, всегда получали замечания за отсутствие doc-блоков к методам и свойствам классов. Так было, пока не появился PHP 7, а затем и 7.1 с их типизацией аргументов и возвращаемых значений (type declaratios). Какое-то время я писал doc-блоки и в этих версиях PHP. Но при очередном рефакторинге поймал себя на том, что больше правлю doc-блоки к методам, чем пишу код. Причём doc-блоки эти не несут ничего полезного - все что нужно для понимания кода уже есть в языке. Какая польза, например, от вот такого doc-блока:

    /**
     * Sets customer full name
     *
     * @param string $fullName Full name
     *
     * @return void
     */
    public function setCustomerFullName(string $fullName): void
    {
        $this->customerFullName = $fullName;
    }

А теперь, допустим, мы решили string заменить на класс FullName. Метод теперь должен выглядеть так:

    /**
     * Sets customer full name
     *
     * @param FullName $fullName Full name
     *
     * @return void
     */
    public function setCustomerFullName(FullName $fullName): void
    {
        $this->customerFullName = $fullName;
    }

Попробуем внести необходимые изменения за один шаг при помощи PhpStorm. Нажимаем комбинацию клавиш ( Refactor->Change signature…, Command-F6 в MacOS) когда курсор стоит на названии метода, меняем тип параметра и… магии не случилось, в doc-блоке получаем:

    /**
     * Sets customer full name
     *
     * @param FullName|string $fullName Full name
     *
     * @return void
     */

Допиливаем напильником руками до нужного вида.

С типом возвращаемого значения так не получится. Refactor->Change signature… в PhpStorm на момент написания этой статьи такого не умел. То есть изменения необходимо вносить вручную в двух местах. Аналогично получается, если нужно сменить название метода или параметра - ни одна IDE их description за вас не поменяет на что-то чуть более интересное, чем преобразование имени переменной или метода в комментарий ($somePropertyName -> Some property name)

Описания (description) к классам, методам переменным, константам по сути - те же комментарии с теми же проблемами, что и обычные комментарии. Существует мнение, что description в данном случае - "декларация о намерениях программиста". В таком случае в description содержится заведомо неверная информации поскольку намерения и то, что сделано на самом деле - две большие разницы. А комментарий, не соответствующий действительности куда хуже, чем полное отсутствие комментария.

Рефакторинг одного простого метода выливается в достаточно приличный геморрой. И ради чего? Ради документации, которую никто никогда не будет генерировать? Или ради двух божественных слов Code Style? Да, соглашения по оформлению кода нужны. Но они придумываются программистами для самих себя. Значит, если code style мешает быстро писать качественный код, то code style нужно менять.

Итак, если документацию мы генерировать не будем, то phpDoc необходим для аннотаций и для компенсации "особенностей языка":

  1. Для переменных и свойств классов - обязательно.
  2. Для функций и методов. Если нужно уточнить передаваемые или возвращаемые данные, например для массива однотипных объектов. Так же, необходимо перечислить все исключения, выбрасываемые методом (здравствуй, java, снова ты).
  3. Для классов в тех случаях, если класс использует магические методы для доступа к свойствам или методам.
  4. Для констант doc-блок бессмысленен и бесполезен. Во первых тега @const нет в природе, а тег @var применительно к константе смотрится смешно и дезинформирует. Во вторых PHP самостоятельно определяет тип константы и проверяет его при передаче в метод, если включён контроль типов. И IDE несоответствие типов подсвечивает (как и любую ошибку)

/**
 * @method float cube(float $argument)
 */
class Example
{
    const CUBE_POWER = 3;

    /** @var string */
    public $property;

    /**
     * @param int[] $data
     * @return int[]
     *
     * @throws RuntimeException
     */
    public function checksEmptyData(array $data): array
    {
        if (!$data) {
            throw new RuntimeException('Data is empty');
        }

        return $data;
    }

    /**
     * @param string $name
     * @param array $arguments
     * @return float
     *
     * @throws RuntimeException
     */
    public function __call($name, $arguments)
    {
        if ($name == 'cube') {
            return pow($arguments[0], self::CUBE_POWER);
        }

        throw new RuntimeException(sprintf('Unknown method "%s"', $name));
    }
}

суббота, 25 июня 2016 г.

Принцип разделения интерфейса (Interface Segregation Principle)

Принцип разделения интерфейса (ISP), четвертый из принципов SOLID

Клиенты не должны зависеть от методов, которые они не используют.

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

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

Использование же "толстых" интерфейсов приводит к:

  • появлению множества "заглушек", и как следствие - ко множеству лишнего кода, который сложно поддерживать
  • соблазну нарушить принцип SRP, поскольку клиентский код "знает" о переданном классе, реализующем такой интерфейс, слишком много

вторник, 14 июня 2016 г.

Принцип открытости/закрытости (Open-closed principle)

Принцип открытости/закрытости (OCP), второй из принципов SOLID

Программные сущности должны быть открыты для расширения, но закрыты для модификации.

Как и принцип единственной ответственности применим не только к классам, но и к модулям, методам и т.д.

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

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

Таким образом, разрабатываемая система должна относительно просто и безболезненно меняться. То есть должна быть гибкой.

Применительно к ООП этот принцип можно реализовать двумя способами:

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

Первый как правило использует паттерн Startegy (Стратегия) для внедрения зависимостей, второй - Template method (Шаблонный метод). Оба способа правильные, каждый нужно использовать исходя из свойств соответствующих отношений.

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

class FirstLogger
{
    /**
     * @return FirstLogger
     */
    public static function me() { /* реализуем синглетон */ }

    public function log($message) { /* логируем */ }
}

class SecondLogger
{
    public function log($message) { /* логируем */ }
}

class Foo
{
    public function doSomething()
    {
        // ...
        FirstLogger::me()->log('Some log info');
        // ...
        (new SecondLogger())->log('Some log info');
        // ...
    }
}

// Использование
(new Foo())->doSomething();

Недостатки такой реализации очевидны: замена, например, классов *Logger на другой класс влечет за собой значительные изменения. Так же проблематично писать юнит-тесты для подобного кода.

Тот же код с применением принципа открытости/закрытости:

class FirstLogger
{
    /**
     * @return FirstLogger
     */
    public static function me() { /* реализуем синглетон */ }

    public function log($message) { /* логируем */ }
}

class SecondLogger
{
    public function log($message) { /* логируем */ }
}


class Foo
{
    /** @var FirstLogger */
    public $firstLogger;

    /** @var SecondLogger */
    public $secondLogger;

    public function __construct(FirstLogger $firstLogger, SecondLogger $secondLogger)
    {
        $this->firstLogger = $firstLogger;
        $this->secondLogger = $secondLogger;
    }

    public function doSomething()
    {
        // ...
        $this->firstLogger->log(('Some log info'));
        // ...
        $this->secondLogger->log('Some log info');
        // ...
    }
}

// Использование
(new Foo(FirstLogger::me(), new SecondLogger()))->doSomething();

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

Еще пример. Допустим в системе есть класс, записывающий лог в файл first_log.log:

class FirstLogger
{
    public function log($message)
    {
        $handler = fopen('first_log.log', 'w+');
        fwrite($handler, sprintf('[%s]%s%s', date('c'), $message, PHP_EOL));
        fclose($handler);
    }
}

// Использование
(new FirstLogger())->log('Some message');

В такой реализации класс полностью закрыт для расширения. Необходимость логирования в другой файл приводит или к переделке интерфейса метода log($message), так, чтобы он принимал имя файла как параметр, или написанию класса, почти полностью дублирующего данный:

class FirstLogger
{
    public function log($file, $message)
    {
        $handler = fopen($file, 'w+');
        fwrite($handler, sprintf('[%s]%s%s', date('d-m-Y H:i:s'), $message, PHP_EOL . PHP_EOL));
        fclose($handler);
    }
}

class SecondLogger
{
    public function log($message)
    {
        $handler = fopen('second_log.log', 'w+');
        fwrite($handler, sprintf('[%s]%s%s', date('d-m-Y H:i:s'), $message, PHP_EOL . PHP_EOL));
        fclose($handler);
    }
}

// Использование
(new FirstLogger())->log('second_log.log', 'Some message');
(new SecondLogger())->log('Some message');

Тот же код с применением принципа открытости/закрытости, опять через внедрение зависимостей:

class FileLogger
{
    private $logFile;

    public function __construct($logFile)
    {
        $this->logFile = $logFile;
    }

    public function log($message)
    {
        $handler = fopen($this->logFile, 'w+');
        fwrite($handler, sprintf('[%s]%s%s', date('c'), $message, PHP_EOL));
        fclose($handler);
    }
}

// Использование
(new FileLogger('first_log.log'))->log('Some message');
(new FileLogger('second_log.log'))->log('Some message');

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

abstract class AbstractLogger
{
    public function log($message)
    {
        $this->connect();
        $this->write(
            $this->formatMessage(
                $message
            )
        );
        $this->disconnect();
    }

    /**
     * @return void
     */
    abstract protected function connect();

    /**
     * @param string $message
     * @return void
     */
    abstract protected function write($message);

    /**
     * @return void
     */
    abstract protected function disconnect();

    /**
     * @param string $message
     * @return string
     */
    abstract protected function formatMessage($message);
}

class FileLogger extends AbstractLogger
{
    private $logFile;

    private $handler;

    public function __construct($logFile)
    {
        $this->logFile = $logFile;
    }

    protected function connect()
    {
        $this->handler = fopen($this->logFile, 'w+');
    }

    protected function write($message)
    {
        fwrite($this->handler, $message);
    }

    protected function disconnect()
    {
        fclose($this->handler);
    }

    protected function formatMessage($message)
    {
        return sprintf('[%s]%s%s', date('c'), $message, PHP_EOL);
    }
}

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

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

Принцип единственной ответственности (Single responsibility principle)

Принцип единственной ответственности (SRP), первый из принципов SOLID.

На каждый объект должна быть возложена одна единственная обязанность.

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

Существует ряд патологических случаев нарушения принципа единственной обязанности, которые сегодня будут очевидны практически любому:

  • Крайний - антипаттерн GodObject.
  • классы бизнес-логики, которые "знают" о пользовательском интерфейсе или о базе данных.
  • "толстые" контроллеры в шаблоне MVC. Обязанности контролера - увязать между собой модель бизнес-логики и представление. То есть, принять данные извне, передать их в модель, получить результат раболты модели и передать его в представление. Ни больше, ни меньше.

Менее очевидные нарушения есть, как ни странно, в ряде известных паттернов:

  • Singleton и его расширение - Multiton. Здесь "подмешан" контроль за количеством порождаемых объектов никакого отношения не имеющий к основным функциям, реализуемых классами на основе этих шаблонов.
  • Active Record, строго говоря, противоречит этому принципу - с одной стороны он является сущностью (entity), с другой - объектом доступа к хранилищу тех же самых сущностей (DAO). Даже если его "правильно готовить", то в нем сосредотачиваются как свойства сущности, так и атомарные методы поиска этих сущностей (ofSomeProperty(property: PropertyType)).

Разделять классы на составляющие необходимо руководствуясь здравым смыслом и моделью предметной области. В противном случае можно сделать только хуже. Один из таких примеров - антипаттерн Anemic Domain Model.

воскресенье, 12 июня 2016 г.

Принципы. SOLID.

SOLID - акроним из первых букв названий пяти принципов разработки программного обеспечения, названных Робертом Мартином в начале 2000-х.

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

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

Принцип единственной ответственности (Single responsibility principle, SRP).

На каждый объект должна быть возложена одна единственная обязанность.

Другими словами — конкретный класс должен решать конкретную задачу — ни больше, ни меньше. Не должно быть больше одной причины для изменения класса, иначе получим хрупкий дизайн: правим одно - ломаем другое. Можно применить не только к классам, но и к методам, модулям, подсистемам и системам.

Принцип открытости/закрытости (Open-closed, OCP)

Программные сущности должны быть открыты для расширения, но закрыты для модификации.

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

Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP)

Объекты в программе могут быть заменены их наследниками без изменения свойств программы.<.p>

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

Принцип разделения интерфейса (Interface Segregation Principle, ISP)

Клиенты не должны зависеть от методов, которые они не используют.

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

Принцип инверсии зависимостей (Dependency Inversion Principle, DIP)

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Другими словами: зависимости должны строится на абстракциях, а не на конкретных реализациях.

суббота, 11 июня 2016 г.

Шаблоны. Порождающие. Multiton (Мультитон, Пул одиночек)

Цель.

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

UML-диаграмма

Недостатки, Альтернативы, Резюме.

Аналогичны шаблону синглетон. Так же можно считать антипаттерном.

Реализация на PHP.

class Multiton
{
    /** The first instance key. */
    const INSTANCE_1 = '1';

    /** The second instance key. */
    const INSTANCE_2 = '2';
    
    private static $instances = array();
    
    private function __construct() { }

    public static function getFirstInstance()
    {
        return self::getInstance(self::INSTANCE_1);
    }

    public static function getSecondInstance()
    {
        return self::getInstance(self::INSTANCE_2);
    }
    
    public function doSomething() { }
    
    /**
     * @return Multiton
     */
    private static function getInstance($instanceName)
    {
        if (!array_key_exists($instanceName, self::$instances)) {
            self::$instances[$instanceName] = new self();
        }
        return self::$instances[$instanceName];
    }

    private function __clone() { } // Защищаем от создания через клонирование
    private function __wakeup() { } // Защищаем от создания через unserialize
}

// Использование
Multiton::getFirstInstance()->doSomething();
Multiton::getSecondInstance()->doSomething();

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

abstract class AbstractSingleton
{
    private static $instances = array();
    
    protected function __construct() {/*_*/}
    
    /**
     * @return static
     */
    final public static function me()
    {
        return self::getInstance(get_called_class());
    }
    
    private static function getInstance($class)
    {
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class;
        }
        return self::$instances[$class];
    }

    private function __clone() { } // Защищаем от создания через клонирование
    private function __wakeup() { } // Защищаем от создания через unserialize
}

// Использование
class Foo extends AbstractSingleton
{
    protected function __construct() { /* инициализация для конкретного наследника, если нужна */ }

    public function doSomething() { }
}

class Bar extends AbstractSingleton
{
    public function doSomethingOther() { }
}

Foo::me()->doSomething();
Bar::me()->doSomethingOther();

Нецелевое использование.

Замена ужасного кода немного лучшим на одном из этапов рефакторинга.


Известный факт: статические методы - зло. Причин на то много и сейчас не об этом. Допустим есть проект, где один из классов использует исключительно статические методы. Пусть для простоты - один метод. Проект растёт и класс этот используется в проекте все в большем и большем количестве мест. То есть, чтобы заменить его статический метод на динамический уже требуются значительные затраты. И вот в один прекрасный день выясняется, что в проекте нужен ещё один (и даже не один) класс с похожим функционалом. В случае с динамическими коассами все просто - общий код выносится в абстрактный класс и от него наследуются классы с необходимыми функциями. Но abstract static function - это нонсенс. Выхода два - или делать почти одинаковые статические классы (ужас), или превратить статические классы в динамические, оставив при этом статический вызов и гарантировать при этом, что обращение будет к единственному экземпляру класса (не зря же исходный класс был статическим - что-то этим хотели сказать авторы или чего-то боялись). В решении подобной проблемы может помочь мультитон.

Код до рефакторинга:

class Foo
{
    public static function doSomething() 
    {
        self::doCommonStep();
        self::doSpecificStep();
    }
    
    private static function doCommonStep() { /* Дублированный код */ }
    
    private static function doSpecificStep() { /* Специфичный для Foo код */ }
}

class Bar
{
    public static function doSomething()
    {
        self::doCommonStep();
        self::doSpecificStep();
    }

    private static function doCommonStep() { /* Дублированный код */ }

    private static function doSpecificStep() { /* Специфичный для Bar код */ }
}

// Использование
Foo::doSomething();
Bar::doSomething();

Код после рефакторинга:

abstract class Base
{
    private static $instances = array();

    protected function __construct() {/*_*/}

    /**
     * @return static
     */
    final public static function me()
    {
        return self::getInstance(get_called_class());
    }

    private static function getInstance($class)
    {
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class;
        }
        return self::$instances[$class];
    }
    
    public static function doSomething() 
    {
        static::me()->something();
    }
    
    private function something() 
    {
        $this->doCommonStep();
        $this->doSpecificStep();
    }

    private function doCommonStep() { /* Дублированный код */ }

    abstract protected function doSpecificStep();
}

class Foo extends Base
{
    protected function doSpecificStep() { /* Специфичный для Foo код */ }
}

class Bar extends Base
{
    protected function doSpecificStep() { /* Специфичный для Bar код */ }
}

// Использование
Foo::doSomething();
Bar::doSomething();

пятница, 10 июня 2016 г.

Шаблоны. Порождающие. Singleton (Одиночка)

Назначение.

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

Часто в системе необходимы сущности или сервисы, которые могут существовать только в единственном экземпляре, например, система логирования или подключение к базе данных. В таких случаях необходимо уметь создавать единственный экземпляр некоторого типа, предоставлять к нему доступ извне и запрещать создание нескольких экземпляров того же типа. Такой объект доступен из любого места кода и своим поведением напоминает глобальную переменную.

UML-диаграмма

Достоинства.

  • единственный экземпляр данного класса в системе

Недостатки.

  • Фактически является глобальной переменной если хранит состояние;
  • Нарушает принцип единственной ответственности (SRP). Помимо выполнения своих непосредственных функций еще и контролирует то, что существует в единственном экземпляре;
  • Снижает тестируемость клиентского кода поскольку вместо него невозможно подставить mock-объект;
  • Повышает связанность кода.

Альтернативы.

Использование шаблонов DependencyInjection или ServiceLocator. Например, фреймворк Symfony позволяет полностью обойтись без синглетона благодаря своей системе DI.

Резюме.

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

Реализация на PHP.

class Singleton
{
    private static $instance = null;  // экземпляр объекта

    private function __construct(){ /* ... @return Singleton */ }  // Защищаем от создания через new Singleton

    /**
     * @return Singleton
     */
    public static function me() // Возвращает единственный экземпляр класса.
    {
        if ( is_null(self::$instance) ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function doSomething() { }

    private function __clone()    { }  // Защищаем от создания через клонирование
    private function __wakeup()   { }  // Защищаем от создания через unserialize
}

// Использование
Singleton::me()->doSomething();

Если в проекте существует несколько синглетонов, то при такой реализации прийдется в каждом из них писать весь вышеприведенный код (кроме метода doSomething(), конечно). Дублирование кода - крайне плохая практика, пусть даже этот код скорее всего меняться никогда не будет. С другой стороны служебные конструкции, реализующие шаблон, в PHP выглядят достаточно громоздко и затрудняют чтение. Так есть ли способ (в PHP) избежать такого дублирования? Начиная с версии 5.4 этот язык получил trait - кусок кода, который можно внедрять в любой объект по своему усмотрению. Насколько хороши треиты в PHP и насколько, где и как стоит их использовать - отдельная тема. Здесь же посмотрим можно ли использовать trait для при реализации синглетонов и насколько такое использование правильно.

В ряде интернет-источников (в том числе и в википедии) приводится пример подобного кода:

trait Singleton 
{
    static private $instance = null;

    private function __construct() { /* ... @return Singleton */ }  // Защищаем от создания через new Singleton

    public static function me() 
    {
        if ( is_null(self::$instance) ) {
            self::$instance = new static();
        }
        return self::$instance;
    }
    
    private function __clone() { /* ... @return Singleton */ }  // Защищаем от создания через клонирование
    private function __wakeup() { /* ... @return Singleton */ }  // Защищаем от создания через unserialize
}

/**
  * Class Foo
  * @method static Foo me()
  */
class Foo 
{
    use Singleton;

    public function doSomething() { }
}

// Применение
Foo::me()->doSomething();

А теперь попробуем добавить в Foo публичный конструктор:

class BrokenSingleton
{
    use Singleton;

    public function __construct() { }

    public function doSomething() { }
}

(new BrokenSingleton())->doSomething();

Теперь это уже не синглетон. Замечу, что реализация без trait сделать подобное не позволяет. Это достаточно большой минус, чтобы не использовать подобную конструкцию вообще. Есть другой, более надежный и правильный способ избежать дублирования кода - шаблон мультитон. Хотя он и предназначен для другого, PHP благодаря своей нестрогой типизации позволяет использовать его в том числе и для сокращения дублирования кода.

Реализация на  Scala.

В Scala есть ключевое слово object, обозначающее, что этот класс будет создан в единственном экземпляре. Поэтому реализация singletone выглядит совсем просто:

object Singleton {}

// Или как объект-компаньон (в одном файле):
class Foo {}

object Foo {}