То, что система сложна, не означает, что вы должны усложнять ее . Если у вас есть класс, который имеет слишком много зависимостей (или коллабораторов), как это:
public class MyAwesomeClass {
public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
// Assign it all
}
}
... тогда это стало слишком сложно, и ты не следишь за SRP , не так ли? Могу поспорить, если бы вы записали, что MyAwesomeClass
на карточке CRC она не помещается на карточку, или вы должны писать очень мелкими неразборчивыми буквами.
Здесь у вас есть то, что ваши ребята следовали только принципу разделения интерфейса и, возможно, довели его до крайности, но это совсем другая история. Можно утверждать, что зависимости являются объектами домена (что происходит), однако наличие класса, который одновременно обрабатывает 20 объектов домена, слишком растягивает его.
TDD даст вам хороший показатель того, сколько делает класс. Прямо поставил; если у тестового метода есть код установки, на запись которого уходит вечность (даже если вы реорганизуете тесты), то у вас, MyAwesomeClass
вероятно, слишком много дел.
Итак, как вы решаете эту головоломку? Вы перемещаете обязанности в другие классы. Есть несколько шагов, которые вы можете предпринять для класса с такой проблемой:
- Определите все действия (или обязанности), которые ваш класс выполняет со своими зависимостями.
- Сгруппируйте действия в соответствии с тесно связанными зависимостями.
- Дать полномочия! Т.е. рефакторинг каждого из выявленных действий либо новым, либо (что более важно) другим классам.
Абстрактный пример рефакторинга ответственности
Пусть C
будет класс , который имеет несколько зависимостей D1
, D2
, D3
, D4
что вам нужно реорганизовать , чтобы использовать меньше. Когда мы определяем, какие методы C
вызывают зависимости, мы можем составить простой список:
D1
- performA(D2)
,performB()
D2
- performD(D1)
D3
- performE()
D4
- performF(D3)
Глядя на список, мы видим это D1
и D2
связаны друг с другом, так как класс так или иначе нуждается в них. Мы также можем видеть, что D4
необходимо D3
. Итак, у нас есть две группировки:
Group 1
- D1
<->D2
Group 2
- D4
->D3
Группировка является показателем того, что у класса теперь есть две обязанности.
Group 1
- Один для обработки вызова двух объектов, которые нужны друг другу. Возможно, вы можете позволить своему классу C
устранить необходимость обработки обеих зависимостей и оставить одну из них для обработки этих вызовов. В этой группе очевидно, что D1
может иметь ссылку на D2
.
Group 2
- Другая ответственность нуждается в одном объекте, чтобы вызвать другой. Не можете D4
справиться D3
вместо вашего класса? Тогда мы, вероятно, можем исключить D3
из класса C
, разрешив D4
вместо этого делать вызовы.
Не принимайте мой ответ, изложенный в камне, так как пример очень абстрактный и содержит много предположений. Я почти уверен, что есть и другие способы реорганизовать это, но, по крайней мере, эти шаги могут помочь вам создать какой-то процесс перемещения обязанностей вместо разделения классов.
Редактировать:
Среди комментариев @ Эммад Карем говорит:
«Если ваш класс имеет 20 параметров в конструкторе, это не похоже на то, что ваша команда знает, что такое SRP. Если у вас есть класс, который выполняет только одну задачу, как у него 20 зависимостей?» - я думаю, что если вы есть класс Customer, это не странно иметь 20 параметров в конструкторе.
Это правда, что объекты DAO имеют тенденцию иметь много параметров, которые вы должны установить в своем конструкторе, и параметры, как правило, являются простыми типами, такими как string. Однако в примере с Customer
классом вы можете сгруппировать его свойства внутри других классов, чтобы упростить задачу. Например, наличие Address
класса с улицами и Zipcode
класса, который содержит почтовый индекс и будет обрабатывать бизнес-логику, такую как проверка данных:
public class Address {
private String street1;
//...
private Zipcode zipcode;
// easy to extend
public bool isValid() {
return zipcode.isValid();
}
}
public class Zipcode {
private string zipcode;
public bool isValid() {
// return regex match that zipcode contains numbers
}
}
Эта тема обсуждается далее в сообщении блога «Никогда, никогда, никогда не используйте String в Java (или, по крайней мере, часто)» . В качестве альтернативы использования конструкторов или статических методов для облегчения создания подобъектов вы можете использовать шаблон построителя флюидов .