Comments 98
Плюсую вообще; а статься действительно интересная, я сейчас студента своего заставлю читать:)
вот так спрашиваешь без задних мыслей, а тебе карму спускают… разве это честно? не понимаю я людей, как буд-то обидел кого-то
В 90% переводов найдется человек, попросивший ссылку на первоисточник.
А однообразные комментарии раздражают.
Есть отличная книга по паттернам с отличным описанием и аналогиями. Не сочтите за рекламу.

Совсем недавно по мотивам этой книги создал репозиторий с имплементацией базовых паттернов (не все, что описаны в книге, только на сколько хватило энтузиазма) на php. Не сочтите за рекламу 2.
Если уж речь идет о паттернах с человеческим лицом, то «человечнее» лица, чем у Фрименов, сложно придумать. Труд Банды Четырех — это все же больше справочник.
А вот же репозиторий с паттернами на PHP https://github.com/domnikl/DesignPatternsPHP
Пример на Python
"""
Define a represention for a grammar of the given language along with an
interpreter that uses the representation to interpret sentences in the
language.
"""

import abc


class AbstractExpression(metaclass=abc.ABCMeta):
    """
    Declare an abstract Interpret operation that is common to all nodes
    in the abstract syntax tree.
    """

    @abc.abstractmethod
    def interpret(self):
        pass


class NonterminalExpression(AbstractExpression):
    """
    Implement an Interpret operation for nonterminal symbols in the grammar.
    """

    def __init__(self, expression):
        self._expression = expression

    def interpret(self):
        self._expression.interpret()


class TerminalExpression(AbstractExpression):
    """
    Implement an Interpret operation associated with terminal symbols in
    the grammar.
    """

    def interpret(self):
        pass


def main():
    abstract_syntax_tree = NonterminalExpression(TerminalExpression())
    abstract_syntax_tree.interpret()


if __name__ == "__main__":
    main()



да, шаблонов заслуживающих внимания много, но здесь речь идет о шаблонах GOF. Я не считаю важным паттерн Interpreter посто он входит в список от GOF. Поэтому суть вопроса в том, что если описывают шаблоны GOF тогда почему пропустили Interpreter?
А почему именно PHP? Хочу такую же для Python c использованием особенностей языка)
Добавим теперь дикую собаку WildDog, на которую охотник тоже может охотиться. Но у нас не получится сделать это напрямую, потому что у собаки другой интерфейс. Чтобы она стала совместима с охотником, нужно создать подходящий адаптер.
// Адаптер вокруг собаки сделает её совместимой с охотником

Адаптер для собаки..) Прям очень улыбнуло)
Было бы прекрасно добавить еще и английские(оригинальные) варианты названий шаблонов проектирования, чтобы при чтении зарубежной литературы и статей не гадать, что к чему. Не всегда они переводятся однозначно с русского языка на английский, что может вызвать диссонанс. Например «Flyweight» — так сразу и не скажешь, что это «Приспособленец».
Во-во, я тоже долго пытался понять, что за «приспособленец» такой. Что за надмозг переводил этот термин (претензия не к переводчику статьи, в вики то же самое)? К чему этот приспособленец присособляется, если это вообще неизменяемый (immutable) объёкт? Почему нельзя было взять прямой и адекватный перевод «легковес»? Flyweight — это название весовой категории в боксе, а как паттерн означает именно максимально облегчённый и дешёвый объект.
В дополнение к статье – https://github.com/domnikl/DesignPatternsPHP. Отличный сборник шаблонов с примерами, тестами и иллюстрациями.
Хоть эта фраза про «любой язык» даже в предисловии упоминается, но, надо признать, примеры слишком специфичны для .NET. Особенно лирические отступления. Потому что книга Теплякова (а она хороша, спору нет) — это не обучающий материал и даже не справочник, а дискуссия на тему особенностей реализации классических паттернов на C#, что весьма ограничивает аудиторию.

Шаблоны мне не нравятся тем, что их всегда подают под неправильным соусом. Лично я не согласен с:


Шаблоны проектирования — это способ решения периодически возникающих проблем. Точнее, это руководства по решению конкретных проблем

