OCP 개방 폐쇄 원칙

맥모닝·2023년 12월 8일
0

CS

목록 보기
4/8

OCP (Open Closed Principle)

  • 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙
  • 확장에 대해서는 개방적(open)이고, 수정에 대해서는 폐쇄적(closed)이어야 한다.
  • 다형성과 확장을 가능케 하는 객체지향의 장점을 극대화하는 설계 원칙

확장에 열려있다

  • 모듈의 확장성을 보장하는 것을 의미한다.
  • 새로운 변경 사항이 발생했을 때 유연하게 코드를 추가함으로써 애플리케이션의 기능을 큰 힘을 들이지 않고 확장할 수 있다.

변경에 닫혀있다

  • 객체를 직접적으로 수정하는 건 제한해야 한다는 것을 의미한다.
  • 새로운 변경 사항이 발생했을 때 객체를 직접적으로 수정해야 한다면 새로운 변경사항에 대해 유연하게 대응할 수 없는 애플리케이션이라고 말한다.

OCP 원칙 위반예제

기존 코드

class Animal(val type: String)

// 동물 타입을 받아 각 동물에 맞춰 울음소리를 내게 하는 클래스 모듈
class HelloAnimal {
    fun hello(animal: Animal) {
        when (animal.type) {
            "Cat" -> println("냐옹")
            "Dog" -> println("멍멍")
            else -> println("알 수 없는 동물")
        }
    }
}

fun main() {
    val hello = HelloAnimal()

    val cat = Animal("Cat")
    val dog = Animal("Dog")

    hello.hello(cat) // 냐옹
    hello.hello(dog) // 멍멍
}

요구사항 업데이트

fun main() {
    val hello = HelloAnimal()

    val cat = Animal("Cat")
    val dog = Animal("Dog")
    val sheep = Animal("Sheep")
    val lion = Animal("Lion")

    hello.hello(cat) // 냐옹
    hello.hello(dog) // 멍멍
    hello.hello(sheep)
    hello.hello(lion)
}
class HelloAnimal {
    // 기능을 확장하기 위해서는 클래스 내부 구성을 일일히 수정해야 하는 번거로움이 생긴다.
    fun hello(animal: Animal) {
        when (animal.type) {
            "Cat" -> println("냐옹")
            "Dog" -> println("멍멍")
            "Sheep" -> println("메에에")
            "Lion" -> println("어흥")
            // ...
            else -> println("알 수 없는 동물")
        }
    }
}
  • 문제점 : 고양이와 개 외에 양이나 사자를 추가하면 각 객체의 필드 변수에 맞게 when문을 분기하여 구성해줘야 한다.
  • HOW) 추상화 클래스를 구성하고 이를 상속하여 확장시키는 관계로 형성하기

OCP 원칙 준수예제

규칙

  1. 변경(확장)될 것과 변하지 않은 것을 엄격히 구분한다.
  2. 두 모듈이 만나는 지점에 추상화(추상클래스 or 인터페이스)를 정의한다.
  3. 구현체에 의존하기보다 정의한 추상화에 의존하도록 코드를 작성한다.
// 추상클래스를 상속만 하면 메소드 강제 구현 규칙으로 규격화만 하면 확장에 제한 없다 (opened)
abstract class Animal {
    abstract fun speak()
}

class Cat : Animal() {
    override fun speak() {
        println("냐옹")
    }
}

class Dog : Animal() {
    override fun speak() {
        println("멍멍")
    }
}

class Sheep : Animal() {
    override fun speak() {
        println("매에에")
    }
}

class Lion : Animal() {
    override fun speak() {
        println("어흥")
    }
}

// 기능 확장으로 인한 클래스가 추가되어도, 더이상 수정할 필요가 없어진다 (closed)
class HelloAnimal {
    fun hello(animal: Animal) {
        animal.speak()
    }
}

fun main() {
    val hello = HelloAnimal()

    val cat: Animal = Cat()
    val dog: Animal = Dog()

    val sheep: Animal = Sheep()
    val lion: Animal = Lion()

    hello.hello(cat) // 냐옹
    hello.hello(dog) // 멍멍
    hello.hello(sheep) // 매에에
    hello.hello(lion) // 어흥
}

OCP 원칙 적용 주의점

  • 확장에는 열려있고 변경에는 닫히게 하기 위해서는 추상화를 잘 설계할 필요성이 있는데, 추상화(추상 클래스 or 인터페이스)를 정의할 때 여러 경우의 수에 대한 고려와 예측이 필요하다.

추상화란 다른 모든 종류의 객체로부터 식별될 수 있는 객체의 본질적인 특징이다. (feat. Grady Booch)

  • 즉, 추상 메서드 설계에서 적당한 추상화 레벨을 선택함으로써, 어떠한 행위에 대한 본질적인 정의를 서브 클래스에 전파함으로써 관계를 성립되게 하는 것이다.
  • 만일 이러한 추상화에 따른 상속 구조를 처음부터 이상하게 구성하게 되면, LSP과 ISP 위반으로 이어지게 된다. 또한 OCP는 DIP의 설계 기반이 되기도 한다.

참고한 사이트

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글

관련 채용 정보