CS

객체 지향의 설계원칙 (OOP)

Twisted 2025. 3. 4. 12:02

객체지향 설계 원칙이란?

객체지향 프로그래밍(Object-Oriented Programming, OOP)은 소프트웨어를 보다 효율적으로 설계하고 유지보수할 수 있도록 돕는 패러다임이다. 하지만 개념을 단순 적용한다고 좋은 코드가 되는 것은 아니다. 이를 위해 몇 가지 중요한 설계 원칙들이 존재하며, 이를 따르면 확장 가능하고 유지보수가 쉬운 소프트웨어를 만들 수 있다.


SOLID 원칙

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

잘못된 코드 예제:

class ReportManager {
    public void generateReport() {
        // 보고서 생성 로직
    }
    public void printReport() {
        // 보고서 출력 로직
    }
    public void saveToDatabase() {
        // 데이터베이스 저장 로직
    }
}

 

개선된 코드:

class ReportGenerator {
    public void generateReport() {
        // 보고서 생성 로직
    }
}
class ReportPrinter {
    public void printReport() {
        // 보고서 출력 로직
    }
}
class ReportSaver {
    public void saveToDatabase() {
        // 데이터베이스 저장 로직
    }
}

실무 적용:

  • 서비스 클래스에서 비즈니스 로직과 데이터베이스 처리 로직을 분리하여 유지보수성을 높인다.
  • 하나의 변경이 다른 기능에 영향을 주지 않도록 한다.

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

잘못된 코드 예제:

class PaymentProcessor {
    public void processPayment(String type) {
        if (type.equals("credit")) {
            System.out.println("Processing credit card payment");
        } else if (type.equals("paypal")) {
            System.out.println("Processing PayPal payment");
        }
    }
}

개선된 코드:

interface PaymentMethod {
    void processPayment();
}
class CreditCardPayment implements PaymentMethod {
    public void processPayment() {
        System.out.println("Processing credit card payment");
    }
}
class PayPalPayment implements PaymentMethod {
    public void processPayment() {
        System.out.println("Processing PayPal payment");
    }
}
class PaymentProcessor {
    public void process(PaymentMethod paymentMethod) {
        paymentMethod.processPayment();
    }
}

실무 적용:

  • 새로운 결제 방식 추가 시 기존 코드를 수정하지 않고 새로운 클래스를 추가할 수 있다.
  • 유지보수가 쉬워지고 버그 발생 가능성이 줄어든다.

3) 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

잘못된 코드 예제:

class Rectangle {
    protected int width, height;
    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    public int getArea() { return width * height; }
}
class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }
}

개선된 코드:

interface Shape {
    int getArea();
}
class Rectangle implements Shape {
    protected int width, height;
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    public int getArea() { return width * height; }
}
class Square implements Shape {
    private int side;
    public Square(int side) {
        this.side = side;
    }
    public int getArea() { return side * side; }
}

실무 적용:

  • 상속을 사용할 때, 하위 클래스가 부모 클래스의 기대 동작을 변경하지 않도록 주의해야 한다.
  • LSP를 위반하면 예상치 못한 버그가 발생할 가능성이 높아진다.

4) 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

실무 적용:

  • 하나의 인터페이스가 너무 많은 기능을 포함하면, 클라이언트가 필요하지 않은 기능까지 구현해야 하는 문제가 발생한다.

5) 의존 역전 원칙 (Dependency Inversion Principle, DIP)

실무 적용:

  • OCP와 DIP는 함께 사용될 때 효과적이다. DIP를 적용하면 인터페이스를 통해 확장이 가능해지고, 기존 코드 수정 없이 새로운 기능을 추가할 수 있다.
interface NotificationService {
    void sendNotification(String message);
}
class EmailNotification implements NotificationService {
    public void sendNotification(String message) {
        System.out.println("Email: " + message);
    }
}
class NotificationManager {
    private NotificationService notificationService;
    public NotificationManager(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
    public void notifyUser(String message) {
        notificationService.sendNotification(message);
    }
}

이렇게 하면 NotificationManagerNotificationService 인터페이스에 의존하므로, 새로운 알림 방식(SMS, Push 등)이 추가되더라도 기존 코드를 수정할 필요가 없다.마무리객체지향 설계를 할 때 이 원칙들을 잘 적용하면 더 나은 소프트웨어를 만들 수 있다!

 

 

마무리

SOLID 원칙을 따르면 유지보수성이 뛰어나고 확장성이 높은 소프트웨어를 만들 수 있다. 원칙 간의 관계를 고려하여 적절히 적용하는 것이 중요하다. OCP와 DIP를 함께 사용하면 새로운 기능 추가가 용이해지고, LSP를 지키면 상속 구조에서 예기치 않은 오류를 방지할 수 있다.

객체지향 설계를 할 때 이 원칙들을 잘 적용하면 더 나은 소프트웨어를 만들 수 있다