Вот проблема, с которой я часто сталкиваюсь: пусть будет проект интернет-магазина с классом Product. Я хочу добавить функцию, которая позволяет пользователям публиковать отзывы о продукте. Итак, у меня есть класс Review, который ссылается на продукт. Теперь мне нужен метод, который перечисляет все отзывы о продукте. Есть две возможности:
(А)
public class Product {
...
public Collection<Review> getReviews() {...}
}
(В)
public class Review {
...
static public Collection<Review> forProduct( Product product ) {...}
}
Посмотрев на код, я бы выбрал (A): он не статичен и не нуждается в параметре. Тем не менее, я чувствую, что (A) нарушает принцип единой ответственности (SRP) и принцип открытого и закрытого доступа (OCP), тогда как (B) не:
(SRP) Когда я хочу изменить способ сбора отзывов о продукте, я должен изменить класс продукта. Но причина изменения класса Product должна быть только одна. И это, конечно, не отзывы. Если я упакую каждую функцию, которая имеет какое-либо отношение к продуктам в Продукте, она скоро будет засорена.
(OCP) Я должен изменить класс Product, чтобы расширить его с помощью этой функции. Я думаю, что это нарушает принцип принципа «закрыто для перемен». До того, как я получил запрос клиента на выполнение проверок, я считал, что Продукт закончен, и «закрыл» его.
Что важнее: следовать принципам SOLID или иметь более простой интерфейс?
Или я вообще что-то здесь не так делаю?
Результат
Вау, спасибо за все ваши отличные ответы! Трудно выбрать один в качестве официального ответа.
Позвольте мне обобщить основные аргументы из ответов:
- pro (A): OCP не является законом, и читабельность кода также имеет значение.
- pro (A): отношения объекта должны быть судоходными. Оба класса могут знать об этих отношениях.
- pro (A) + (B): выполните оба действия и делегируйте их в (A) - (B), поэтому вероятность повторного изменения Product меньше.
- pro (C): поместить методы поиска в третий класс (сервис), где он не является статичным.
- Противоположность (B): препятствует насмешкам в тестах.
Несколько дополнительных вещей, которые сделали мои коллеги на работе:
- pro (B): наша платформа ORM может автоматически генерировать код для (B).
- pro (A): по техническим причинам нашей платформы ORM в некоторых случаях необходимо будет изменить «закрытую» сущность, независимо от того, куда идет поиск. Так что я не всегда смогу придерживаться SOLID.
- Против (С): много суеты ;-)
Вывод
Я использую оба (A) + (B) с делегированием для моего текущего проекта. Однако в сервис-ориентированной среде я пойду с (C).
Assert(5 = Math.Abs(-5));
Abs()
не проблема, тестирование того, что от него зависит. У вас нет шва для изоляции зависимого тестируемого кода (CUT) для использования макета. Это означает, что вы не можете проверить его как элементарный модуль, и все ваши тесты станут интеграционными тестами, которые проверяют логику модуля. Сбой в тесте может быть в CUT или в Abs()
(или его зависимом коде) и устраняет преимущества диагностики модульных тестов.