Шаблоны проектирования — это не способ решения проблем, это способ записи решения проблемы на объектно-ориентированном языке программирования в некотором устоявшемся виде. Оформление кода в соответствии с шаблоном, а не использование велосипеда, делает код более структурированным и читаемым, только и всего.

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

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

UFO landed and left these words here

А что в данном случае значит "написать реализацию фасада"? Самому придумать пару классов и завернуть их в класс-фасад? Или что-то другое?

UFO landed and left these words here

Прикольно, и ведь выбрали один из самых абстрактных шаблонов. Видно совсем у народа вера в кандидатов подточена.

А как вам мода — напишите на листочке реализацию фасада, на собеседованиях

А это результат того, что в резюме пишут: «знаю Design Patterns», а потом не могут назвать ни одного примера (хоть название)
UFO landed and left these words here

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

UFO landed and left these words here

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

напишите на листочке реализацию фасада, на собеседованиях?

сразу уточню что подразумевается. Вдруг интервьювер слишком много писал на ларавели и у нас различается понимание этого слова.


А так — я чаще просто спрашиваю чем адаптер от декоратора отличается. Бывает весьма интересно послушать. В целом мало кто пытается строить связь даже между названием паттерна и его предназначением… просто заучивают. Грустно. При том что пользы от этого ноль.

UFO landed and left these words here

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

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

Многие паттерны созданы для преодоления проблем ООП, которых в, например, ФП просто нет. Более того, некоторые паттерны созданы для решения проблем исключительно статически типизируемых ООП языков и в языках типа PHP, Ruby, Python и JS решаются нативно в рантайме.

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

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


В примере же, приведенном в статье, класс Organization не реализует интерфейс Employee — таким образом, он не является композитом в том смысле, который закладывается в данном шаблоне.


Более правильный пример был бы что-то вроде:


interface Assignee {
  public function canHandleTask($task): bool;
  public function takeTask($task);
}

class Employee implements Assignee {
  // реализуем методы интерфейса
}

class Team implements Assignee {
  /** @var Assignee[] */
  private $assignees;

  // вспомогательные методы для управления композитом:
  public function add($assignee);
  public function remove($assignee);

  // метода интерфейса Employee

  public function canHandleTask($task): bool {
    foreach ($this->assignees as $assignee) if ($assignee->canHandleTask($task)) return true;
    return false;
  }
  public function takeTask($task) {
    // может быть разная имплементация - допустим, некоторые задания требуют нескольких человек из команды одновременно
    // в простейшем случае берем первого незанятого работника среди this->assignees
    $assignee = ...;
    $assignee->takeTask($task);
  }
}

// Использование:

class TaskManager {
  private $assignees;
  public function performTask($task) {
    foreach ($this->assignees as $assignee) {
       if ($assignee->canHandleTask($task)) {
         $assignee->takeTask($task);
         return;
       }
    }

    throw new Exception('Cannot handle the task - please hire more people');
  }
}

