Java

[Java] 단일 책임 원칙(SRP)과 개방-폐쇄 원칙(OCP)

Castle Bird 2025. 11. 25. 23:12

1. 단일 책임 원칙 - SRP (Single Responsibility Principle)

객체는 단 하나의 책임만 가져야 한다는 원칙이다. 여기서 책임이란 기능을 말한다.

하나의 클래스는 한 가지의 기능만을 수행해야한다

 

실생활 예시를 들어보자.

단일 책임 원칙에 맞지 않은 도구는 대표적으로 멀티툴이 있다.

 

 

하나의 도구로 칼,가위,니퍼,손톱깍이 등등 많은 기능들이 한 가지 도구에 기능이 집합되어있다.
이것은 코드적으로 보았을 때 유지보수가 어려운 코드로 비교될 수 있다.

코드적으로 좋은 상황으로 만든다면 칼,가위,니퍼,손톱깍이 등등을 모두 따로 만드는 것이 아주 이상적일 것이다.

실생활 예시를 들어보았으니 코드 예시로 넘어가보자.

class User {
    private String name;
    private String email;

    public void saveToDatabase() {
        // DB 저장 로직
    }

    public void sendEmail(String message) {
        // 이메일 전송 로직
    }
}

 

위 코드는 단일 책임 원칙에 맞지 않은 코드이다. 하나씩 살펴보자.
현재 User라는 객체가 있다. 이 객체는 유저의 정보(name, email)와 DB 저장(saveToDatabase), 이메일 전송(sendEmail) 총 3가지의 기능을 가지고 있다.

 

이런식으로 한 객체의 볼륨이 커지고 기능이 많아진다면 추후 유지보수의 어려움이 생길 확률이 매우 높다.

이제 이 코드를 단일 책임 원칙에 맞는 코드로 변경해보자

class User {
    private String name;
    private String email;
}

class UserRepository {
    public void save() {
        // DB 저장 로직
    }
}

class EmailService {
    public void sendEmail() {
        // 이메일 전송 로직
    }
}

 

위의 코드는 유저의 정보와 각 기능 2가지를 적절히 분배한 코드이다. 이렇게 각 클래스에게 한 가지의 기능만을 책임지게 한다면 추후 유지보수에서 유연하게 작업을 이어나갈 수 있을 것이다.


2. 개방-폐쇄 원칙 - OCP (Open Closed Principle)

  • 확장에는 열려 있고, 수정에는 닫혀 있어야 한다는 설계 원칙
  • 기존 코드를 변경하지 않고, 새로운 기능은 확장을 통해 추가할 수 있도록 설계하라는 뜻

즉, 기존 코드를 매번 수정하면 오류가 생길 위험이 증가하기에 기존 코드는 웬만하면 수정 불가능하게 하라는 뜻.

코드 예시를 들어보자.

class PayService {

    public void pay(String type) {

        if (type.equals("카카오페이")) {
            System.out.println("카카오페이로 결제...");
        } else if (type.equals("토스")) {
            System.out.println("토스페이로 결제...");
        } else if (type.equals("네이버")) {
            System.out.println("네이버페이로 결제...");
        }else {
            System.out.println("존재하지 않는 결제 수단입니다.");
        }
    }
}

 

위 코드는 결제수단/방법을 정의하는 클래스 및 메서드다. 만약 결제 수단이 추가되어 삼성페이를 추가해야된다고 가정해보자.
삼성페이를 추가하기 위해선 이미 사용하고 있는 pay메서드 PaymentService를 수정해야하는 번거로움이 생긴다.
이 경우 수정에 닫혀 있어야 한다, 확장에 열려 있어야 한다.라는 개방-폐쇄 원칙 - OCP과 맞지 않는 상황이 발생한다.

개방-폐쇄 원칙 - OCP을 지킨 코드를 보자.


interface PayService {
    void pay();
}

class KakaoPay implements PayService {

    @override
    public void pay() {
        System.out.println("카카오페이로 결제...");
    }
}

class TossPay implements PayService {

    @override
    public void pay() {
        System.out.println("토스페이로 결제...");
    }
}

 

위 코드는 결제를 담당하는 공통 pay메서드를 interface로 분류하여 각 결제 방법을 나타내는 클래스에 구현시킨 클래스이다.
이제 이 PayService를 구현하는 객체들은 pay메서드 생성을 강제할 수 있으며 각 결제 방법마다 다른 클래스를 사용하기에 유지보수성도 크게 증가한다.

 

이렇게 공통된 기능들을 추상화하여 interface추출한다면 기존 코드는 수정하지 않고, 확장에 용이하게 작업할 수 있다.

'Java' 카테고리의 다른 글

[Java] Spring의 탄생 배경  (0) 2025.12.11
[Java] HashSet  (0) 2025.12.07
[Java] Stream - map과 flatMap  (0) 2025.11.26
[Java] 싱글톤 패턴  (0) 2025.11.18
[Java/Set] HashSet, LinkedHashSet, TreeSet  (0) 2025.10.21