SOLID 원칙 - (1) 단일 책임 원칙 (SRP)



(1) 단일 책임 원칙 (SRP)

👴 Gather together the things that change for the same reasons. Separate things that change for different reasons.

마틴 아저씨는 위와 같이 말했다. 같은 이유로 변하는 애들을 모아라. 다른 이유로 변하는 애들은 분리해라. 객체 지향에 대입해보면, 클래스에는 단 한 가지의 책임을 할당해야하고, 클래스를 변경하는 이유는 단 한 개여야 한다는 말이다.

객체 지향의 기본은 객체에 책임을 할당하는 데에 있다. 객체가 가지는 책임(역할)이 많아질수록 한 책임에서의 변화가 다른 곳에 미치는 영향은 증가할 것이다. 이는 결국 유지보수의 어려움까지 이어진다.


EX1) 유지보수 관점에서의 SRP

(상황 ) 도서관 대출 서비스가 있다. 시민의 정보에 따라 책을 대출해주는 서비스이다.

Librarian이라는 사서는 월급도 쥐꼬리만큼 받는데 여러 가지의 책임을 갖는다.

1. 서버에서 데이터 가져옴
2. 가져온 데이터를 파싱함
3. 시민이 대출 가능한지 여부 확인 (연체일수가 0이면 대출 가능)
4. 대출

서버에서 보내주는 데이터와, Librarian의 코드는 다음과 같다.

{
	name : "Seulgi Kim",
	late : 2
}
public class Librarian {
    public void reserve(){
        // 1. 네 고객님.. 성함이..?
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        // 2. 네 잠시만요.. 타닥타닥.. 서버에서 시민의 정보 가져옴
        String data = connectToServer(name);
        int late = getLate(data);

        // 3. 대출 여부 확인
        if(late == 0) {
            // 4. 대출
            System.out.println("대출 가능합니다.");
        }
        else {
            System.out.println("대출은 무슨!");
        }
    }
    public String connectToServer(String name){
        /* get data from server */
        /* 데이터 가져오는 코드가 존재한다고 가정 */
        return "{ name : \"Seulgi Kim\"," +
                " lateDate : 2 }";
    }
    public int getLate(String data){
        /* parse logic */
        /* 파싱 로직 존재한다고 가정 */
        return 2;
    }
}

:heavy_exclamation_mark: 여기서 만약 서버에서 보내주는 데이터가 변경되면? :heavy_exclamation_mark:

서버에서 보내주는 데이터가 int에서 boolean으로 바뀌었다고 가정하자.

{
	name : "Seulgi Kim",
	late : true
}

그렇다면 Librarian의 코드는 다음과 같이 바뀐다.

public class Librarian {
    public void reserve(){
        // 1. 네 고객님.. 성함이..?
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        // 2. 네 잠시만요.. 타닥타닥.. 서버에서 시민의 정보 가져옴
        String data = connectToServer(name);
        boolean late = getLate(data);

        // 3. 대출 여부 확인
        if(!late) {
            System.out.println("대출 가능합니다.");
        }
        else {
            System.out.println("대출은 무슨!");
        }
    }
    public String connectToServer(String name){
        /* get data from server */
        /* 데이터 가져오는 코드가 존재한다고 가정 */
        return "{ name : \"Seulgi Kim\"," +
                " lateDate : true }";
    }
    public boolean getLate(String data){
        /* parse logic */
        /* 파싱 로직 존재한다고 가정 */
        return true;
    }
}

바뀐 부분

  • 데이터 파싱 로직
  • 대출 여부 확인 로직

등이 바뀌었다.

Librarian이 여러 책임을 가지고 있었기 때문에 한 책임에서의 변화가 다른 책임에게도 영향을 준 것이다.

😆 친구들에게 이 예시를 보여줬더니, 사서가 고생할 필요없이 데이터 형식을 바꾼 서버 개발자를 까면 된다고 했다 😆


🤔 해결) 만약 아르바이트를 고용해서 책임을 나눠준다면? 🤔

사서는 더 이상 참을 수 없어서 아르바이트를 고용했다. 자신의 일부 책임을 분리해서 알바한테 시키기로 했다.

Librarian 사서의 책임

1. 대출

PartTimeJob 알바의 책임

