1289 단어
6 분
객체지향 SOLID
2025-01-06

SOLID#

SRP(Single Responsibility Principle)#

SRP는 단일 책임 원칙을 말한다. 말 그대로 객체는 단 하나의 책임만을 가져야 한다는 것이다. 객체지향에서 말하는 책임이란 어떤 객체의 동작이나 기능을 말한다.

이런 SRP는 프로그램의 유지보수와 관련이 있는데, 만약 어떤 클래스가 여러가지의 책임을 떠맡았다고 하자. 그러면 클래스의 메서드을 수정할때나 아니면 다른 클래스가 메서드를 가져다 쓴다고 할 때 문제가 발생한다. 책임이 너무 많은 클래스의 기능이 바뀌면 그것을 가져다가 쓰는 다른 클래스의 기능 또한 수정되어야 한다.

한 클래스에 많은 책임을 주면, 다른 클래스와 긴밀하게 결합될 가능성이 있다. 이 말은 객체의 응집도를 낮추는 것이다. 객체지향이란 객체의 응집도는 높이고, 객체 간 결합도는 낮춰야한다.

public class Invoice {
    private String customer;
    private double amount;

    public Invoice(String customer, double amount) {
        this.customer = customer;
        this.amount = amount;
    }

    public double calculateTotal() {
        return amount * 1.3;
    }
    
    public void printInvoice() {
        System.out.println("Customer: " + customer);
        System.out.println("Amount: $" + calculateTotal());
    }

    public String getCustomer() {
        return customer;
    }

    public double getAmount() {
        return amount;
    }
}

이 코드는 SRP를 위배했다고 볼 수 있다. Invoice 클래스는 세금을 계산하는 기능(calculateTotal)과 인보이스를 출력(printInvoice) 기능을 동시에 가지고 있기 때문이다.


그렇다면 printInvoice()를 분리해보자.

Invoice 클래스의 printInvoice() 메서드를 PrinInvoice 클래스에게 넘길려고 한다.

public class PrintInvoice {
    Invoice invoice;

    public PrintInvoice(Invoice invoice) {
        this.invoice = invoice;
    }

    public void print() {
        System.out.println("Customer: " + invoice.getCustomer());
        System.out.println("Amount: " + invoice.getAmount());
        System.out.println("Total: " + invoice.calculateTotal());
    }
}
public class Invoice {
    private String customer;
    private double amount;

    public Invoice(String customer, double amount) {
        this.customer = customer;
        this.amount = amount;
    }
	
    /** PrintInvoice 클래스에게 invoice 출력의 책임을 넘김 **/
    public void printInvoice() {
        new PrintInvoice(this).print();
    }

    public double calculateTotal() {
        return amount * 1.3;
    }

    public String getCustomer() {
        return customer;
    }

    public double getAmount() {
        return amount;
    }
}
public class Main {
    public static void main(String[] args) {
        Invoice invoice = new Invoice("coca_cola", 22L);
        invoice.printInvoice();
    }
}

Invoice와 PrintInvoice 객체는 각각 하나의 책임만을 갖으므로 SRP 원칙을 만족하게 된다.


OCP(Open-Close Principle)#

OCP는 확장에는 열려있고, 변경(수정)에는 닫혀 있어야 한다는 원칙이다.

만약 특정 클래스의 메서드를 다른 클래스들이 많이 가져다 쓴 상황에서 그 클래스의 메서드가 수정되면 다른 클래스들도 수정해야 할 가능성이 높아진다. 이러한 문제를 방지하기 위해서 구조 자체를 수정이 이루어지지 않게 만드는 것이다.

이때 OCP 원칙이 제대로 지켜졌다면, 새로운 코드를 추가하거나 수정해야 할 때 기존의 코드를 변경하지 않아도 된다.

  1. 확장에 열려 있다
    • 객체의 새로운 동작을 추가할 수 있다는 의미이다.
  2. 수정에 대해 닫혀 있다
    • 기존의 코드를 건드리지 않고도 객체를 확장하거나 변경할 수 있다.

이러한 OCP는 추상화를 통해서 이루어질 수 있다.


만약 다양한 알림을 전송하는 시스템이 있다고 가정하자. 알림은 SMS, 앱푸시, 이메일을 지원한다. 나중에 또 다른 알림 기능이 필요할지도 모른다.

class NotificationService {
    public void sendNotification(String type, String message) {
        if (type.equals("EMAIL")) {
            System.out.println("이메일로 보냄: " + message);
        } else if (type.equals("SMS")) {
            System.out.println("SMS로 보냄: " + message);
        } else if (type.equals("PUSH")) {
            System.out.println("app Push로 보냄 " + message);
        } else {
            throw new IllegalArgumentException("아직 지원하지 않는 알림 타입");
        }
    }
}

위의 코드는 OCP를 전혀 지켜지지 않고 있다. 이러한 코드는 나중에 다른 알림 기능을 추가할 때 직접 NotificationService를 수정해야 하는 일이 발생한다. 또한 sendNotification()를 이용하는 다른 클래스도 수정해야 할 것이다.

이러한 문제를 해결하기 위해서 추상화를 이용한다. Notification을 구현하는 Email, SMS, PUSH를 객체를 만들면 된다.

public interface Notification {
    void send(String message);
}
public class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("이메일로 보냄: " + message);
    }
}
public class PushNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("app Push로 보냄: " + message);
    }
}
public class SmsNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("SMS로 보냄: " + message);
    }
}
public class NotificationService {
    private void sendNoti(Notification notification, String message) {
        notification.send(message);
    }
}
public class Main {
    public static void main(String[] args) {
        NotificationService noti = new NotificationService();

        noti.sendNoti(new EmailNotification(), "안녕하세요");
        noti.sendNoti(new SmsNotification(), "안녕");
        noti.sendNoti(new PushNotification(), "하이");
    }
}

만약 Discord 알림 기능을 더 추가하고 싶다. 그러면 DiscordNotification 클래스만 추가해주면 된다.

// discord Notification 추가
public class DiscordNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Discord로 보냄: " + message);
    }
}

// Main 클래스
public class Main {
    public static void main(String[] args) {
        NotificationService noti = new NotificationService();

        noti.sendNoti(new DiscordNotification(), "디스코드");
    }
}

기존에 있던 NotificationService 클래스의 수정 없이 Notification을 구현한 DiscordNotification 클래스만 생성하고 기존의 코드 방식과 유사하게 작성하면 된다.

객체지향 SOLID
https://realits.me/posts/srp-ocp/
저자
realitsyourman
게시일
2025-01-06
라이선스
CC BY-NC-SA 4.0