Kotlin - OOP에서의 SOLID

이동수·2024년 12월 17일

Kotlin

목록 보기
32/33
post-thumbnail

SRP - 단일 책임 원칙

Single Responsibility Principle

  • 설명 클래스는 단 하나의 책임만 가져야 한다
    • 하나의 클래스는 하나의 기능 또는 목적에만 집중해야 하며, 이 책임이 변경될 때만 클래스가 수정되어야함
  • 잘못된 예시 - 여러가지 일을 하는거
    • 학생정보를 가지고, 데이터 저장 둘다 함

      // 잘못된 예시: Student 클래스가 너무 많은 책임을 가짐
      class Student(val name: String, val age: Int) {
          fun saveToFile() {
              // 파일 저장 로직
          }
      }
  • 올바른 예시 - 하나의 클래스는 하나의 책임만
    • 학생정보관리, 정보저장 따로함

      // 올바른 예시: 단일 책임으로 클래스 분리
      class Student(val name: String, val age: Int)
      
      class StudentFileSaver {
          fun save(student: Student) {
              // 파일 저장 로직
          }
      }

OCP - 개방 폐쇄 원칙

Open/Closed Principle

  • 설명 확장에는 열려 있고, 수정에는 닫혀 있어야 한다는 원칙
    • 기능을 추가하거나 확장할 수 있지만, 기존 코드를 수정하지 않도록 설계
    • 인터페이스나 추상 클래스, 상속을 통해 구현
  • 잘못된 예시 - 하나를 추가하려하는데 코드 수정이 필요함
    • 결제방식을 추가할때마다 수정이 필요함

      class PaymentProcessor {
          fun processPayment(paymentType: String) {
              when (paymentType) {
                  "CreditCard" -> println("Processing credit card payment")
                  "PayPal" -> println("Processing PayPal payment")
              }
          }
      }
  • 올바른 예시 - 하나를 추가하려해도 기존 코드는 수정 필요없이, 새로운 코드만 추가하면됨
    • interface로 결제 방식 확장

    • 기존 PaymentProcessor를 수정하지 않고, 새로운 결제 클래스만 추가

      // 올바른 예시: 인터페이스로 결제 방식 확장
      interface PaymentMethod {
          fun processPayment()
      }
      
      class CreditCardPayment : PaymentMethod {
          override fun processPayment() {
              println("Processing credit card payment")
          }
      }
      
      class PayPalPayment : PaymentMethod {
          override fun processPayment() {
              println("Processing PayPal payment")
          }
      }
      
      class PaymentProcessor {
          fun processPayment(paymentMethod: PaymentMethod) {
              paymentMethod.processPayment()
          }
      }

LSP - 리스코프 치환 원칙

Liskov Substitution Principle

  • 설명 자식 클래스는 언제나 부모 클래스로 대체할 수 있어야 한다는 원칙
    • 부모 클래스가 기대하는 동작을 자식 클래스도 모두 제공해야 하며, 이를 위반할 경우 상속의 의미가 퇴색
  • 잘못된 예시 - 자식이 부모의 기대동작을 어김
    • 펭귄 날수 없음

      open class Bird {
          open fun fly() {
              println("Bird is flying")
          }
      }
      
      class Penguin : Bird() {
          override fun fly() {
              throw UnsupportedOperationException("Penguins can't fly")
          }
      }
  • 올바른 예시 - 자식들중 하나라도 어기면 부모를 분리한다
    • 날 수 있는 새와 날 수 없는 새를 구분

      open class Bird {
          // 일반적인 새의 속성
      }
      
      interface Flyable {
          fun fly()
      }
      
      class Eagle : Bird(), Flyable {
          override fun fly() {
              println("Eagle is flying")
          }
      }
      
      class Penguin : Bird() {
          // 날지 않음
      }

ISP - 인터페이스 분리 원칙

Interface Segregation Principle

  • 설명 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다는 원칙
    • 인터페이스는 클라이언트가 사용하는 메서드만 포함
    • 여러 기능을 한 인터페이스에 몰아넣으면 안된다
  • 잘못된 예시 - 인터페이스가 너무 많은 기능을 가지고 있는 경우
    • 이를 사용하지 않는 클래스도 불필요한 메서드를 구현

      // 잘못된 예시: 너무 많은 책임을 가진 Printer 인터페이스
      interface Printer {
          fun print()
          fun scan()
          fun fax()
      }
      
      class SimplePrinter : Printer {
          override fun print() {
              println("Printing...")
          }
      
          override fun scan() {
              // 구현할 필요 없는 메서드
          }
      
          override fun fax() {
              // 구현할 필요 없는 메서드
          }
      }
  • 올바른 예시 - 인터페이스 분리
    // 올바른 예시: 기능별로 인터페이스 분리
    interface Printer {
        fun print()
    }
    
    interface Scanner {
        fun scan()
    }
    
    interface Fax {
        fun fax()
    }
    
    class SimplePrinter : Printer {
        override fun print() {
            println("Printing...")
        }
    }

DIP - 의존성 역전 원칙

Dependency Inversion Principle

  • 설명 상위 모듈은 하위 모듈에 의존해서는 안 되며, 추상화에 의존해야 한다는 원칙
    • 구체적인 클래스가 아닌 인터페이스나 추상 클래스에 의존함으로써 의존성을 역전시키는 구조를 만들어야 한다
    • 구체화클래스 의존X, 추상화 클래스 의존O
  • 잘못된 예시 - 상위 클래스가 하위 클래스의 구현에 의존하는 경우
    • KeyboardMonitor 클래스가 Computer 클래스에 의존

    • ComputerKeyboardMonitor의 구체적인 구현에 의존하기 때문에 각종 변경 사항이 발생하면 Computer클래스도 같이 수정해야함

      class Keyboard {
          fun type() {
              println("Typing on keyboard")
          }
      }
      
      class Monitor {
          fun display() {
              println("Displaying on monitor")
          }
      }
      
      class Computer {
          private val keyboard = Keyboard()
          private val monitor = Monitor()
      
          fun run() {
              keyboard.type()
              monitor.display()
          }
      }
  • 올바른 예시 - 인터페이스에 의존
    • Computer 클래스는 InputDeviceDisplayDevice라는 인터페이스에 의존

    • 체적인 하위 클래스가 변경되더라도 영향을 받지 않게된다

      interface InputDevice {
          fun type()
      }
      
      interface DisplayDevice {
          fun display()
      }
      
      class Keyboard : InputDevice {
          override fun type() {
              println("Typing on keyboard")
          }
      }
      
      class Monitor : DisplayDevice {
          override fun display() {
              println("Displaying on monitor")
          }
      }
      
      class Computer(private val inputDevice: InputDevice, private val displayDevice: DisplayDevice) {
          fun run() {
              inputDevice.type()
              displayDevice.display()
          }
      }

0개의 댓글