Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Стратегия (Паттерн) PHP: Полиморфизм

Закрепим пройденную теорию на одном практическом примере, показывающем типичное применение полиморфизма подтипов.

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

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

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

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


<?php

// Примеры с потолка
if ($age < 18) {
  $cost = $salary * $age;

  if ($country === 'uganda') {
    $cost = $cost / 2;
  }
} else if ($age >= 18 && $age < 24) {
    // ...
}

Можно ли как-то сделать код понятнее и проще для восприятия? Иногда да. Из всех факторов, участвующих в расчёте, нужно попытаться найти те, которые влияют на вычисление глобально. Они проявляются как глобальный if на верхнем уровне. Предположим, что в случае страховки это возраст. То есть считаем, что возраст определяет формулу расчёта стоимости страховки. Следующим шагом смотрим на ветки в этой условной конструкции и то, какие диапазоны там указаны. Допустим такую картину:

  • До 18
  • От 18 до 24
  • От 24 до 65
  • Старше 65

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

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

<?php

interface CostInsuranceCalculation
{
    public function calculate($params);
}

class LessThan18 implements CostInsuranceCalculation
{
    // Параметры, это те самые факторы, по которым строится вычисление
    public function calculate($params)
    {
        // тут считаем и возвращаем результат
    }
}

// Имя конечно так себе, в реальном коде стоит придумать что-нибудь говорящее
class MoreThan18AndLessThan24 implements CostInsuranceCalculation
{
    // Структура параметров должна 100% совпадать с остальными классами,
    // так как только в этом случае возможен полиморфизм
    public function calculate($params)
    {
        // тут считаем и возвращаем результат
    }
}

// Остальные классы

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

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


<?php

function calculateCost($strategy, $params)
{
    $strategy->calculate($params);
}

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

<?php

function chooseCostInsuranceStrategy($user)
{
    if ($user->getAge() < 18) {
        return new LessThan18();
    } else if (/* ... */) {
        // some code
    }
}

$strategy = chooseCostInsuranceStrategy($user);
$strategy->calculate($params);

Как видно по примерам выше, кода с использованием стратегии будет больше (но не так много если используется диспетчеризация функций по ключам в ассоциативном массиве). Это касается фактически всех ситуаций, в которых задействован полиморфизм подтипов в PHP. Это цена, которую придётся заплатить за разделение, упрощающее расширение кода и уменьшающее его сложность. С другой стороны, очень легко попасть в ловушку и, наоборот, сделать сложность кода выше, чем оно было до внедрения полиморфизма подтипов. Этот полиморфизм делает код многословным и излишне абстрактным, если применять его налево и направо. А расширение нужно не так часто, как об этом говорят. Более того, инвертировать зависимости можно по ходу действия, когда в этом появляется необходимость.


Дополнительные материалы

  1. Что такое expression problem
  2. Expression Problem (англ.)

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Занимайтесь созданием сайтов, веб-приложений, сервисов и их интеграцией с внутренними бизнес-системами на бекенд-языке PHP
10 месяцев
с нуля
Старт 28 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»