$employee1 = new Employee();
$employee2 = new Employee();
$employee3 = new Employee();
$employee4 = new Employee();
$team1 = new Team([$employee3, $employee4);

// ВНИМАНИЕ: передаем команду в taskManager как единый композит.
// Сам taskManager не знает, что это команда и работает с ней без модификации своей логики.
$taskManager = new TaskManager([$employee1, $employee2, $team1]);
$taskManager->preformTask($task);
Тоже заметил данную неточность. Еще по моему мнению пример декоратора также не верен, так как не показывает основное отличие декоратора и прокси, а именно добавление нового поведения/функционал к объекту. В примере показан обычный прокси. Классический пример декоратора div/table renderer для элементов формы имеет более «человеческое» лицо
кстати хороший пример composite это symfony form компонент, там как раз отдельный элемент формы и форма реализуют один интерфейс (методы setData, submit etc.)
Аналогично с состоянием. Вместо этого у автора пример стратегии. ИМХО, в качестве хорошего примера подойдет что-то детерминированное. Например, нам в университете замечательно иллюстрировали состояние на примере телефона:

// интерфейс-состояние
interface IPhoneState {
    pickUp():IPhoneState;
    hangUp():IPhoneState;
    dial():IPhoneState;
}

// несколько реализаций
class PhoneStateIdle implements IPhoneState {
    pickUp():IPhoneState {
        return new PhoneStatePickedUp();
    }
    hangUp():IPhoneState {
        throw new Exception("already idle");
    }
    dial():IPhoneState {
        throw new Exception("unable to dial in idle state");
    }
}

class PhoneStatePickedUp implements IPhoneState {
    pickUp():IPhoneState {
        throw new Exception("already picked up");
    }
    hangUp():IPhoneState {
        return new PhoneStateIdle();
    }
    dial():IPhoneState {
        return new PhoneStateCalling();
    }
}

class PhoneStateCalling implements IPhoneState {
    pickUp():IPhoneState {
        throw new Exception("already picked up");
    }
    hangUp():IPhoneState {
        return new PhoneStateIdle();
    }
    dial():IPhoneState {
        throw new Exception("already dialing");
    }
}

// автомат
class Phone {
    private IPhoneState state;

    constructor() {
        this.state = new PhoneStateIdle();
    }
    pickUp():void {
        this.state = this.state.pickUp();
    }
    hangUp():void {
        this.state = this.state.hangUp();
    }
    dial():void {
        this.state = this.state.dial();
    }
}


Лично меня особенно восхищает этот шаблон за его умение организовать ветвящуюся логику без if'ов.
Для .NET разработчиков рекомендую отличную книгу Design Patterns via C#. Научит правильно подбирать и использовать шаблоны проектирования, описанные в классическом труде «Приемы объектно-ориентированного проектирования, авторами которого являются Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес переписанную для платформы .Net.

Вобщем-то да. Это далеко не лучшее чтиво на данную тему на сегодняшний день.

А почему бы и нет?
Посмотрите на статистику этой статьи.
Пипл до сих пор хавает пересказ книги начала нулевых с ухудшениями от Рабиновичей, а мыло готово кормить этим досыта.
И пофиг, что синглтон — антипаттерн, что создающие паттерны давным-давно реализованы в DI-контейнерах, что State — это тихий ужас, что компоновщик в статье перевран и т.д. и т.п.

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

Синглтон — антипаттерн по построению.
Он имеет две ответственности: делает что-то полезное и контролирует количество собственных экземпляров.
Это нарушение SRP с особым цинизмом.
На практике это выливается в многослойный геморрой:


  1. Приходится наследоваться от специального класса или копипастить
  2. Тестирование синглтона затруднено
  3. Тестирование синглтона с зависимостями от других синглтонов еще сложнее
  4. Когда становится нужно иметь два экземпляра класса-синглтона — у программиста большие проблемы

Самое забавное, что правильное решение тривиально — достаточно, чтобы количество экземпляров контролировал отдельный объект. Причем его даже не надо писать самому — полно готовых реализаций, от Lazy до DI-контейнеров.
А синглтону место в параде антипаттернов, как безусловно вредному, но крайне распространенному и живучему решению.

Когда становится нужно иметь два экземпляра класса-синглтона — у программиста большие проблемы

зачем иметь два экземпляра класса СИНГЛтона? это не проблема паттерна, это проблема того, кто хочет «два экземпляра класса-синглтона». Это тоже самое, если бы Вы купили шоколадное мороженое, а потом сказали бы «ну блин, я же хочу ванильное. с этим мороженым что-то не так»

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

Никто не спорит, что требования меняются. Повторюсь, в Вашем примере проблема не в паттерне, а в том, что Вы его применили не там, где надо. Требования поменялись и он Вам больше не подходит. Но это Ваша проблема, так как Вы не подумали наперед и применили неподходящий паттерн. Если Вам завтра скажут «перепишите все с С++ на Rust», Вы будете говорить, что С++ плохой и вообще антиязык?

Я не спорю, что синглтон атипаттерн и что его функционал (слежение за количеством экземпляров) реализован в DI-контейнерах, но Вы приводите смешные примеры.

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

Это проблема нарушения SRP паттерном. Никому (наверное) не нужно два синглтона, но бывает становится нужно два и более соединения к СУБД или иному серверу, два и более экземпляра конфигов, два и более экземпляра профиля пользователя и т. п., но по коду уже везде разбросаны вызовы SomeSingleton::getInstance() даже не из требования строго ограничить количество экземпляров одним, а просто потому что так удобно и экономятся ресурсы.

SomeSingleton::getInstance() даже не из требования строго ограничить количество экземпляров одним, а просто потому что так удобно и экономятся ресурсы.


Ну адекватная IDE должна помочь с рефакторингом. Не такая уж это и проблема. Проблема в том где вызывается это гет инстанс. Eсли гдето в дебрях бизнес логики потому что так быстро и удобно, то это банальный говнокод и неявные зависимости. Я не защищаю синглтон, но в конце концов, это просто паттерн. Его можно использовать там, где это к месту.

Так внедрение неявных зависимостей с тяжелой инициализацией и есть основной способ применения синглтона де-факто. Просто замена многочисленных new Class() на Class::getInstance. Если бы его применяли где-то в самом начале стэка вызовов, а потом передавали параметрами, то может он бы антипаттерном и не считался, вот только смысла бы в нём было мало, а так смысл в том, что он доступен глобально.

Смысл в нем то, что будет 1) гарантировано будет создан только один объект. 2) создан он будет лениво в момент первого обращения. То что он доступен глобально лишь побочный ефект. В абстрактном языке без статических методов тоже можно реализовать синглтон, но он не будет доступен глобально.

