JAVA

[JAVA]객체 지향 프로그래밍의 5가지 원칙 (SOLID 원칙) 예시와 개념

채야미 2024. 2. 5. 16:06

SOLID란?

SOLID 원칙은 클린코드로 유명한 로버트 마틴 정의한 좋은 객체 지향 설계의 5가지 원리입니다.
소프트웨어 개발을 유연하고 유지보수가 쉽게하기 위해 만들어진 원칙이라고 보면 됩니다. 

 

1. SRP 단일 책임의 원칙 - Single responsibility principle

 

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 변경이 있을 때 코드 변경이 적다면 그것은 단일 책임의 원칙을 잘 준수한것 (기능 별로 책임, 계층이 잘 나눠져 있음)

    클래스  Order { 
      생성자 ( orderDetails ) { 
        this . orderDetails = 주문 세부정보; 
      } 
      계산Total ( ) { 
        // 총 주문 금액 계산
       } 
      generateInvoice ( ) { 
        // 송장 생성
       } 
    }​


    예시 코드에서는 Order 클랴스가 주문을 계산하고 invoice(명세서)까지 같이 작성 합니다. 이는 SRP를 위반했기때문에 책임을 다른 클래스를 만들어서 분리해야 합니다.

 

2. OCP 개방 폐쇄 원칙 - Open/closed principle

 

  • 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다
  • 기존 코드 변경을 최대한 줄일 수 있는
    -> 다형성을 사용하기
  • 인터페이스를 구현한 클래스를 하나 더 만들어서 새로운 기능을 구현하는 것

 

3. LSP 리스코프 치환 원칙 - Liskov substitution principle 

 

  • 프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다
  • 다형성에서 하위 클래스는 인터페이스 규약을 지켜야한다는 의미
  • 쉽게 말해 하위 클래스가 상위 클래스의 기능으로 대체 되어야 한다는 뜻

    class Bird {
        public void fly() {
            System.out.println("Bird is flying");
        }
    }
    
    class Penguin extends Bird {
        // 리스코프 원칙을 위반하는 메서드 오버라이딩
        @Override
        public void fly() {
            System.out.println("Penguin cannot fly");
        }
    }
    
    class Sparrow extends Bird {
        // 추가적인 메서드
        public void tweet() {
            System.out.println("Sparrow is tweeting");
        }
    }​
    위의 예시에서 처럼 펭귄은 날 수 없는데 부모 클래스로 대체하였을때 "Bird is flying"이라는 메소드가 실행되므로 부모 클래스로 자식 클래스가 대체 될수 없습니다. 리스코프 원칙을 위반하는 예시입니다. 

 

4. ISP 인터페이스 분리 원칙 - Interface segregation principle

 

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리
  • 더 작은 클라이언트별 인터페이스를 통해 각각의 클라이언트가 필요한 것만 구현, 종속성 낮추기
  • 인터페이스가 명확해짐, 대체 가능성이 높아진다

    // 단일 대형 인터페이스 대신 
    class  Worker { 
      work ( ) { 
        // 작업 수행
       } 
    eat ( ) { 
        // 먹기 수행
       } 
      sleep ( ) { 
        // 수면 수행
       } 
    } 
    // 더 작은 클라이언트별 인터페이스 사용 
    class  Worker { 
      work ( ) { 
        // 작업 수행
       } 
    } 
    class  Eater { 
      eat ( ) { 
        // 식사 수행
       } 
    } 
    class  Sleeper { 
      sleep ( ) { 
        // 수면 수행
       } 
    }​

    워커를 만들어서 3개의 다른 작업을 하게하는 것이 아닌, 작은 단위로 행동별로 사람을 만들어 불필요한 종속성을 줄
    였습니다.

5. DIP 의존관계 역전 원칙 - D ependency Inversion Principle

 

  • 프로그래머는 "추상화에 의존해야하고, 구체화에 의존하면 안된다"
  • 구현 클래스에 의존하는 것이 아니라, 인터페이스에 의존해야한다는 의미
    -> 운전자는 자동차 인터페이스에 대해 알아야하는것이지
    특정한 자동차인 K3에 대해 알고 그것으로 코드를 작성하면 안됨!
  • 인터페이스에 의존하면 변경이 유연해진다

// 추상 인터페이스
interface Switchable {
    void turnOn();
    void turnOff();
}

// 저수준 모듈 (구체적인 구현)
class LightBulb implements Switchable {
    @Override
    public void turnOn() {
        // Turn on the light bulb
    }

    @Override
    public void turnOff() {
        // Turn off the light bulb
    }
}

// 고수준 모듈
class Switch {
    private final Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate() {
        // 사용하는 장치의 상태를 변경
        // LightBulb 또는 다른 Switchable 인터페이스를 구현한 클래스들이 올 수 있음
        device.turnOn();
        // 또는 device.turnOff();
    }
}


위의 예시에서는 switch 클래스에 device만 설정해 준다면
Switchable로 구현된 어떠한 전구더라도 Switch클래스에서 코드 변경없이 사용될 수 있습니다.