Доменный дизайн, доменные объекты, отношение к сеттерам - PullRequest
11 голосов
/ 15 декабря 2010

В последнее время я смотрел несколько видео Грега Янга, и я пытаюсь понять, почему на объектах домена негативно относятся к сеттерам.Я думал, что доменные объекты должны были быть "тяжелыми" с логикой в ​​DDD.Есть ли в Интернете хорошие примеры плохого примера, а затем они могут исправить это?Любые примеры или объяснения хороши.Это относится только к событиям, хранящимся в манере CQRS, или это относится ко всем DDD?

Ответы [ 7 ]

11 голосов
/ 13 июня 2012

Я добавляю этот ответ, чтобы дополнить ответ Роджера Алсинга об инвариантах другими причинами.

Семантическая информация

Роджер четко объяснил, что установщики свойств не несут семантической информации. Разрешение установки для свойства, такого как Post.PublishDate, может привести к путанице, поскольку мы не можем точно знать, была ли публикация опубликована или только если дата публикации была установлена. Мы не можем знать наверняка, что это все, что нужно для публикации статьи. «Интерфейс» объекта не показывает свое «намерение».

Тем не менее, я считаю, что такие свойства, как «Включено», несут достаточно семантики для «получения» и «установки». Это то, что должно вступить в силу немедленно, и я не вижу необходимости в методах SetActive () или Activate () / Deactivate () по одной только причине отсутствия семантики в установщике свойств.

Инварианты

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

Инициализация / установка порядка свойств зависимостей

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

    public class Period
    {
        DateTime start;
        public DateTime Start
        {
            get { return start; }
            set
            {
                if (value > end  && end != default(DateTime))
                    throw new Exception("Start can't be later than end!");
                start = value;
            }
        }

        DateTime end;
        public DateTime End
        {
            get { return end; }
            set
            {
                if (value < start && start != default(DateTime))
                    throw new Exception("End can't be earlier than start!");
                end = value;
            }
        }
    }

Это наивный пример проверки «сеттера», которая вызывает зависимости порядка доступа. Следующий код иллюстрирует эту проблему:

        public void CanChangeStartAndEndInAnyOrder()
        {
            Period period = new Period(DateTime.Now, DateTime.Now);
            period.Start = DateTime.Now.AddDays(1); //--> will throw exception here
            period.End = DateTime.Now.AddDays(2);
            // the following may throw an exception depending on the order the C# compiler 
            // assigns the properties. 
            period = new Period()
            {
                Start = DateTime.Now.AddDays(1),
                End = DateTime.Now.AddDays(2),
            };
            // The order is not guaranteed by C#, so either way may throw an exception
            period = new Period()
            {
                End = DateTime.Now.AddDays(2),
                Start = DateTime.Now.AddDays(1),
            };
        }

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

Это становится более серьезным, когда мы используем инициализаторы объектов. Поскольку C # не гарантирует какой-либо порядок присваивания, мы не можем делать безопасных предположений, и код может выдавать или не выдавать исключение в зависимости от выбора компилятора. BAD!

В конечном итоге это проблема ПРОЕКТИРОВАНИЯ классов. Поскольку свойство не может «знать», что вы обновляете оба значения, оно не может «отключить» проверку, пока оба значения не будут фактически изменены. Вы должны либо сделать оба свойства доступными только для чтения и предоставить метод для одновременной установки обоих (теряя особенность инициализаторов объектов), либо вообще удалить код проверки из свойств (возможно, вводя другое свойство только для чтения, такое как IsValid, или выполнить валидацию). это когда нужно).

ОРМ "Увлажнение" *

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

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

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

Поскольку многие инструменты ORM поддерживают отложенную загрузку (фундаментальный аспект ORM!) Посредством использования виртуальных свойств (или методов), сопоставление с полями сделает невозможным для ORM отложенную загрузку объектов, сопоставленных с полями.

Заключение

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

Я все еще выясняю, где должна быть эта логика «проверки». Где мы проверяем аспекты инвариантов объекта? Куда мы помещаем проверки более высокого уровня? Используем ли мы хуки на ORM для выполнения валидации (OnSave, OnDelete, ...)? И т. Д. И т. Д. Но это не является предметом этого ответа.

10 голосов
/ 21 декабря 2010

Setters не несет никакой семантической информации.

например,

blogpost.PublishDate = DateTime.Now;

Означает ли это, что сообщение было опубликовано?Или просто, что дата публикации была установлена?

Рассмотрим:

blogpost.Publish();

Это ясно показывает намерение публикации поста в блоге.

Кроме того, сеттеры могут прерваться.инварианты объекта.Например, если у нас есть сущность «Треугольник», инвариант должен быть таким, чтобы сумма всех углов составляла 180 градусов.

Assert.AreEqual (t.A + t.B + t.C ,180);

Теперь, если у нас есть установщики, мы можем легко разбить инварианты:

t.A = 0;
t.B = 0;
t.C = 0;

Итак, у нас есть треугольник, в котором сумма всех углов равна 0 ... Это действительно треугольник?Я бы сказал нет.

Замена сеттеров методами может заставить нас поддерживать инварианты:

t.SetAngles(0,0,0); //should fail 

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

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

7 голосов
/ 15 декабря 2010

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

Простым примером будет сущность с именем. Если у вас есть общедоступный установщик, вы сможете изменить имя объекта из любой точки вашего приложения. Если вместо этого вы удалите этот установщик и добавите метод, подобный ChangeName(string name), к вашей сущности, это будет единственный способ изменить имя. Таким образом, вы можете добавить любую логику, которая будет всегда работать при изменении имени, потому что есть только один способ изменить его. Это также намного понятнее, чем просто присвоить имя чему-либо.

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

2 голосов
/ 17 декабря 2010

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

Я знаю, что это плохая практика, и что у вас, вероятно, должна быть модель представления (как в MVVM) или тому подобное, но для некоторых небольших приложений имеет смысл не чрезмерно моделировать IMHO.

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

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

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

1 голос
/ 15 декабря 2010

Я настоятельно рекомендую прочитать книгу Эрика Эванса DDD и Построение объектно-ориентированного программного обеспечения Бертрана Мейера . У них есть все образцы, которые вам понадобятся.

1 голос
/ 15 декабря 2010

Сеттер просто устанавливает значение.Он не должен быть "heavy" with logic.

Методы на ваших объектах с хорошими описательными именами должны быть "heavy" with logic и иметь аналог в самом домене.

0 голосов
/ 02 мая 2012

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

а) Это имеет смысл в .Net. Каждый разработчик знает о свойствах. Вот как вы устанавливаете вещи на объекте. Зачем отклоняться от этого для доменных объектов?

б) У сеттеров может быть код. Я полагаю, что до 3.5 установка объекта состояла из внутренней переменной и сигнатуры свойства

private object _someProperty = null;
public object SomeProperty{
    get { return _someProperty; }
    set { _someProperty = value; }
}

Очень легко и элегантно поместить валидацию в сеттер. В IL геттеры и сеттеры все равно преобразуются в методы. Зачем дублировать код?

В приведенном выше примере метода Publish () я полностью согласен. Иногда мы не хотим, чтобы другие разработчики устанавливали свойство. Это должно быть обработано методом. Однако имеет ли смысл иметь метод установки для каждого свойства, когда .Net предоставляет все функции, которые нам необходимы в объявлении свойства уже?

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

...