Глобальная доступность — свойство синглтона по определению.

Да ну? И где это написано? Он вносит глобальное состояние в приложение, это да. Но требование глобальной доступности везде — это я впервые слышу. Основное требование — это создание одного инстанса. Именно поэтому он синглтон, а не глобалтон какойто

Ну открываем определение сингелтона и читаем требования к реализации:


An implementation of the singleton pattern must:
  • ensure that only one instance of the singleton class ever exists; and
  • provide global access to that instance.

последний пункт как раз про это


Именно поэтому он синглтон, а не глобалтон какойто

глобальный доступ это не цель, это деталь реализации.

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


Если вдруг нужно 2 инстанса — не вопрос, убираем синглтон и делаем какую-то обертку, но ведь рефакторинг под изменившиеся требования это ок. Или вы всегда предусматриваете ВСЕ возможные изменения в будущем во ВСЕХ местах? Звучит как оверинжинеринг.

и себя конфигурирует, и http-запросы посылает? Вот негодяй!

поэтому придумали фабрики.


Хотя в целом я с вами согласен.

Класс может таким образом защищаться от неправильного использования.

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


С тем же успехом можно и конструктор называть "второй ответственностью", что же, у класс и себя конфигурирует, и http-запросы посылает?

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


Синглтон это такой подвид конструктора, который не принимает параметров.

Это прямо противоречит определению от "банды четырех".


Если вдруг нужно 2 инстанса — не вопрос, убираем синглтон и делаем какую-то обертку, но ведь рефакторинг под изменившиеся требования это ок.

Рефакторинг, который появляется исключительно от использования синглтона — это совсем другое слово: "Антипаттерн".


Звучит как оверинжинеринг.

С точностью до наоборот. Перепроектирование — это добавление к классу вредного кода, контролирующего число его экземпляров.
Альтернатива — одна строка кода (при использовании Lazy) или один дополнительный вызов (при использовании DI-контейнера) или вообще бесплатно (если регистрация в контейнере ограничивает число экземпляров по умолчанию).

Представьте что вам нужно написать библиотеку для работы с COM портом. Доступ к нему должен быть строго у одного инстанса на процесс. Предложите способ организовать это так, что бы клиентскому коду небыло проблем? Я вижу один — сингелтон. Просто и железобетонно. А недостатки вроде "глобального доступа" можно скрыть с клиентской стороны добавив в тот же контейнер. Но зато мы с нимем с разработчика, который будет использовать нашу библиотеку риски того, что он "случайно" может сделать что-то не то.


Но таких кейсов 1 на тысячу. В большинстве же случаев сингелтон лепят просто так.

Ровно один ком-порт на машину? Ну-ну.
Добавив контейнер, синглтон можно выкинуть сразу.

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

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

библиотека не должна принуждать клиента к определенной композиции.

В тоже время вы именно этот вариант и предлагаете.


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

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

А у меня с точностью до наоборот мнение.


Рефакторинг, который появляется исключительно от использования синглтона — это совсем другое слово: "Антипаттерн".

Рефакторинг, который появляется от изменения требований.


С точностью до наоборот. Перепроектирование — это добавление к классу вредного кода, контролирующего число его экземпляров.
Альтернатива — одна строка кода (при использовании Lazy) или один дополнительный вызов (при использовании DI-контейнера) или вообще бесплатно (если регистрация в контейнере ограничивает число экземпляров по умолчанию).

Надо полагать, что если DI в проекте, нет, то авторы — лохи, которым нужно ткнуть в нос SOLID и пинать, пока они не поймут, что без IoC жизни нет.

А у меня с точностью до наоборот мнение.

Отсюда и понятие "антипаттерн": несмотря на объективный вред, у многих другое субъективное мнение.


Рефакторинг, который появляется от изменения требований.

Без синглтона этот рефакторинг не нужен и после изменения требований.


Надо полагать, что если DI в проекте, нет, то авторы — лохи, которым нужно ткнуть в нос SOLID и пинать, пока они не поймут, что без IoC жизни нет.

Проект от среднего размера без DI (не контейнера, а паттерна) — либо легаси, либо авторы таки да.

либо легаси, либо авторы таки да.

ушел жалеть бедалаг которые страдают с python/javascript/ruby… Или вы все же имели ввиду IoC и не паттерн на принцип. Ну там всякие don't call us, we call you.

DI как паттерн это всего лишь замена композиции агрегацией. В питоне, яваскрипте и руби все это есть.

Теги: "Никто не читает теги" — порадовало
А статья — весьма монументальна, приятно видеть

кому они нужны после прочтения статьи? (а до прочтения их хрен найдёшь)
Вот, кстати, вопрос: зачем вообще читать теги кому-то, кроме автора? Они же не для почитать, а для поиска и упорядочивания по ключевым словам. Совершенно логично, что «никто не читает теги». Зато по этому тегу можно найти сразу весь список «оригинально» пошутивших авторов ;)

В PHP с версии 5.4 имеются первоклассные функции, почему они не применяются здесь?

Спасибо за статью. Проще, чем описаны паттерны тут refactoring.guru еще не видел. К тому же, примеры на псевдокоде + диаграммы, на русском.
Чем объясняется повсеместное применение реализаций интерфейсов вместо наследования? Взять тот же пример шаблона «мост»:
interface WebPage
{
    public function __construct(Theme $theme);
    public function getContent();
}

Конструктор как элемент интерфейса, серьёзно? И почему бы не использовать наследование от абстрактного класса, например:

abstract class AbstractWebPage
{
    protected $theme;

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

    abstract public function getContent();
}


class About extends AbstractWebPage
{
    public function getContent()
    {
        return "About page in " . $this->theme->getColor();
    }
}

class Careers extends AbstractWebPage
{
    public function getContent()
    {
        return "Careers page in " . $this->theme->getColor();
    }
}
Причем в данном случае как раз About и Careers «is-a» а не «has-a» WebPage. Потому extends вполне подходит
Чем объясняется повсеместное применение реализаций интерфейсов вместо наследования?

Видимо, следованием принципов SOLID. Но конкретный пример действительно ужасен: если уж следовать паттернам и делать по-человечески, то интерфейс должен называться IWebContentProvider, а не WebPage, а конструирование вынесено в отдельную фабрику.


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

Видимо, следованием принципов SOLID

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


Вообще на тему implemets vs extends есть неплохая статья: Why extends is evil


то интерфейс должен называться IWebContentProvider, а не WebPage

скорее просто ContentProvider. Нам же в целом только это важно.

Чем объясняется повсеместное применение реализаций интерфейсов вместо наследования?

1. Строка public function __construct(Theme $theme); в интерфейсе, еще не повод ставить под сомнение повсеместное применение интерфейсов. Это все равно, если запретить спички, только потому, что один ребенок обжег ими палец. Или поставить под вопрос использование автомобилей, ведь за их рулем часто оказываются нетрезвые люди. Просто, нужно правильно использовать возможности языка, а не писать определение конструктора в интерфейсе, только лишь потому, что язык этого не запрещает.

