Как стать автором
Обновить

Комментарии 49

Думаю в качестве примера использования «неизменяемых объектов» прекрасно подошел бы шаблон «Value Object» Фаулера.

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

В статье приводятся «неизменяемые объекты», ни слова о VO в статье нет, а ведь пример с VO был бы более показательным.

Ни слова о VO в статье нет потому что статья не про VO, а про неизменяемость в целом.

Давно ли на хабре стало принято отвечать не читая ветку комментов?

Александр написал ровно то что я хотел, так что в чем суть притензии. Если что продублирую.


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

Думаю в качестве примера использования «неизменяемых объектов» прекрасно подошел бы шаблон «Value Object» Фаулера

Перечитайте еще пару раз.

Он там и есть в примере. Просто не написано «это VO».

В статье абстрактный (логически, а не программно) пример реализации «неизменяемых объектов». От того, что VO тоже должны быть неизменяемыми, не значит, что они тоже есть в примерах, поверьте.
От того, что VO тоже должны быть неизменяемыми, не значит, что они тоже есть в примерах, поверьте.

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


Ну и из ваших слов не понятно. то есть вы признаете реализацию паттерна только если рядом напишут название? Как-то это глупо.


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

Ну и из ваших слов не понятно. то есть вы признаете реализацию паттерна только если рядом напишут название? Как-то это глупо

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

Я не буду против, если вы назовете пример, при чтении которого у читателя не возникнет вопроса «зачем» хотя бы трамваем, мне все равно. Говорю о VO я здесь по двум причинам:
1. Прекрасно подходит в качестве примера
2. Все его знают (я надеюсь)

Боюсь вы размышляете не о том, о чем говорю я.
Прекрасно подходит в качестве примера

Первый же пример с Address. Просто в статье не говорится о том что это value object, а просто объект. Что более чем просто понятно и не нагружает мозг читателя лишними терминами.


  1. Все его знают (я надеюсь)


знаете, вот из общения с PHP разработчиками — далеко не все. А многие под VO подразумевают только DTO. Да и Эванса многие не читали и используют active record с публичными пропертями. А еще есть приличный процент тех кто используют массивчики и глобальное состояние в сингелтонах.

Просто в статье не говорится о том что это value object

А ведь как было бы здорово, если бы говорилось.
у читателя не возникнет вопроса «зачем»


Что более чем просто понятно и не нагружает мозг читателя лишними терминами

Все его знают (я надеюсь)


знаете, вот из общения с PHP разработчиками — далеко не все

Вот с этого и надо было начинать, а то вас куда то не в ту глуш увело.
Вот с этого и надо было начинать, а то вас куда то не в ту глуш увело.

нет, вы зациклились на том что бы в статье упоминалось VO. Я пытался объяснить что статья НЕ про VO, хотя темы пересекаются. А дальше пошла рекурсия.

нет, вы зациклились на том что бы в статье упоминалось VO

Я не циклился, я предложил VO как хороший пример использования «неизменяемых объектов». Пример, отвечающий на вопрос «зачем».
Я пытался объяснить что статья НЕ про VO

А кто спорит то? )
Перечитайте еще пару раз.

вы тоже перечитайте еще пару раз. Пример с Address это классический VO. Просто сам термин VO имеет несколько значений в зависимости от контекста. Да, интерпритация Фаулера и Эванса популярна, но это не подавляющее большинство. Да и зачем усложнять то? Если у вас объект имутабельный — он автоматически становится VO как бы мы не крутили.

он автоматически становится VO как бы мы не крутили

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

VO это тоже не цель — это просто средство.


Конкретно в этой статье речь только про имутабельноть и как это важно для борьбы с сайд эффектами. VO это более обширная тема и имутабельность это не только их характеристика.


Это бесполезный спор. Хотите — напишите статью про VO.

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


Нет. Определяющая характеристика VO — сравнение по значению всех полей, а не по идентифицирующим. Пример — объект финансовой транзакции по какой-то сущности. Иммутабельный (чёрную бухгалтерию не рассматриваем), изменение баланса сущности производится выравнивающими/сторнирующими транзакциями, но обладающий чёткой идентичностью — даже если все значимые значения (суммы, даты с точностью до наносекунд, контрагенты и т. п.) равны, то всё равно каждая транзакция обладает собственной идентичностью. С другой стороны, иммутабельность не является даже необходимым признаком VO. Это лишь рекомендуемая характеристика для избежания необходимости отслеживания побочных эффектов.
Так же в качестве примера неизменяемых объектов можно было бы привести PSR-7
А давайте писать все примеры на PHP 7 и двигать его в массы

Все примеры работают на PHP 7.

