Так что я недавно сделал несколько серьезных рефакторингов в своем коде. Одной из основных вещей, которые я пытался сделать, было разделение моих классов на объекты данных и рабочие объекты. Это было вдохновлено, среди прочего, этим разделом Чистого кода :
Гибриды
Эта путаница иногда приводит к неудачным гибридным структурам данных, которые являются наполовину объектами и наполовину структурами данных. У них есть функции, которые делают важные вещи, и у них также есть либо открытые переменные, либо открытые методы доступа и мутаторы, которые, для всех намерений и целей, делают частные переменные общедоступными, побуждая другие внешние функции использовать эти переменные так, как процедурная программа будет использовать структура данных.
Такие гибриды затрудняют добавление новых функций, но также затрудняют добавление новых структур данных. Они худшие из обоих миров. Избегайте их создания. Они указывают на запутанный дизайн, авторы которого не уверены - или, что еще хуже, не знают - нуждаются ли они в защите от функций или типов.
Недавно я просматривал код одного из моих рабочих объектов (который реализует шаблон посетителя ) и увидел следующее:
@Override
public void visit(MarketTrade trade) {
this.data.handleTrade(trade);
updateRun(trade);
}
private void updateRun(MarketTrade newTrade) {
if(this.data.getLastAggressor() != newTrade.getAggressor()) {
this.data.setRunLength(0);
this.data.setLastAggressor(newTrade.getAggressor());
}
this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}
Я сразу сказал себе: «Завидую особенностью! Эта логика должна быть в Data
классе, особенно в handleTrade
методе, handleTrade
и всегдаupdateRun
должна происходить вместе». Но потом я подумал, что «класс данных - это просто структура данных, если я начну это делать, то это будет гибридный объект!»public
Что лучше и почему? Как вы решаете, что делать?
Ответы:
В цитируемом вами тексте есть хороший совет, хотя я заменю «структуры данных» на «записи», предполагая, что подразумевается нечто вроде структур. Записи - это просто тупые скопления данных. Хотя они могут быть изменяемыми (и, таким образом, сохранять состояние в мышлении функционального программирования), они не имеют никакого внутреннего состояния, никаких инвариантов, которые должны быть защищены. Вполне допустимо добавлять в запись операции, облегчающие ее использование.
Например, мы можем утверждать, что трехмерный вектор является глупой записью. Однако это не должно мешать нам добавлять метод like
add
, который упрощает добавление векторов. Добавление поведения не превращает (не так глупо) запись в гибрид.Эта строка пересекается, когда открытый интерфейс объекта позволяет нам нарушить инкапсуляцию: есть некоторые внутренние объекты, к которым мы можем получить прямой доступ, таким образом приводя объект в недопустимое состояние. Мне кажется, что у
Data
него есть состояние, и его можно перевести в недопустимое состояние:Если какое-либо состояние действительно для ваших данных, то с вашим кодом все в порядке, и вы можете продолжать. В противном случае:
Data
класс отвечает за собственную согласованность данных. Если обработка сделки всегда включает обновление агрессора, это поведение должно быть частьюData
класса. Если изменение агрессора предполагает установку длины пробега на ноль, это поведение должно быть частьюData
класса.Data
никогда не был глупой записью. Вы уже сделали это гибридом, добавив публичные сеттеры.Существует один сценарий, в котором вы можете рассмотреть возможность смягчения этих строгих обязанностей: если он
Data
является частным для вашего проекта и, следовательно, не является частью какого-либо общедоступного интерфейса, вы все равно можете обеспечить правильное использование класса. Однако это возлагает ответственность за поддержаниеData
согласованности во всем коде, а не на сбор их в центральном месте.Недавно я написал ответ об инкапсуляции , в котором более подробно рассматривается , что такое инкапсуляция и как вы можете ее обеспечить.
источник
Тот факт, что
handleTrade()
иupdateRun()
всегда происходят вместе (а второй метод на самом деле находится на посетителе и вызывает несколько других методов для объекта данных), пахнет временной связью . Это означает, что вы должны вызывать методы в определенном порядке, и я предполагаю, что вызов методов не по порядку может что-то сломать в худшем случае или не даст значимого результата в лучшем случае. Фигово.Как правило, правильный способ рефакторинга этой зависимости состоит в том, что каждый метод возвращает результат, который можно либо передать в следующий метод, либо воздействовать напрямую.
Старый код:
Новый код:
Это имеет несколько преимуществ:
источник
updateRun
метод был закрытым . Хороший совет - избегать последовательной связи, но она применяется только к разработке API / общедоступным интерфейсам, а не к деталям реализации. Похоже, реальный вопрос заключается в том,updateRun
должен ли он быть в посетителе или в классе данных, и я не вижу, как этот ответ решает эту проблему.updateRun
имеет значения, важна реализация,this.data
которой нет в вопросе и является объектом, управляемым объектом посетителя.С моей точки зрения, класс должен содержать «значения для состояния (переменные-члены) и реализации поведения (функции-члены, методы)».
«Прискорбные гибридные структуры данных» появляются, если вы сделаете общедоступными переменные-члены состояния класса (или их методы получения / установки), которые не должны быть открытыми.
Поэтому я вижу, что нет необходимости иметь отдельные классы для объектов данных и рабочих объектов.
Вы должны быть в состоянии сохранить переменные-члены-состояния непубличными (ваш уровень базы данных должен уметь обрабатывать непубличные переменные-члены)
Зависть к объектам - это класс, который чрезмерно использует методы другого класса. Смотрите Code_smell . Наличие класса с методами и состоянием устранит это.
источник