Книга: Экстремальное программирование. Разработка через тестирование

Объект-значение (Value Object)

Объект-значение (Value Object)

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

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

Представьте, что я – объект и у меня есть прямоугольник (Rectangle). Я вычисляю некоторое значение, зависящее от этого прямоугольника, например его площадь. Чуть позже некто (например, другой объект) вежливо просит меня предоставить ему мой прямоугольник для выполнения некоторой операции. Чтобы не показаться невежливым, я предоставляю ему мой прямоугольник. А через пару мгновений, вы только посмотрите, прямоугольник был модифицирован у меня за спиной! Значение площади, которое я вычислил ранее, теперь не соответствует действительности, и не существует способа известить меня об этом.

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

Существует несколько способов решения проблемы наложения имен. Во-первых, вы можете никому не отдавать объект, от состояния которого вы зависите. Вместо этого в случае необходимости вы можете создавать копии этого объекта. Такой подход может потребовать слишком много времени и слишком много пространства, кроме того, игнорируется ситуация, когда вы хотите сделать изменения некоторого объекта общими для нескольких других объектов, зависящих от его состояния. Еще одно решение – шаблон «Наблюдатель» (Observer). В этом случае, если вы зависите от состояния некоторого объекта, вы должны предварительно сообщить ему об этом, иначе говоря, зарегистрироваться. Объект, за состоянием которого следят, оповещает все зарегистрированные им объекты-наблюдатели о своем изменении. Шаблон «Наблюдатель» (Observer) может затруднить понимание последовательности выполнения операций, кроме того, логика формирования и удаления зависимостей между объектами выглядит далеко не идеальной.

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

Я помню, как похожая ситуация возникла с целыми числами, когда я впервые изучал язык Smalltalk. Если я изменяю бит 2 на 1, почему все двойки не становятся шестерками?

a:= 2.

b:= a.

a:= a bitAt: 2 put: 1.

a => 6

b => 2

Целые числа – это значения, которые маскируются под объекты. В языке Small-talk это утверждение является истиной для небольших целых чисел и имитируется в случае, если целое число не умещается в машинное слово. Когда я устанавливаю бит, то получаю в свое распоряжение новый объект с установленным битом. Старый объект остается неизменным.

В рамках шаблона «Объект-значение» (Value Object) каждая операция должна возвращать новый объект, а первоначальный объект должен оставаться неизменным. Пользователи должны знать, что они используют объект-значение. В этом случае полученный объект следует сохранить (как в предыдущем примере). Конечно же, из-за необходимости создания новых объектов полученный в результате код может оказаться медленным. Однако в данном случае любые проблемы с производительностью должны решаться в точности так же, как и любые другие проблемы с производительностью: вы должны оценить производительность при помощи тестов с реальными данными, определить, насколько часто производится обращение к медленному коду, выполнить профилирование и определить, какой именно код должен быть оптимизирован и как лучше всего этого достичь.

Я предпочитаю использовать «Объект-значение» (Value Object) в ситуациях, когда операции, выполняемые над объектами, напоминают алгебру. Например, пересечение и объединение геометрических фигур, операции над значениями, с каждым из которых хранится единица измерения, а также операции символьной арифметики. Каждый раз, когда использование «Объект-значение» (Value Object) имеет хоть какой-то смысл, я пытаюсь его использовать, так как результирующий код проще читать и отлаживать.

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

Оглавление книги


Генерация: 0.984. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз