객체지향 프로그래밍(OOP)

[객체지향 설계] SOLID 원칙 심화: LSP, ISP, DIP 개념과 예제 코드 완벽 정리

CodeCaine Explorer 2024. 12. 16. 23:55
728x90
반응형
SMALL

2024.10.30 - [객체지향 프로그래밍(OOP)] - [쉬운설명]SOLID 원칙이란?

 

[쉬운설명]SOLID 원칙이란?

SOLID 원칙이란? 🌟SOLID는 좋은 코드 설계를 위한 다섯 가지 원칙의 약자입니다. 이 원칙을 따르면 코드가 더 이해하기 쉽고, 유지보수가 쉬워지고, 확장 가능해져요. 하나씩 쉽게 설명해볼게요!1.

alswnsghd1234.tistory.com

 

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

  • 쉽게 말하면: "자식 클래스는 부모 클래스를 대신해서 사용될 수 있어야 해요."
    📌 즉, 부모 클래스를 사용하는 코드는 자식 클래스를 사용해도 문제없이 동작해야 합니다.

예시: "도형의 넓이를 계산하기"

Java 코드

// LSP를 지키지 않은 코드 😥
class Rectangle {
    protected double width;
    protected double height;

    public void setWidth(double width) {
        this.width = width;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public double getArea() {
        return width * height; // 사각형 넓이
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(double width) {
        this.width = this.height = width; // 정사각형은 가로와 세로가 같아야 함
    }

    @Override
    public void setHeight(double height) {
        this.width = this.height = height;
    }
}

// 동작 테스트
public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(5);
        rectangle.setHeight(10);
        System.out.println("Rectangle Area: " + rectangle.getArea()); // 50

        Rectangle square = new Square(); // 부모 클래스 타입으로 자식 클래스 사용
        square.setWidth(5);
        square.setHeight(10); // 가로와 세로가 같아짐, 의도와 다르게 동작
        System.out.println("Square Area: " + square.getArea()); // 100 (잘못된 결과)
    }
}

Python 코드

# LSP를 지키지 않은 코드 😥
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def get_area(self):
        return self.width * self.height  # 사각형 넓이 계산


class Square(Rectangle):
    def set_width(self, width):
        self.width = self.height = width  # 정사각형의 가로와 세로는 같아야 함

    def set_height(self, height):
        self.width = self.height = height


# 동작 테스트
rectangle = Rectangle(5, 10)
print("Rectangle Area:", rectangle.get_area())  # 50

square = Square(5, 10)
square.set_width(5)
square.set_height(10)  # 정사각형이므로 가로=세로가 됨
print("Square Area:", square.get_area())  # 100 (잘못된 결과)

코드가 왜 잘못됐나요? 😥

  1. Square 클래스가 Rectangle 클래스를 대체했지만, 정사각형의 규칙 때문에 예상과 다르게 동작합니다.
  2. LSP를 어김으로 인해 Rectangle을 사용하는 코드에서 오류가 발생했어요.

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

  • 쉽게 말하면: "사용하지 않는 기능은 구현하지 않아야 해요."
    📌 큰 인터페이스 하나를 여러 작은 인터페이스로 나누어 필요한 것만 구현하게 해야 합니다.

예시: "동물의 행동"

Java 코드

// ISP를 지키지 않은 코드 😥
interface Animal {
    void eat();
    void fly(); // 모든 동물이 날 수는 없음
}

class Dog implements Animal {
    public void eat() {
        System.out.println("Dog eats");
    }

    public void fly() {
        throw new UnsupportedOperationException("Dogs cannot fly"); // 강아지는 날 수 없어요!
    }
}

// ISP를 지킨 코드 😊
interface Eater {
    void eat();
}

interface Flyer {
    void fly();
}

class Dog implements Eater {
    public void eat() {
        System.out.println("Dog eats");
    }
}

class Bird implements Eater, Flyer {
    public void eat() {
        System.out.println("Bird eats");
    }

    public void fly() {
        System.out.println("Bird flies");
    }
}

Python 코드

# ISP를 지키지 않은 코드 😥
class Animal:
    def eat(self):
        pass

    def fly(self):
        pass  # 모든 동물이 날 수는 없음

class Dog(Animal):
    def eat(self):
        print("Dog eats")

    def fly(self):
        raise NotImplementedError("Dogs cannot fly")  # 강아지는 날 수 없어요!

# ISP를 지킨 코드 😊
class Eater:
    def eat(self):
        pass

class Flyer:
    def fly(self):
        pass

class Dog(Eater):
    def eat(self):
        print("Dog eats")

class Bird(Eater, Flyer):
    def eat(self):
        print("Bird eats")

    def fly(self):
        print("Bird flies")

5. DIP (Dependency Inversion Principle) - 의존성 역전 원칙

  • 쉽게 말하면: "고수준 모듈은 저수준 모듈에 의존하지 않고, 둘 다 추상화된 인터페이스에 의존해야 해요."
    📌 이를 통해 코드를 더 유연하고 확장 가능하게 만들 수 있어요.

예시: "알림 기능 구현"

Java 코드

// DIP를 지키지 않은 코드 😥
class EmailService {
    public void sendEmail(String message) {
        System.out.println("Email sent: " + message);
    }
}

class Notification {
    private EmailService emailService;

    public Notification() {
        this.emailService = new EmailService(); // EmailService에 의존
    }

    public void notify(String message) {
        emailService.sendEmail(message);
    }
}

// DIP를 지킨 코드 😊
interface MessageService {
    void sendMessage(String message);
}

class EmailService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Email sent: " + message);
    }
}

class SMSService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("SMS sent: " + message);
    }
}

class Notification {
    private MessageService messageService;

    public Notification(MessageService messageService) {
        this.messageService = messageService; // 인터페이스에 의존
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

Python 코드

# DIP를 지키지 않은 코드 😥
class EmailService:
    def send_email(self, message):
        print(f"Email sent: {message}")

class Notification:
    def __init__(self):
        self.email_service = EmailService()  # EmailService에 의존

    def notify(self, message):
        self.email_service.send_email(message)

# DIP를 지킨 코드 😊
class MessageService:
    def send_message(self, message):
        pass

class EmailService(MessageService):
    def send_message(self, message):
        print(f"Email sent: {message}")

class SMSService(MessageService):
    def send_message(self, message):
        print(f"SMS sent: {message}")

class Notification:
    def __init__(self, message_service):
        self.message_service = message_service  # 인터페이스에 의존

    def notify(self, message):
        self.message_service.send_message(message)

요약! 🌟

  1. SRP: 한 클래스는 하나의 일만!
  2. OCP: 기존 코드는 건드리지 말고, 새 기능은 확장!
  3. LSP: 자식 클래스는 부모를 완벽히 대체 가능해야!
  4. ISP: 필요 없는 기능은 구현하지 말자!
  5. DIP: 구체적인 구현 대신 인터페이스에 의존하자!

결론: SOLID 원칙을 지키면 코드는 더 유연하고, 읽기 쉽고, 수정하기 쉬워져요. ✨

반응형
SMALL