단일 책임 원칙(SRP)

박시시·2022년 10월 19일
0

SOLID

목록 보기
1/5

SOLID란 객체지향 5대 원칙을 말한다. SRP(단일 책임 원칙), OCP(개방폐쇄 원칙), LSP(리스코프 치환 원칙), DIP(의존 역전 원칙), ISP(인터페이스 분리 원칙) 이 다섯가지의 앞글자를 따서 만든 원칙이다. 해당 원칙을 준수해가며 소프트웨어를 설계하게 된다면 유지보수와 확장에 유리한 애플리케이션을 구축할 수 있다.

SRP란

개인적으로 다섯가지 원칙 중 가장 이해하기도, 준수하기도 어려운 원칙 아닐까 한다. 책임이라는 기준이 애매하기도 하며 이로인해 해당 원칙의 개념도 조금 붕떠있는 감이 없지 않아 있다.

SRP란 각 클래스는 하나의 책임, 단일 목적을 가져야만 한다는 뜻이다.

그렇다면 '책임'이란 무엇일까? 책임은 소프트웨어의 변경을 요청하는 특정 사용자들에 대해 클래스/함수가 갖는 것이다. 풀어서 설명해보자면, 애플리케이션에 대해 특정 사용자가 무언가를 변경하고 싶어 요청을 하면, 그 해당 요청에 대해 특정 클래스가 대응을 할 텐데 이때 이 클래스가 해당 요청에 대한 책임을 갖고 있다고 할 수 있다.

그럼 '특정사용자'는 누구인가? 특정 사용자라 했으나 개개인의 하나의 특별한 유저를 말하는 것이 아니다. 그들이 수행하는 역할로 나눈 일종의 그룹이라 할 수 있다. 이렇게 특정 역할을 수행하는 유저를 actor라 한다. 즉 책임은 개개인의 유저가 아닌 actor와 연결된다는 것을 알 수 있다.

SRP를 다시 정리해보자. 클래스는 하나의 책임을 가져야 한다. 이 클래스는 그러므로 특정 actor의 요구사항을 만족시키기 위해 일련의 메서드들로 이루어져 있다. actor의 요구사항 변경은 클래스의 변경 이유가 된다. 바꿔 말하자면, 클래스를 수정해야 할 이유는 단 하나여야 한다(actor의 요구사항).

예제

public class Book {
    public String getTitle() {
        return "A Great Book";
    }
 
    public String getAuthor() {
        return "John Doe";
    }
 
    public int turnPage() {
        // pointer to next page
    }
 
    public void printCurrentPage() {
        system.out.println("current page content");
    }
}

위의 클래스를 통해 책과 관련된 정보를 얻을 수 있다. 그리고 현재 페이지를 출력할 수도 있다.
'그리고'가 들어갔다면 책임이 여러개인지 의심해봐야 한다.
'도서관 사서'라는 액터가 있다고 하자. 지금까지는 책의 제목만 가져와도 됐었다. 하지만 도서관 사서가 앞으로는 책의 제목과 함께 작가의 이름을 같이 가져올 수 있도록 요구사항을 변경했다고 하자. 이 경우 위의 Book 클래스를 수정해야 한다.
또한 데이터 출력 메커니즘 혹은 관리자 라는 액터가 있다고 하자. 지금까지는 콘솔에 출력을 해도 됐으나 html 출력 기능을 붙이고자 한다. 이 경우에도 위의 book 클래스를 수정하게 된다.

이는 명백히 SRP를 어긴 것이라 할 수 있다. 클래스를 수정해야 하는 이유가 여러개 발생했기 때문이다. 아래와 같이 책임을 분리하여 클래스를 작성하면 좀 더 유연한 설계가 된다.

public class Book {
 
    public String getTitle() {
        return "A Great Book";
    }
 
    public String getAuthor() {
        return "John Doe";
    }
 
    public String turnPage() {
        // pointer to next page
    }
 
    public void getCurrentPage() {
        system.out.println("current page content");
    }
 
}
 
public interface Printer {
    void printPage();
}
 
public class PlainTextPrinter implements Printer {
 
    public void printPage() {
        system.out.println("this is plain text printer");
    }
 
}
 
public class HtmlPrinter implements Printer {
 
    public void printPage() {
        system.out.println("this is html printer");
    }
 
}

응집력

무턱대고 클래스를 잘게 쪼개선 안된다. 클래스의 책임을 명확히 정의하여 이를 분리했다면, 그 클래스 내 메서드들은 응집력이 높은 상태가 될 것이다. 예를 들어보자.

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;
    }
}

위의 클래스의 책임은 명확히 텍스트 조작이다. 특정한 하나의 actor가 해당 클래스를 사용하게 될 것이다. 또한 '텍스트 조작'이라는 책임, 목표를 위해 충분히 응집되어있다. 응집성을 생각하지 않았을 때, 텍스트 쓰기와 텍스트 업데이트를 다른 하나의 책임으로 보고 분리하려 들 수도 있다. 이러한 점을 경계해야 한다. 해당 클래스를 사용하는 액터는 텍스트 조작을 위해 해당 클래스를 사용할 것이므로 텍스트 조작을 위한 메서드를 응집해 두어야 한다.

0개의 댓글