2. Почему же их все-таки нужно использовать и желательно почаще?
Не будем далеко ходить и возьмем ваш пример. Мы имеем один абстрактный класс и два класса реализующие его. Допустим, у нас есть некий клиентский класс, который обращается к getContent().
class Client 
{
    private $page;

    /**
     * Client constructor.
     *
     * @param $page
     */
    public function __construct(AbstractWebPage $page)
    {
        $this->page = $page;
    }

    public function showPage()
    {
        return $this->page->getContent();
    }
}

Таким образом мы научили наш клиент работать только с AbstractWebPage. Что, если мы хотим добиться от клиента работы не только с AbstractWebPage типом но и другими похожими типами? Стоит отметить, клиенту важно только одно, это метод getContent(), все что он должен знать о типе AbstractWebPage.
Тут вспоминается пример из жизни, когда обезьянку научили приносить воду в ведре. Та ходила по искусственной дорожке, где в конце был небольшой резервуар с водой. Позже, к этим условиям добавили бассейн с водой, однако обезьянка продолжила ходить за водой по привычному маршруту, тогда как бассейн с водой был намного ближе.
Также и тут, в нашем примере, мы словно учим обезьяну. Мы говорим ей вместо «воды» «принести», «воооон там, есть дорога, ты должна пройти по ней и только по ней, а в конце дороги будет резервуар и из него набрать воды» «принести»

Возвращаясь к нашему примеру. С помощью интерфейса мы можем определить более абстрактно тип страницы PageInterface. И любой класс реализующий данный тип будет обязан описать логику для метода getContent()

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

В итоге у нас может быть такое решение, когда типов страниц может быть больше одного, а клиент не хочет брать на себя ответственность за их обработку. Он знает только о getContent и что на входе у него уже есть какая то реализация типа PageInterface.
abstract class AbstractWebPage implements PageInterface
{
    protected $theme;

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


class About extends AbstractWebPage
{
    public function getContent()
    {
        return "About page in ".$this->theme->getStyle();
    }
}

abstract class AbstractPdfPage implements PageInterface
{
    protected $theme;

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

class Cv extends AbstractPdfPage
{
    public function getContent()
    {
        return "Cv page in ".$this->theme->getStyle();
    }
}

class Client 
{
    private $page;

    /**
     * Client constructor.
     *
     * @param $page
     */
    public function __construct(PageInterface $page)
    {
        $this->page = $page;
    }

    /**
     * @return PageInterface
     */
    public function showPage(): PageInterface
    {
        return $this->page->getContent();
    }
}

Полностью согласен с вами. Более того, изначально хотел дописать, что при необходимости дополнительной абстракции, можно вынести метод getContent() в интерфейс и указать его реализацию в абстрактном классе (как у вас в примере).
Сложилось впечатление, что в понимании автора (оригинальной статьи) интерфейс — это представление некой абстрактной сущности, а не её поведения (даже судя просто по названиям интерфейсов).
У меня в закладках есть папочка «on hand», эта статья должна лежать именно там. Спасибо!
Только вас, так итератору не нужно явное позиционирование.

Но почему вы молчали так долго? :)
так как прочел так и написал )
Только вас, так итератору не нужно явное позиционирование.
не совсем понял, я туплю или чего-то не знаю о итераторах…
public function current(): RadioStation
    {
        return $this->stations[$this->counter]; //$this->counter по умолчанию пустая
    }
Да, так и должно быть. На пустой коллекции или при достижении конца текущий элемент — null или его эквивалент.
Но надо учитывать, что тут итератор сам по себе… нестандартный. Обычной имеется ввиду отдельный объект, позволяющий последовательно перебрать все элементы в исходной коллекции. И ничего кроме Current, Next и Reset не должно быть в итераторе.
это я в курсе, но неопределенный элемент при вызове в данном коде просто выдаст варнинг, не думаю что это хороший вариант использования. Либо он должен быть задан при добавлении первого элемента в $this->stations, либо иметь явный сеттер. То же относится к
public function key()
    {
        return $this->counter;
    }

но тут она просто NULL, что не так страшно.
Only those users with full accounts are able to leave comments. Log in, please.

Information

Founded
Location
Россия
Website
team.mail.ru
Employees
5,001–10,000 employees
Registered

Habr blog