1. 서버에서 데이터 가져옴
2. 가져온 데이터를 파싱함
3. 시민이 대출 가능한지 여부 확인
public class PartTimeJob {
    private String connectToServer(String name){
        /* get data from server */
        return "{ name : \"Seulgi Kim\"," +
                " lateDate : true }";
    }
    private boolean getLate(String data){
        /* parse logic */
        return true;
    }
		// 네ㅠㅠ 이 사람 대출 가능한지 여부 보내드립니다..
    public boolean check(String name) {
        String data = connectToServer(name);
        boolean late = getLate(data);
        return late;
    }
}
public class Librarian {
    public void reserve(){
        // 1. 네 고객님 ^^ 성함이 어떻게 되세요? ^^
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        // 2. 야 알바! 데이터 가져와
        PartTimeJob arbeit = new PartTimeJob();
        boolean isReservationAvailable = arbeit.check(name);

        // 3. 대출해드릴게요~
        if(isReservationAvailable){
            System.out.println("대출 가능합니다.");
        }
        else {
            System.out.println("대출은 무슨!");
        }
    }
}

위와 같이 책임을 분리했다. 알바가 데이터를 관리하고, 사서는 대출만 해주는 것으로 바뀌었다. 사서는 이제 대출해주는 행위에만 신경쓰면 된다!!


😝 만약 데이터 형식이 bool -> int로 다시 바뀐다면? 😝

public class PartTimeJob {
    private String connectToServer(String name){
        /* get data from server */
        return "{ name : \"Seulgi Kim\"," +
                " lateDate : 2 }";
    }
    private int getLateDate(String data){
        /* parse logic */
        return 2;
    }
    public boolean check(String name) {
        String data = connectToServer(name);
        int lateDate = getLateDate(data);
        return lateDate == 0;
    }
}
public class Librarian {
    public void reserve(){
        // 1. 네 고객님 ^^ 성함이 어떻게 되세요? ^^
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        // 2. 야 알바! 데이터 가져와
        PartTimeJob arbeit = new PartTimeJob();
        boolean isReservationAvailable = arbeit.check(name);

        // 3. 대출해드릴게요~
        if(isReservationAvailable){
            System.out.println("대출 가능합니다.");
        }
        else {
            System.out.println("대출은 무슨!");
        }
    }
}

사서는 자신의 일을 바꿀 필요 없이 알바생만 죽어나면 된다! 👏


EX2) 재사용 관점에서의 SRP

(상황 ) 도서관 내에 새로운 직업이 생겼다. 도서관 문앞에 경호원 Bodyguard가 생겼고 경호원은 대출이 연체된 사람은 입장도 못하게 막는다!

도서관은 예산이 딸려서 알바를 고용하지 못한 상태다 ㅠㅠ

import java.util.Scanner;

public class BodyGuard {
    public void letIn(){
        // 1. 어서오세요.. 성함이?
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        // 2. 네 잠시만요..
        String data = connectToServer(name);
        boolean lateDate = getLateDate(data);

        // 3. 입장 가능한지 확인
        if(!lateDate) {
            System.out.println("입장 가능합니다.");
        }
        else {
            System.out.println("나가!");
        }
    }
    public String connectToServer(String name){
        /* get data from server */
        return "{ name : \"Seulgi Kim\"," +
                " lateDate : true }";
    }
    public boolean getLateDate(String data){
        /* parse logic */
        return true;
    }
}

알바를 고용하지 않았으므로(책임을 분리하지 않았으므로) 아까 사서가 했던 일을 똑같이 그대로 해야한다.


:interrobang: 경호원이 알바한테 데이터 확인을 시킨다면? :interrobang:

public class BodyGuard {
    public void letIn(){
        // 1. 어서오세요~ 도서관입니다~ 성함이 어떻게 되세요?
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        // 2. 야 알바! 데이터 가져와
        PartTimeJob arbeit = new PartTimeJob();
        boolean isEnterAvailable = arbeit.check(name);

        // 3. 연체일수가 없으면 입장 가능
        if(isEnterAvailable) {
            System.out.println("입장 가능합니다.");
        }
        else {
            System.out.println("나가!");
        }
    }
}

사서와 경호원 모두 알바를 재사용함으로써 코드는 감소한다!


결론

단일 책임 원칙은 변경의 여파를 줄이기 위해, 재사용을 용이하게 하기 위해 적용한다.


그렇다면 단일 책임 원칙은 어떻게 지켜요?

메소드를 실행하는 애들을 확인하면 된다.

img

B를 변경하는 경우 X만 변경되고, C를 변경하는 경우 Y만 변경된다면 A의 X책임과 Y책임을 분리하자!



:bookmark: REFERENCE
최범균, 「개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴」, 인투북스
Solid Relevance