ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 객체지향 5대 원칙 - SOLID (단일책임원칙, 개방폐쇄원칙, 리스코프치환 원칙, 인터페이스분리원칙, 의존역전원칙)
    전산학/소프트웨어공학 2024. 7. 30. 21:50
    728x90
    반응형

    객체지향 개발에서 SOLID 원칙은 코드의 유연성과 유지보수성을 높이기 위한 다섯 가지 설계 원칙을 말합니다. 각 원칙과 이에 대한 Java 코드 예시는 다음과 같습니다:

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

    • 설명: 하나의 클래스는 하나의 책임만 가져야 한다. 즉, 클래스는 하나의 기능만 수행해야 하며, 그 기능에 대해서만 책임져야 한다.

    Java 코드 예시:

    // 위반된 예시: User 클래스가 사용자 데이터 처리와 이메일 전송 두 가지 책임을 가짐
    class User {
        private String name;
        private String email;
    
        public User(String name, String email) {
            this.name = name;
            this.email = email;
        }
    
        public void sendEmail(String message) {
            // 이메일 전송 로직
            System.out.println("Sending email to " + email + ": " + message);
        }
    
        // 사용자 데이터 관련 메소드들
        public String getName() {
            return name;
        }
    
        public String getEmail() {
            return email;
        }
    }
    
    // 수정된 예시: 책임을 분리한 두 개의 클래스
    class User {
        private String name;
        private String email;
    
        public User(String name, String email) {
            this.name = name;
            this.email = email;
        }
    
        // 사용자 데이터 관련 메소드들
        public String getName() {
            return name;
        }
    
        public String getEmail() {
            return email;
        }
    }
    
    class EmailService {
        public void sendEmail(User user, String message) {
            // 이메일 전송 로직
            System.out.println("Sending email to " + user.getEmail() + ": " + message);
        }
    }

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

    • 설명: 소프트웨어 요소(클래스, 모듈 등)는 확장에 열려 있어야 하고, 수정에는 닫혀 있어야 한다. 즉, 기능을 확장할 수 있지만, 기존 코드를 변경하지 않고 기능을 추가할 수 있어야 한다.

    Java 코드 예시:

    // 위반된 예시: 새로운 할인 정책을 추가할 때마다 기존 코드를 수정해야 함
    class DiscountService {
        public double calculateDiscount(String type, double price) {
            if (type.equals("seasonal")) {
                return price * 0.1;
            } else if (type.equals("clearance")) {
                return price * 0.5;
            }
            return 0;
        }
    }
    
    // 수정된 예시: 할인 정책을 확장할 수 있도록 구조 변경
    interface DiscountStrategy {
        double applyDiscount(double price);
    }
    
    class SeasonalDiscount implements DiscountStrategy {
        @Override
        public double applyDiscount(double price) {
            return price * 0.1;
        }
    }
    
    class ClearanceDiscount implements DiscountStrategy {
        @Override
        public double applyDiscount(double price) {
            return price * 0.5;
        }
    }
    
    class DiscountService {
        public double calculateDiscount(DiscountStrategy strategy, double price) {
            return strategy.applyDiscount(price);
        }
    }

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

    • 설명: 서브 클래스는 언제나 자신의 기반 클래스(Base class)로 교체할 수 있어야 한다. 즉, 자식 클래스는 부모 클래스의 기능을 온전히 수행해야 하며, 부모 클래스의 계약을 위반해서는 안 된다.

    Java 코드 예시:

    // 위반된 예시: Square는 Rectangle을 상속하지만, 기능이 왜곡됨
    class Rectangle {
        protected int width;
        protected int 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) {
            this.width = width;
            this.height = width; // 정사각형이므로 width와 height가 같아야 함
        }
    
        @Override
        public void setHeight(int height) {
            this.height = height;
            this.width = height; // 정사각형이므로 width와 height가 같아야 함
        }
    }
    
    // 수정된 예시: 상속 관계를 피하고, 개념을 분리함
    interface Shape {
        int getArea();
    }
    
    class Rectangle implements Shape {
        private int width;
        private int height;
    
        public Rectangle(int width, int height) {
            this.width = width;
            this.height = height;
        }
    
        @Override
        public int getArea() {
            return width * height;
        }
    }
    
    class Square implements Shape {
        private int side;
    
        public Square(int side) {
            this.side = side;
        }
    
        @Override
        public int getArea() {
            return side * side;
        }
    }

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

    • 설명: 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다. 즉, 인터페이스는 특정 클라이언트를 위한 최소한의 기능만을 포함해야 한다.

    Java 코드 예시:

    // 위반된 예시: 모든 기능을 하나의 인터페이스에 몰아넣음
    interface Worker {
        void work();
        void eat();
    }
    
    class HumanWorker implements Worker {
        @Override
        public void work() {
            System.out.println("Human working");
        }
    
        @Override
        public void eat() {
            System.out.println("Human eating");
        }
    }
    
    class RobotWorker implements Worker {
        @Override
        public void work() {
            System.out.println("Robot working");
        }
    
        // Robot은 eat() 메소드를 필요로 하지 않지만, 구현해야 함
        @Override
        public void eat() {
            throw new UnsupportedOperationException("Robots do not eat");
        }
    }
    
    // 수정된 예시: 인터페이스를 분리하여 필요 없는 메소드 구현을 방지함
    interface Workable {
        void work();
    }
    
    interface Eatable {
        void eat();
    }
    
    class HumanWorker implements Workable, Eatable {
        @Override
        public void work() {
            System.out.println("Human working");
        }
    
        @Override
        public void eat() {
            System.out.println("Human eating");
        }
    }
    
    class RobotWorker implements Workable {
        @Override
        public void work() {
            System.out.println("Robot working");
        }
    }

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

    • 설명: 고수준 모듈(정책 결정)이 저수준 모듈(세부 구현)에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다. 또한, 추상화는 구체적인 것에 의존해서는 안 된다. 즉, 구현보다 인터페이스에 의존해야 한다.

    Java 코드 예시:

    // 위반된 예시: 고수준 모듈이 저수준 모듈에 의존함
    class Keyboard {
        public void type() {
            System.out.println("Typing on keyboard");
        }
    }
    
    class Computer {
        private Keyboard keyboard;
    
        public Computer() {
            this.keyboard = new Keyboard(); // 저수준 모듈에 직접 의존
        }
    
        public void typeOnKeyboard() {
            keyboard.type();
        }
    }
    
    // 수정된 예시: 인터페이스를 통해 의존성을 역전함
    interface InputDevice {
        void input();
    }
    
    class Keyboard implements InputDevice {
        @Override
        public void input() {
            System.out.println("Typing on keyboard");
        }
    }
    
    class Computer {
        private InputDevice inputDevice;
    
        public Computer(InputDevice inputDevice) {
            this.inputDevice = inputDevice; // 추상화에 의존
        }
    
        public void typeOnInputDevice() {
            inputDevice.input();
        }
    }

    이렇게 SOLID 원칙을 따르면, 소프트웨어 시스템의 유연성과 유지보수성이 높아지며, 코드 재사용성도 향상됩니다. 각 원칙은 독립적으로 적용될 수 있지만, 함께 사용되었을 때 가장 큰 효과를 발휘합니다.

    SOLID는 객체 지향 소프트웨어 개발에서 좋은 설계를 위한 다섯 가지 원칙의 약어입니다. 이 원칙들은 로버트 C. 마틴(애칭: "아저씨" 마틴)에 의해 소개되었으며, 유지보수가 쉽고 확장 가능하며 재사용 가능한 소프트웨어 시스템을 만드는 데 도움을 줍니다. 각 원칙의 의미와 목적은 다음과 같습니다:

    1. **단일 책임 원칙 (Single Responsibility Principle, SRP)**:
      • **설명**: 클래스는 단 하나의 책임만 가져야 합니다. 즉, 클래스는 한 가지 기능만 담당해야 합니다.
      • **목적**: 변경의 이유를 최소화하여 클래스를 보다 단순하고 이해하기 쉽게 만듭니다. 이는 유지보수와 확장이 용이하도록 합니다.
    2. **개방-폐쇄 원칙 (Open/Closed Principle, OCP)**:
      • **설명**: 소프트웨어 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 합니다.
      • **목적**: 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 시스템을 확장할 수 있도록 합니다. 이는 버그가 발생할 가능성을 줄이고 시스템 안정성을 높입니다.
    3. **리스코프 치환 원칙 (Liskov Substitution Principle, LSP)**:
      • **설명**: 서브타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 합니다. 즉, 서브 클래스는 부모 클래스의 기능을 온전히 수행해야 합니다.
      • **목적**: 객체 지향 시스템에서 다형성을 사용하여 코드를 더 유연하게 만들고, 예상치 못한 동작을 방지합니다.
    4. **인터페이스 분리 원칙 (Interface Segregation Principle, ISP)**:
      • **설명**: 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫습니다. 즉, 인터페이스는 클라이언트가 사용하는 메서드들만 포함해야 합니다.
      • **목적**: 인터페이스의 크기를 작게 만들어 클라이언트가 필요하지 않는 메서드를 구현하지 않도록 합니다. 이는 클라이언트와의 결합도를 낮추고 시스템의 유연성을 높입니다.
    5. **의존 역전 원칙 (Dependency Inversion Principle, DIP)**:
      • **설명**: 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 합니다. 또한, 추상화는 구체적인 사항에 의존하지 않아야 합니다.
      • **목적**: 모듈 간의 의존성을 줄이고, 변화에 더 잘 대처할 수 있도록 합니다. 이를 통해 시스템을 더 유연하고 재사용 가능하게 만듭니다.

    이 다섯 가지 원칙을 따르면 객체 지향 시스템의 설계가 더 견고해지고 유지보수가 용이하며, 확장이 쉬워집니다. 각 원칙은 코드의 품질을 높이기 위해 특정한 측면에서의 모범 사례를 따르도록 합니다.

    물론입니다. 각 원칙을 객체지향 언어(여기서는 Python)로 설명해 보겠습니다.

    1. 단일 책임 원칙 (SRP)

    **잘못된 예:**
    ```python
    class User:
    def __init__(self, username, email):
    self.username = username
    self.email = email

    def save\_to\_database(self):  
        # 데이터베이스에 사용자 정보 저장  
        pass  
    
    def send\_email(self, message):  
        # 이메일 보내기  
        pass  

    ```

    **SRP 적용 예:**
    ```python
    class User:
    def __init__(self, username, email):
    self.username = username
    self.email = email

    class UserRepository:
    def save(self, user):
    # 데이터베이스에 사용자 정보 저장
    pass

    class EmailService:
    def send_email(self, user, message):
    # 이메일 보내기
    pass
    ```

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

    **잘못된 예:**
    ```python
    class Rectangle:
    def __init__(self, width, height):
    self.width = width
    self.height = height

    class AreaCalculator:
    def calculate(self, shape):
    if isinstance(shape, Rectangle):
    return shape.width * shape.height
    # 새로운 도형을 추가하려면 이 메서드를 수정해야 함
    ```

    **OCP 적용 예:**
    ```python
    class Shape:
    def area(self):
    pass

    class Rectangle(Shape):
    def __init__(self, width, height):
    self.width = width
    self.height = height

    def area(self):  
        return self.width \* self.height  

    class Circle(Shape):
    def __init__(self, radius):
    self.radius = radius

    def area(self):  
        return 3.14 \* self.radius \* self.radius  

    class AreaCalculator:
    def calculate(self, shape):
    return shape.area()
    ```

    3. 리스코프 치환 원칙 (LSP)

    **잘못된 예:**
    ```python
    class Bird:
    def fly(self):
    pass

    class Penguin(Bird):
    def fly(self):
    raise Exception("펭귄은 날 수 없습니다.")
    ```

    **LSP 적용 예:**
    ```python
    class Bird:
    pass

    class FlyingBird(Bird):
    def fly(self):
    pass

    class Sparrow(FlyingBird):
    def fly(self):
    # 참새가 나는 방법 구현
    pass

    class Penguin(Bird):
    # 펭귄은 날지 않으므로 fly 메서드가 없음
    pass
    ```

    4. 인터페이스 분리 원칙 (ISP)

    **잘못된 예:**
    ```python
    class Worker:
    def work(self):
    pass

    def eat(self):  
        pass  

    class HumanWorker(Worker):
    def work(self):
    # 일하는 방법 구현
    pass

    def eat(self):  
        # 먹는 방법 구현  
        pass  

    class RobotWorker(Worker):
    def work(self):
    # 일하는 방법 구현
    pass

    def eat(self):  
        raise Exception("로봇은 먹을 수 없습니다.")  

    ```

    **ISP 적용 예:**
    ```python
    class Workable:
    def work(self):
    pass

    class Eatable:
    def eat(self):
    pass

    class HumanWorker(Workable, Eatable):
    def work(self):
    # 일하는 방법 구현
    pass

    def eat(self):  
        # 먹는 방법 구현  
        pass  

    class RobotWorker(Workable):
    def work(self):
    # 일하는 방법 구현
    pass
    ```

    5. 의존 역전 원칙 (DIP)

    **잘못된 예:**
    ```python
    class LightBulb:
    def turn_on(self):
    pass

    def turn\_off(self):  
        pass  

    class Switch:
    def __init__(self, light_bulb):
    self.light_bulb = light_bulb

    def operate(self):  
        self.light\_bulb.turn\_on()  
        # 스위치가 직접 LightBulb 클래스에 의존  

    ```

    **DIP 적용 예:**
    ```python
    class Switchable:
    def turn_on(self):
    pass

    def turn\_off(self):  
        pass  

    class LightBulb(Switchable):
    def turn_on(self):
    # 전구 켜기 구현
    pass

    def turn\_off(self):  
        # 전구 끄기 구현  
        pass  

    class Switch:
    def __init__(self, device: Switchable):
    self.device = device

    def operate(self):  
        self.device.turn\_on()  
        # 스위치가 추상화된 Switchable 인터페이스에 의존  

    ```

    이 예제들은 SOLID 원칙을 적용하여 객체 지향 설계를 개선하는 방법을 보여줍니다. 각 원칙은 코드의 유지보수성과 확장성을 높이는 데 도움이 됩니다.

    728x90
    반응형
공기업 전산 필기 후기 + 공기업 전산학 지식 모음