я имел ввиду использовать типизацию, return types и т.д.

Можно, но смысл от этого не поменяется. В следующих статьях предложу это Марку.

использовать типизацию

вы имели в виду тайп хинтинг для скаляров? Можно, но это излишнее усложнение.


return types и т.д.

опять же можно, но в половине примеров это бесполезно без дженериков и nullable types (последние появятся только в 7.1)

На мой взгляд для полноты примера было бы полезно описать код Money::USD().

И Money::val(). Он чем-то отличается от Money::getAmount()?

Это вопервых не статические методы, а во вторых либо имеет место опечатка, либо оно и в правду одно и то же.

Это принятое обозначение, чтобы сослаться на метод класса, не важно статический он или нет, вот например, тут так делают. Или я совсем туплю под вечер? :)

Да, общепринятое.

это просто статический метод-фабрика. Именованный конструктор если хотите. Тут не так давно перевод поста Матиаса Верраеса проскакивала на эту тему.

Суть-то понятна, но код этого метода — 1 строка, не обязательно на ней было экономить

public static function USD($val) {
    return new static($val, static::CURRENCY_USD);
}
В зачем здесь Late Static Binding?

Если класс final — то точно не нужно, а так не вижу проблемы. Если вы их видите — предлагаю обсудить, мне будет полезно.

Список аргументов конструктора может измениться при наследовании

final наследовать нельзя.

Несомненно. Fesor сказал «Если класс final — то точно не нужно, а так не вижу проблемы». Как я понял, под «а так» подразумевалось, что класс не финальный.

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


public static function USD($amount) {
    $money = new static();
    $money->amount = $amount;
    $money->currency = static::CURRENCY_USD;

    return $money;
}

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


Собственно в этом соль статических методов-фабрик. Мы можем объявлять для объекта разные "конструкторы" со своими ограничениями. Вроде у нас есть два способа создать объект, и для одного нужно два аргумента а для другого 4 и не меньше.

<?php

final class Money 
{
    private $amount;

    public function getAmount()
    {
        return $this->amount;
    }

    public function add($amount)
    {
        return new self($this->amount + $amount, $this->currency);
    }
}


Я что-то не понял? Или откуда тут взялся $this->currency?

Это валюта, в которой хранится сумма. Конструктора просто не хватает в примере. Что-то типа такого должно быть


final class Money 
{
    const CURRENCY_USD = 1;

    private $amount;
    private $currency;

    private function __construct($amount, $currency)
    {
        $this->amount = $amount;
        $this->currency = $currency;
    }

    public static function USD($amount) {
        return new static($amount, self::CURRENCY_USD);
    }

    public function getAmount()
    {
        return $this->amount;
    }

    public function add($amount)
    {
        return new self($this->amount + $amount, $this->currency);
    }
}
НЛО прилетело и опубликовало эту надпись здесь
Как-то странно это выглядит:
public function add($amount)
{
    return new self($this->amount + $amount, $this->currency);
}

Если вызываешь add у объекта, то ждешь, что добавление будет именно к этому объекту. В чем проблема была через статик дублировать.
Нормально выглядит для ФП, единственное что мне не нравится, что на уровне синтаксиса PHP нельзя ограничить возвращаемое значение только $this, так что без PHPDoc или залезания в код не обойтись.

С другой стороны, то же самое различие у стандартных DateTime и DateTimeImmutable уже давно, так что удивлять особо не должно.
НЛО прилетело и опубликовало эту надпись здесь
Слишком короткая цепочка получается. В случае, например, построения запросов, конфигурирования, фильтрации и т.д. это было бы оправданно. Но здесь, на мой личный взгляд, бессмысленное притягивание ФП за уши. У неизменяемого объекта лучше уж вообще не делать таких методов, чтобы не вводить в заблуждение. А изменять его статичными методами, чтобы было очевидно, что объект неизменяемый.
> У неизменяемого объекта лучше уж вообще не делать таких методов, чтобы не вводить в заблуждение.

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

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

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

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

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

final class Money 
{
    public static function add(Money $source, double $sum): Money
    {
        // ...
    }
}


К тому же изменяемые объекты так же можно делать неизменяемыми, для этого можно создавать обертки:
class Mutable
{
    protected $val;

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

    public function getVal(): int
    {
        return $this->val;
    }

    public function add(int $val): void
    {
        $this->val += $val;
    }
}

class Immutable extends Mutable
{
    public static function addAndClone(Immutable $immutable, int $val): Immutable
    {
        return new Immutable($immutable->getVal() + $val);
    }

    public function add(int $val): void
    {
        throw new \RuntimeException('Immutable value.');
    }
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий