단일 책임 원칙(SRP) 기본 개념

2025. 6. 14. 20:56·개발/java
728x90

단일 책임 원칙(SRP)의 모든 것: 기본 원칙부터 실제 적용까지

단일 책임 원칙(SRP)은 객체지향 설계의 핵심 원칙인 SOLID 중 첫 번째(S)에 해당하는, 가장 기본적이면서도 중요한 원칙입니다. 이 원칙을 이해하고 적용하는 것은 유지보수하기 좋은 코드를 작성하는 첫걸음입니다.

1. 핵심 원리: "변경의 이유가 단 하나여야 한다"

**단일 책임 원칙(SRP)**이란, 하나의 클래스는 단 하나의 책임만 가져야 한다는 원칙입니다.

여기서 '책임'이란 "클래스를 변경해야 하는 이유(A reason for change)" 를 의미합니다. 즉, 어떤 클래스를 수정해야 할 이유가 여러 개가 아닌 단 하나뿐이어야 한다는 뜻입니다.

만약 한 클래스가 보고서 내용을 만드는 책임과 보고서를 특정 형식(PDF)으로 출력하는 책임을 모두 갖게 되면, 다음과 같은 두 가지 다른 이유로 클래스를 수정해야 합니다.

  1. 보고서에 포함될 내용이 바뀔 때
  2. 보고서를 출력하는 방식(예: PDF 라이브러리 변경, CSV 형식 추가)이 바뀔 때

이렇게 여러 책임을 가진 클래스는 관련 없는 코드가 섞여 응집도(Cohesion)는 낮아지고, 다른 기능과의 결합도(Coupling)는 높아져 결국 유지보수를 어렵게 만듭니다.

2. 코드로 배우는 SRP 적용 (책임의 분리)

로봇의 상태 보고서를 생성하고, 다양한 형식으로 내보내는 예시를 통해 SRP를 적용하는 과정을 살펴보겠습니다.

Before: SRP 위반 사례

하나의 RobotStatusReport 클래스가 보고서 데이터를 만드는 책임과 데이터를 PDF로 내보내는 책임을 모두 갖고 있습니다.

// SRP 위반: 보고서 내용 생성 + PDF 출력 책임을 모두 가짐
class RobotStatusReport {
    // ... 필드 및 생성자 생략 ...

    // 1. 책임: 보고서 내용 생성
    public String generateReportData() {
        // ... 보고서 내용 생성 로직 ...
    }

    // 2. 책임: 보고서를 PDF 형식으로 내보내기
    public void exportToPdf() {
        String reportData = generateReportData();
        // ... PDF 라이브러리를 사용해 파일로 저장하는 로직 ...
        System.out.println("PDF 저장 완료.");
    }
}

문제점: 위에서 언급했듯이, 내용이 바뀌거나 출력 방식이 바뀔 때마다 이 클래스를 수정해야 합니다.

After: SRP 적용 사례

'데이터 생성'과 '출력'이라는 두 책임을 아래와 같이 별도의 클래스로 분리합니다.

// 책임 1: 보고서 데이터와 구조를 정의하는 클래스
class Report {
    private String title;
    private String content;

    public Report(String title, String content) {
        this.title = title;
        this.content = content;
    }
    
    // Getter 메소드들...
    public String getTitle() { return title; }
    public String getContent() { return content; }
}

// 책임 2: 보고서를 특정 형식으로 외부에 출력하는 클래스
class ReportExporter {
    public void exportToPdf(Report report) {
        System.out.println("'" + report.getTitle() + ".pdf' 파일로 저장 중...");
        System.out.println(report.getContent());
        System.out.println("PDF 저장 완료.");
    }
    
    public void exportToCsv(Report report) {
        System.out.println("'" + report.getTitle() + ".csv' 파일로 저장 중...");
        System.out.println(report.getContent().replace("\\n", ","));
        System.out.println("CSV 저장 완료.");
    }
}

이제 각 클래스는 변경되어야 할 이유가 단 하나뿐입니다. Report는 데이터 구조가 바뀔 때, ReportExporter는 출력 방식이 바뀔 때만 수정됩니다.

3. 분리된 클래스의 '조정과 협력' (Facade 패턴의 적용)

그렇다면 SRP를 지켜 분리된 클래스들은 어떻게 함께 사용해야 할까요? 여기서 두 클래스를 사용하는 제3의 클래스를 만드는 것은 SRP를 위반하는 것이 아니라, 오히려 더 나은 설계로 가는 과정입니다.

이 새로운 클래스는 '보고서 생성과 출력을 포함한 전체 프로세스를 조정하고 관리하는 것' 이라는 자신만의 단일 책임을 갖습니다.

새로운 '서비스' 클래스의 등장

이 클래스는 클라이언트의 복잡한 요청을 대신 처리해주는 창구(Facade) 역할을 합니다.

// 책임 3: 보고서 생성과 출력 과정을 '조정'하고 '서비스'를 제공
class ReportService {
    // ReportExporter 라는 부품을 사용 (의존성)
    private final ReportExporter exporter;

    public ReportService(ReportExporter exporter) {
        this.exporter = exporter;
    }

    /**
     * 클라이언트로부터 필요한 최소한의 정보만 받아
     * 보고서 생성과 PDF 출력의 전 과정을 책임지고 수행합니다.
     */
    public void createAndExportPdfReport(String robotId, double batteryLevel, String currentLocation) {
        // 1. 보고서 내용 생성 로직 (내부적으로 처리)
        String reportContent = String.format("--- 로봇 상태 보고서 ---\\n로봇 ID: %s\\n배터리: %.1f%%\\n위치: %s\\n",
                robotId, batteryLevel, currentLocation);

        // 2. Report 객체 생성 위임
        Report report = new Report(robotId + "_Status", reportContent);
        
        // 3. ReportExporter 객체에 출력 위임
        exporter.exportToPdf(report);
    }
}

단순해진 클라이언트 코드

클라이언트는 이제 복잡한 내부 과정을 알 필요 없이, ReportService가 제공하는 간단한 메소드만 호출하면 됩니다.

public class Main {
    public static void main(String[] args) {
        // 준비: 필요한 부품(Exporter)과 서비스(Service) 객체를 생성
        ReportExporter exporter = new ReportExporter();
        ReportService reportService = new ReportService(exporter);

        // 사용: 클라이언트는 이제 "Q800 로봇의 PDF 보고서를 만들어줘" 라고 서비스에 요청만 하면 됨
        reportService.createAndExportPdfReport("Q800", 87.5, "A-1 구역");
    }
}

4. 종합 효용성 및 결론

SRP를 기반으로 책임을 분리하고, 이를 조정하는 서비스 계층을 둠으로써 우리는 다음과 같은 가치를 얻게 됩니다.

  • 높은 응집도 & 낮은 결합도: 각 클래스는 자신의 책임에만 집중하므로 응집도가 높아지고, 다른 클래스와의 불필요한 의존성이 사라져 결합도가 낮아집니다.
  • 관심사의 분리 (SoC): 클라이언트(Main), 서비스(ReportService), 부품(Report, ReportExporter)이 각자의 관심사에만 집중할 수 있습니다. 클라이언트는 서비스의 내부 구현을 전혀 몰라도 됩니다.
  • 유지보수성 및 테스트 용이성:
    • 보고서 내용이 바뀌면 ReportService의 내용 생성 로직만 수정합니다.
    • 출력 방식(PDF, CSV)이 바뀌면 ReportExporter만 수정합니다.
    • 전체 프로세스(예: 보고서 출력 전 DB에 로그 남기기)가 바뀌면 ReportService만 수정합니다.
    • 각 클래스의 역할이 명확하여 단위 테스트가 매우 용이해집니다.

결론적으로, 단일 책임 원칙은 단순히 클래스를 작게 쪼개는 것이 아니라, '변경의 이유'에 따라 책임의 단위를 명확히 나누는 것입니다. 그리고 잘 분리된 책임들을 더 큰 단위의 서비스로 조합함으로써, 변경에 유연하고 확장하기 쉬우며 유지보수가 용이한 소프트웨어 아키텍처를 구축하는 핵심적인 발판이 됩니다.

728x90

'개발 > java' 카테고리의 다른 글

Collections.max() 함수 설명  (0) 2025.05.12
스트림 API를 사용하여 HashMap을 반환하는 방법  (0) 2025.05.11
HashMap의 자주 사용되는 함수  (0) 2025.05.11
lock.lock()과 lock.tryLock() 비교  (0) 2025.05.08
ReentrantLock 이란  (0) 2025.05.08
'개발/java' 카테고리의 다른 글
  • Collections.max() 함수 설명
  • 스트림 API를 사용하여 HashMap을 반환하는 방법
  • HashMap의 자주 사용되는 함수
  • lock.lock()과 lock.tryLock() 비교
nix-be
nix-be
  • nix-be
    NiX
    nix-be
  • 전체
    오늘
    어제
    • 홈
      • 책
        • 오브젝트
      • 성장
        • jpa Querydsl 정리
        • 코딩테스트
      • 인프라
        • linux
        • vmware
        • CI&CD
        • 네트워크
        • docker
      • 개발
        • spring boot
        • JPA
        • java
        • thymeleaf
        • 이슈
        • jquery
        • javascript
        • 안드로이드
      • DB
        • postgreSql
      • 잡다한것
        • 프로그램
        • 일상 관련
      • 회사
        • 티
  • 블로그 메뉴

    • 홈
    • 개발
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
nix-be
단일 책임 원칙(SRP) 기본 개념
상단으로

티스토리툴바