Статьи

Примитивная Одержимость

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

Анти-паттерн описывает «одержимость» примитивными значениями, будь то скалярные типы или даже объекты, предоставляемые платформой. Примером может служить управление положительным целым числом в языках без ключевого слова unsigned: они часто представляются как примитивные целые числа и передаются как переменные типа int или объекты типа Integer, несмотря на то, что половина значения поддерживается типом не являются действительными или значимыми.

В целом, это значение термина примитив указывает на происхождение типа как платформы, а не самого разработчика. Естественным следствием этого является то, что примитивный тип игнорирует область приложения (фактически он поддерживается платформой, такой как язык Java или PHP, используемой во многих различных областях). Отсутствие доменной логики не добавляет никакого поведения к объекту, что делает его анемичным.

Первый пример в PHP

Пример PHP может быть следующим. Предположим, у нас есть метод с методом подписи (My_Class $ firstParameter, массив $ secondParameter). Имена параметров в этом примере являются полностью гипотетическими и лишены смысла.

На второй параметр метода влияет Primitive Obsession. Как только я прочитаю эту подпись, что мне следует использовать в качестве второго параметра? Массив строк, или чисел, или многомерный? Это действительно легко создать недопустимое значение для $ secondParameter, в то время как для $ firstParameter будет трудно, если My_Class обеспечивает корректный метод создания (например, обязательные параметры в конструкторе и автоматическая вставка значений по умолчанию).

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

Конечно, не все переменные достойны замены структурой данных, связанной с доменом. Основное правило для обнаружения Примитивной Одержимости таково: передаете ли вы скалярное значение или массив с одинаковой структурой более чем одному методу?

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

Вот некоторые специальные категории Primitive Obsession в PHP-приложениях и способы их решения.

Струны

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

Сам класс может добавить немного поведения, например неизменяемость или преобразования в разные форматы. Моя типичная реализация, которая использует поддержку языка, представляет собой комбинацию частного поля плюс метод получения или метод __toString ().

Ассоциативные массивы

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

Другими полезными дополнениями могут быть метод equals () и метод для генерации других объектов-значений, полученных из текущего объекта, особенно когда объект неизменный.

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

Числовые массивы

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

В этом примере я извлекаю объекты-значения на служебном уровне, но это может быть применено к модели предметной области с отличными результатами. Единственный улов — система объектно-реляционного сопоставления (или система сопоставления объектно-чего-то) должна уметь переводить ваш сложный граф объектов в хранилище. Доктрина 2 пока не может отобразить объекты-значения в сущности в один столбец из коробки. Таким образом, у нас гораздо больше свободы в моделировании других слоев, чем в модели предметной области, по крайней мере, в PHP.

<?php
/**
 * Here we reason on functions, but this pattern will probably be applied
 * to object methods.
 * This is the worst possible function: it accepts everything
 * and if you pass it the wrong $options, an internal error will be raise
 */
function searchArticles($options) { /* ... */ }

/**
 * This function, which I encountered in a real project, is slightly
 * better than the previous one, but still I don't know what array I should pass
 * to it. Strings? Fields?
 * I can document it here with a sample call, but if $options is used throughout
 * many similar methods I would duplicate all the information...
 * <code>
 * $result = search(array(
 *     'field' => 'value'
 * ));
 * </code>
 */
function searchArticles(array $options) {}

/**
 * Here we define a Parameter Object to avoid using the primitive type.
 * We don't need any behavior like immutability or population, so we stay
 * very simple.
 * The sample value is centralized here:
 * <code>
 * new SearchOptions(array(
 *     'field' => 'value'
 * ));
 * </code>
 */
class SearchOptions extends ArrayObject {}

/**
 * Now we have a clear signature in every search() method scattered throughout
 * the codebase.
 */
function searchArticles(SearchOptions $options) {}
function searchComments(SearchOptions $options) {}
/**
 * Here we have a second argument that we can clearly distinguish from the previous one.
 */
function searchAuthors(SearchOptions $options, OrderingOptions $ordering) {}
/**
 * Here a totally different $options that we could have confused. Parameter names 
 * are never enough to characterize signatures: they do not enforce anything, can 
 * easily get out of date and confuse who reads the code. 
 */
function getAllTags(OrderingOptions $options) {}