Согласно « Когда примитивная одержимость не является запахом кода»? Я должен создать объект ZipCode для представления почтового индекса вместо объекта String.
Однако, по моему опыту, я предпочитаю видеть
public class Address{
public String zipCode;
}
вместо того
public class Address{
public ZipCode zipCode;
}
потому что я думаю, что последний требует, чтобы я перешел в класс ZipCode, чтобы понять программу.
И я считаю, что мне нужно перемещаться между многими классами, чтобы увидеть определение, если все примитивные поля данных были заменены классом, который чувствует себя так, как будто страдает от проблемы йо-йо (анти-паттерн).
Поэтому я хотел бы переместить методы ZipCode в новый класс, например:
Старый:
public class ZipCode{
public boolean validate(String zipCode){
}
}
Новое:
public class ZipCodeHelper{
public static boolean validate(String zipCode){
}
}
так что только тот, кому нужно проверить почтовый индекс, будет зависеть от класса ZipCodeHelper. И я обнаружил еще одно «преимущество» сохранения примитивной одержимости: он сохраняет класс похожим на его сериализованную форму, если таковая имеется, например: таблицу адресов со строковым столбцом zipCode.
Мой вопрос заключается в том, является ли «избегание проблемы йо-йо» (переход между определениями классов) действительной причиной, позволяющей «примитивную одержимость»?
источник
Ответы:
Предполагается, что вам не нужно идти в класс ZipCode, чтобы понять класс Address. Если ZipCode хорошо спроектирован, должно быть очевидно, что он делает, просто читая класс Address.
Программы не читаются из конца в конец - как правило, программы слишком сложны, чтобы сделать это возможным. Вы не можете хранить весь код в программе одновременно. Поэтому мы используем абстракции и инкапсуляции для «разбиения» программы на значимые блоки, чтобы вы могли просматривать одну часть программы (скажем, класс Address) без необходимости читать весь код, от которого она зависит.
Например, я уверен, что вы не будете читать исходный код String каждый раз, когда сталкиваетесь со String в коде.
Переименовывая класс из ZipCode в ZipCodeHelper, можно предположить, что теперь есть две разные концепции: почтовый индекс и вспомогательный почтовый индекс. Так что вдвое сложнее. И теперь система типов не может помочь вам различить произвольную строку и действительный почтовый индекс, поскольку они имеют одинаковый тип. Вот где уместна «одержимость»: вы предлагаете более сложную и менее безопасную альтернативу только потому, что хотите избежать простого типа оболочки вокруг примитива.
Использование примитива ИМХО оправдано в тех случаях, когда нет проверки или другой логики в зависимости от этого конкретного типа. Но как только вы добавляете какую-либо логику, гораздо проще, если эта логика инкапсулируется с типом.
Что касается сериализации, я думаю, что это звучит как ограничение в используемой платформе. Конечно, вы должны иметь возможность сериализовать ZipCode в строку или сопоставить его со столбцом в базе данных.
источник
ZipCodeHelper
(который я бы скорее назвалZipCodeValidator
) вполне может установить соединение с веб-сервисом для выполнения своей работы. Это не было бы частью единственной обязанности «держать данные почтового индекса». Заставить систему типов запретить недопустимые почтовые индексы все еще можно, сделавZipCode
конструктор эквивалентом private-пакета Java и вызвав его с помощью оператора,ZipCodeFactory
который всегда вызывает валидатор.ZipCode
будет «структурой данных» иZipCodeHelper
«объект». В любом случае, я думаю, что мы согласны с тем, что нам не нужно передавать веб-соединения конструктору ZipCode.int
качестве идентификатора позволяет умножить идентификатор на идентификатор ...)Foo
иFooValidator
классов. У нас может бытьZipCode
класс, который проверяет формат, и класс, который обращается кZipCodeValidator
некоторому веб-сервису, чтобы проверить, что правильно отформатированныйZipCode
актуален. Мы знаем, что почтовые индексы меняются. Но практически у нас будет список действительных почтовых индексов, инкапсулированных вZipCode
или в какой-либо локальной базе данных.Если можете сделать:
И конструктор для ZipCode делает:
Затем вы нарушили инкапсуляцию и добавили довольно глупую зависимость в класс ZipCode. Если конструктор не вызывает,
ZipCodeHelper.validate(...)
то у вас есть отдельная логика на его собственном острове без фактического ее применения. Вы можете создать недействительные почтовые индексы.validate
Метод должен быть статическим методом на классе ZipCode. Теперь знание «действительного» почтового индекса связано с классом ZipCode. Учитывая, что ваши примеры кода выглядят как Java, конструктор ZipCode должен выдать исключение, если задан неправильный формат:Конструктор проверяет формат и выдает исключение, тем самым предотвращая создание недопустимых почтовых индексов, а статический
validate
метод доступен для другого кода, поэтому логика проверки формата инкапсулируется в классе ZipCode.В этом варианте класса ZipCode нет "йо-йо". Это просто называется надлежащим объектно-ориентированным программированием.
Мы также собираемся игнорировать интернационализацию, что может потребовать использования другого класса, называемого ZipCodeFormat или PostalService (например, PostalService.isValidPostalCode (...), PostalService.parsePostalCode (...) и т. Д.).
источник
ZipCode.validate
метод - это предварительная проверка, которую можно выполнить перед вызовом конструктора, который выдает исключение.Если вы много боретесь с этим вопросом, возможно, язык, который вы используете, не является подходящим инструментом для работы? Этот вид «доменных типов примитивов» тривиально легко выразить, например, в F #.
Там вы могли бы, например, написать:
Это действительно полезно для избежания распространенных ошибок, таких как сравнение идентификаторов разных сущностей. А поскольку эти типизированные примитивы намного легче, чем C # или Java-класс, вы в конечном итоге будете их использовать.
источник
ZipCode
?Ответ полностью зависит от того, что вы на самом деле хотите делать с почтовыми индексами. Вот две крайние возможности:
(1) Все адреса гарантированно находятся в одной стране. Нет исключений на всех. (Например, нет иностранных клиентов или сотрудников, чей частный адрес находится за границей, пока они работают на иностранного клиента.) В этой стране есть почтовые индексы, и от них можно ожидать, что они никогда не будут серьезно проблематичными (то есть они не требуют ввода в свободной форме такие как «в настоящее время D4B 6N2, но это меняется каждые 2 недели»). Почтовые коды используются не только для адресации, но и для проверки платежной информации или в аналогичных целях. - В этих обстоятельствах класс почтового индекса имеет большой смысл.
(2) Адреса могут быть практически в каждой стране, поэтому актуальны десятки или сотни схем адресации с почтовыми индексами или без них (а также с тысячами странных исключений и особых случаев). «Почтовый индекс» действительно требуется только для напоминания людям из стран, где используются почтовые индексы, чтобы не забыть предоставить свои. Адреса используются только для того, чтобы, если кто-то потерял доступ к своей учетной записи и смог подтвердить свое имя и адрес, доступ будет восстановлен. - В этих обстоятельствах классы почтовых индексов для всех соответствующих стран будут огромными усилиями. К счастью, они не нужны вообще.
источник
В других ответах говорилось о моделировании предметной области и использовании более богатого типа для представления вашей ценности.
Я не согласен, особенно учитывая приведенный вами пример кода.
Но мне также интересно, отвечает ли это на самом деле название вашего вопроса.
Рассмотрим следующий сценарий (взят из реального проекта, над которым я работаю):
У вас есть удаленное приложение на полевом устройстве, которое взаимодействует с вашим центральным сервером. Одним из полей БД для записи устройства является почтовый индекс для адреса, по которому находится полевое устройство. Вы не заботитесь о почтовом индексе (или любой другой адрес в этом отношении). Все люди, которые заботятся об этом, находятся на другой стороне границы HTTP: вы просто являетесь единственным источником правды для данных. Ему не место в моделировании вашего домена. Вы просто записываете его, проверяете, сохраняете и, по запросу, перетасовываете его в BLOB-объект JSON на точки в другом месте.
В этом сценарии выполнение чего-либо помимо проверки вставки с помощью ограничения регулярных выражений SQL (или его эквивалента в ORM), вероятно, является избыточным вариантом YAGNI.
источник
RegexValidatedString
, содержащую саму строку и регулярное выражение, используемое для ее проверки. Но если каждый экземпляр не имеет уникального регулярного выражения (что возможно, но маловероятно), это кажется немного глупым и расточительным в памяти (и, возможно, время компиляции регулярного выражения). Таким образом, вы либо помещаете регулярное выражение в отдельную таблицу и оставляете ключ поиска в каждом экземпляре, чтобы найти его (что, возможно, хуже из-за косвенного обращения), либо находите какой-то способ сохранить его один раз для каждого распространенного типа, разделяющего это правило, - Например статическое поле в типе домена или эквивалентный метод, как сказал IMSoP.ZipCode
Абстракция может иметь смысл , только если вашAddress
класс не такжеTownName
собственность. В противном случае у вас есть половина абстракции: почтовый индекс обозначает город, но эти два связанных фрагмента информации находятся в разных классах. Это не совсем имеет смысла.Однако, даже тогда, это все еще не правильное применение (или, скорее, решение) примитивной одержимости; который, насколько я понимаю, в основном фокусируется на двух вещах:
Твой случай ни тот, ни другой. Адрес - это четко определенная концепция с четко определенными свойствами (улица, номер, почтовый индекс, город, штат, страна, ...). Нет оснований разбивать эти данные, так как они несут единственную ответственность: указать место на Земле. Адрес требует, чтобы все эти поля были значимыми. Половина адреса бессмысленна.
Таким образом, вы знаете, что вам не нужно делить что-либо дальше: его дальнейшее разрушение умаляет функциональное намерение
Address
класса. Точно так же вам не нуженName
подкласс для использования вPerson
классе, кроме случаев, когдаName
(без привязки к человеку) является значимым понятием в вашей области. Что это (обычно) не так. Имена используются для идентификации людей, они обычно не имеют никакой ценности сами по себе.источник
Name
подкласс для использования вPerson
классе, если только Имя (без прикрепленного лица) не является значимым понятием в вашем домене ». Когда у вас есть пользовательская проверка имен, имя становится значимым понятием в вашем домене; который я явно упомянул в качестве допустимого варианта использования для подтипа. Во-вторых, для проверки почтового индекса вы вводите дополнительные предположения, такие как почтовые индексы, которые должны соответствовать формату данной страны. Вы затрагиваете тему, которая намного шире, чем цель вопроса ОП.Из статьи:
Исходный код читается гораздо чаще, чем пишется. Таким образом, проблема йо-йо в необходимости переключения между многими файлами является проблемой.
Однако нет , проблема йо-йо кажется гораздо более актуальной при работе с глубоко взаимозависимыми модулями или классами (которые обращаются друг к другу взад-вперед). Это особый вид кошмара для чтения, и, вероятно, это имел в виду тот, кто задумал проблему йо-йо.
Однако - да , важно избегать слишком большого количества уровней абстракции!
Например, я не согласен с предположением, сделанным в ответе mmmaaa, о том, что «вам не нужно идти ([посещать)] класс ZipCode, чтобы понять класс Address». Мой опыт показывает, что вы делаете - по крайней мере, первые несколько раз, когда вы читали код. Однако, как отметили другие, бывают случаи, когда
ZipCode
урок подходит.YAGNI («Я не хочу») - это лучший образец, чтобы следовать за лазанским кодом (кодом со слишком большим количеством слоев) - абстракции, такие как типы и классы, помогают программисту и не должны использоваться, если они не используются. помощь.
Я лично стремлюсь «сохранить строки кода» (и, конечно, связанные «сохранить файлы / модули / классы» и т. Д.). Я уверен, что есть некоторые, кто применил бы ко мне эпитет «примитивно одержимый» - я считаю более важным иметь код, который легко рассуждать, чем беспокоиться о метках, шаблонах и анти-шаблонах. Правильный выбор того, когда создавать функцию, модуль / файл / класс или помещать функцию в обычном месте, очень ситуативный. Я нацеливаюсь примерно на 3-100 строковых функций, 80-500 строковых файлов и "1, 2, n" для многократно используемого библиотечного кода ( SLOC - не включая комментарии или шаблон); обычно я хочу как минимум 1 дополнительный минимум SLOC на строку обязательного шаблонный).
Большинство положительных шаблонов возникли от разработчиков, которые делают именно это, когда они им нужны . Гораздо важнее научиться писать читаемый код, чем пытаться применять шаблоны без решения той же проблемы. Любой хороший разработчик может реализовать фабричный шаблон, не видев его раньше в необычном случае, когда он подходит для их задачи. Я использовал фабричный шаблон, шаблон наблюдателя и, вероятно, сотни, не зная их имени (т. Е. Есть ли «шаблон назначения переменных»?). Для забавного эксперимента - посмотрите, сколько шаблонов GoF встроено в язык JS - я прекратил считать примерно после 12-15 назад в 2009 году. Шаблон Factory так же прост, как, например, возврат объекта из конструктора JS - не нужно WidgetFactory.
Так что - да , иногда
ZipCode
это хороший класс. Однако, нет , проблема йо-йо не является строго актуальной.источник
Проблема йо-йо актуальна только в том случае, если вам нужно переворачиваться назад и вперед. Это вызвано одной или двумя вещами (иногда обеими):
Если вы можете посмотреть на имя и иметь разумное представление о том, что оно делает, а методы и свойства делают достаточно очевидные вещи, вам не нужно смотреть на код, вы можете просто использовать его. В этом и заключается весь смысл классов - они представляют собой модульные фрагменты кода, которые можно использовать и разрабатывать изолированно. Если вам нужно взглянуть на что-то другое, кроме API для класса, чтобы увидеть, что он делает, это в лучшем случае частичная ошибка.
источник
Помните, серебряной пули нет. Если вы пишете чрезвычайно простое приложение, которое нужно быстро пролистать, то эту работу может выполнить простая строка. Однако в 98% случаев объект Value, описанный Эриком Эвансом в DDD, идеально подходил бы. Вы можете легко увидеть все преимущества объектов Value, читая вокруг.
источник