1. Обзор
В этом руководстве мы обсудим принцип единой ответственности как один из SOLID-принципов объектно-ориентированного программирования.
В целом, мы подробно рассмотрим, что это за принцип и как его реализовать при разработке нашего программного обеспечения. Кроме того, мы объясним, когда этот принцип может ввести в заблуждение.
*SRP = принцип единой ответственности
2. Принцип единой ответственности
Как следует из названия, этот принцип гласит, что у каждого класса должна быть одна обязанность, одна единственная цель . Это означает, что класс будет выполнять только одну работу, что приводит нас к выводу, что у него должна быть только одна причина для изменения .
Нам не нужны объекты, которые слишком много знают и имеют несвязанное поведение. Эти классы сложнее поддерживать. Например, если у нас есть класс, который мы часто меняем и по разным причинам, то этот класс следует разбить на несколько классов, каждый из которых обрабатывает одну проблему. Наверняка, при возникновении ошибки ее будет проще найти.
Давайте рассмотрим класс, содержащий код, каким-либо образом изменяющий текст. Единственной задачей этого класса должно быть манипулирование текстом .
public class TextManipulator {
private String text;
public TextManipulator(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void appendText(String newText) {
text = text.concat(newText);
}
public String findWordAndReplace(String word, String replacementWord) {
if (text.contains(word)) {
text = text.replace(word, replacementWord);
}
return text;
}
public String findWordAndDelete(String word) {
if (text.contains(word)) {
text = text.replace(word, "");
}
return text;
}
public void printText() {
System.out.println(textManipulator.getText());
}
}
Хотя это может показаться хорошим, это не лучший пример SRP. Здесь у нас есть две обязанности : манипулирование и печать текста .
Наличие метода, выводящего текст в этом классе, нарушает принцип единственной ответственности. Для этого мы должны создать еще один класс, который будет обрабатывать только печать текста:
public class TextPrinter {
TextManipulator textManipulator;
public TextPrinter(TextManipulator textManipulator) {
this.textManipulator = textManipulator;
}
public void printText() {
System.out.println(textManipulator.getText());
}
public void printOutEachWordOfText() {
System.out.println(Arrays.toString(textManipulator.getText().split(" ")));
}
public void printRangeOfCharacters(int startingIndex, int endIndex) {
System.out.println(textManipulator.getText().substring(startingIndex, endIndex));
}
}
Теперь в этом классе мы можем создавать методы для любого количества вариантов печати текста, потому что это его работа.
3. Как этот принцип может ввести в заблуждение?
Хитрость реализации SRP в нашем программном обеспечении заключается в том, чтобы знать ответственность каждого класса.
Однако у каждого разработчика есть свое видение назначения класса , что усложняет задачу. Поскольку у нас нет строгих инструкций о том, как реализовать этот принцип, мы остаемся со своими интерпретациями того, какой будет ответственность.
Это означает, что иногда только мы, как разработчики нашего приложения, можем решить, находится ли что-то в области действия класса или нет.
При написании класса по принципу SRP приходится думать о проблемной области, потребностях бизнеса и архитектуре приложения. Это очень субъективно, что делает реализацию этого принципа сложнее, чем кажется. Это будет не так просто, как пример, который мы имеем в этом уроке.
Это приводит нас к следующему пункту.
4. Сплоченность
Следуя принципу SRP, наши классы будут придерживаться одной функциональности. Их методы и данные будут связаны с одной четкой целью. Это означает высокую связность , а также надежность, которые вместе уменьшают количество ошибок .
При разработке программного обеспечения на основе принципа SRP важна сплоченность, поскольку она помогает нам найти единые обязанности для наших классов. Эта концепция также помогает нам находить классы, которые несут более одной ответственности.
Вернемся к нашим методам класса TextManipulator :
...
public void appendText(String newText) {
text = text.concat(newText);
}
public String findWordAndReplace(String word, String replacementWord) {
if (text.contains(word)) {
text = text.replace(word, replacementWord);
}
return text;
}
public String findWordAndDelete(String word) {
if (text.contains(word)) {
text = text.replace(word, "");
}
return text;
}
...
Здесь у нас есть четкое представление о том, что делает этот класс: Работа с текстом.
Но если мы не думаем о связности и у нас нет четкого определения ответственности этого класса, мы могли бы сказать, что написание и обновление текста — это две разные и отдельные работы. Руководствуясь этой мыслью, мы можем сделать вывод, что это должны быть два отдельных класса: WriteText
и UpdateText
.
На самом деле мы получим два тесно связанных и слабо связанных класса , которые почти всегда следует использовать вместе. Эти три метода могут выполнять разные операции, но по существу они служат одной цели : манипулированию текстом. Главное не переусердствовать.
Одним из инструментов, который может помочь достичь высокой согласованности методов, является LCOM. По сути, LCOM измеряет связь между компонентами класса и их отношение друг к другу.
Мартин Хитц и Бехзад Монтазери представили LCOM4 , который некоторое время измерял Sonarqube , но с тех пор он устарел.
5. Вывод
Несмотря на то, что название принципа говорит само за себя, мы видим, как легко его реализовать неправильно. Обязательно разграничивайте ответственность каждого класса при разработке проекта и уделяйте особое внимание сплоченности.
Как всегда, код доступен на GitHub.