SRP - Single Responsibility Principle

Единна отговорност

Този принцип гласи, че един елемент трябва да има само една отговорност. Освен това тя следва да има само една причина да се промени, тази причина е промяна в изискването, какво се изисква от този елемент да изпълнява.

Прилагането на този принцип позволява:

  1. По-лесно тестване – Клас с една отговорност ще има много по-малко тестови случаи.
  2. Наличието на по-малко връзки – По-малко функционалност в един клас ще има по-малко зависимости.
  3. По-добра организация – По-малките, добре организирани класове са по-лесни за разбиране от монолитните.

Принципът Една функция трябва да прави едно и само едно нещо се използва когато се налага рефакториране (преработка на код, с цел да се подобри неговото качество, без да се промени крайния резултат) на големи функции в по-малки функции; използва се на най-ниските нива.

public static void calculateSum() {
     System.out.println("Enter the Two numbers for addtion: ");  
     Scanner readInput = new Scanner(System.in);  
     
     int a = readInput.nextInt();  
     int b = readInput.nextInt();
     int sum = a + b;
     
     System.out.println("The sum of numbers is: "+sum);     
}

Така написана, функцията не може да получава входни аргументи от различни източници (файл, база данни, друга функция, сензор и т.н.).

SOLID принципа за единна отговорност (SRP), много прилича на принципа по-горе, но има и разлики. Докато принципа “Една функция, една отговорност” е приложим на ниско ниво, колкото по-нагоре се отива (към по-високо ниво на абстракция), толкова по-трудно става разпознаването на отговорностите.

Всеки клас може да съдържа повече от един метод или поле, в тази ситуация не може да се приложи директно подхода както при функциите, а трябва да се мисли за средата, в която това обединение от методи и променливи ще намери приложение.

Този принцип може да бъде разбран и осъзнат чрез разглеждане на различни начини, по които той се нарушава.

Пример:

Разглеждаме приложение за заплати, в което присъства клас Employee, който има три метода: calculatePay(), reportHours() и save().

public class Employee {
     public void calculatePay() {
          //код на метода
     }
     public void reportHours() {
          //код на метода
     }
     public void save() {
          //код на метода
     }
}

Методът calculatePay() изчислява заплатата, необходима за счетоводния отдел.

Методът reportHours() е специфичен и се отнася към отдела за човешки ресурси.

Методът save() се отнася до администраторите на базата данни.

Нека предположим, че функциите calculatePay() и reportHours() споделят общ алгоритъм за изчисляване на часове без извънреден труд.

По какъв начин би бил организиран кода в такъв случай?

Логично е да поставим този алгоритъм във функция с името regularHours(), като предимството ще е липсата на повторяемост на един и същи код.

Обединяването на тези 3 метода в един клас може да доведе до наличие на взаимовръзки между различните отдели. От своя страна това може да е причина действията на един отдел да повлияят върху тези на друг, което не винаги е необходимо.

Пример:

public class Book {

    private String name;
    private String author;
    private String text;

    //constructor, getters and setters

    // methods that directly relate to the book properties
    public String replaceWordInText(String word){
        return text.replaceAll(word, text);
    }

    public boolean isWordInText(String word){
        return text.contains(word);
    }
}

В този код има декларирани свойства и два метода, описващи поведение на клас Book. Той работи добре и могат да се съхраняват толкова книги в приложението, колкото е необходимо.

В последствие се налага да извеждаме информацията, което води до рефакториране на съществуващия код:

public class Book {
    //...

    void printTextToConsole(){
        // our code for formatting and printing the text
    }
}

Този метод ще има достъп до системните ресурси за извеждане, което ще добави нова зависимост в него. Освен да съхранява информацията за книгата, класа знае и къде да я презентира.

Това обаче нарушава принципа на единната отговорност - класът има повече от една функционалност.

За да бъде спазен този принцип, трябва да се добави отделен клас, чиято единствена отговорност да бъде отпечатването/извеждането на текст:

public class BookPrinter {

    // methods for outputting text
    void printTextToConsole(String text){
        //our code for formatting and printing the text
    }

    void printTextToAnotherMedium(String text){
        // code for writing to any other location..
    }
}

Наличието на отделен клас за отпечатване прави възможно отпечатването на текст в различни среди.

С цел внедряване на SRP в програмен код е необходимо познаването на отговорността на всеки клас.

Следвайки принципа SRP, класовете ще се придържат към една функционалност. Предназначението на методите и данните ще е ясно. Това означава, че кодът ще притежава високо кохерентност, както и устойчивост на промени, които заедно намаляват грешките.

Въпреки че името на принципа е показателно, неговото прилагане не винаги е лесна. При разработване на проект е необходимо правилното разграничаване на отговорностите на всеки клас и обръщане внимание на кохерентността.