SOLID 원칙과 개방 폐쇄 원칙(OCP) | Today I Learned

hoya·2022년 8월 28일
0

Today I Learned

목록 보기
8/11
post-thumbnail

피드백은 언제나 환영합니다 :)


🚪 개방 폐쇄 원칙 (Open Closed Principle)

소프트웨어의 요소(클래스, 모듈, 함수 등)는 확장에 열려있고 변경에는 닫혀 있어야 한다.

확장과 변경, 말만 들었을 때는 이해가 될듯 싶으면서도 아리송한 부분이 있다. 예제를 보면서 이해해보자.

class 개발자(private val 언어: String) {
    fun 공부() {
        when(언어) {
            "코틀린" -> println("코틀린 개발자가 공부를 하고 있어요!")
            "자바" -> println("자바 개발자가 공부를 하고 있어요!")
            "파이썬" -> println("파이썬 개발자가 공부를 하고 있어요!")
            else -> throw Error("으잉?")
        }
    }
}

fun main() {
    val 코틀린_개발자 = 개발자("코틀린")
    val 자바_개발자 = 개발자("자바")
    val 파이썬_개발자 = 개발자("파이썬")

    코틀린_개발자.공부()
    자바_개발자.공부()
    파이썬_개발자.공부()
}

위의 코드는 굉장히 간단하다. 개발자 클래스를 선언하고, 매개 변수로 언어 를 입력받아 공부() 메소드를 실행하는 시나리오이다.

시나리오 진행에 문제가 발생할 것 같지는 않다. 그러나, 여기서 새로운 개발자가 회사에 들어오게 되고, 만약 그 개발자는 자바스크립트 개발자라면 어떻게 대응해야 할까?

class 개발자(private val 언어: String) {
    fun 공부() {
        when(언어) {
            "코틀린" -> println("코틀린 개발자가 공부를 하고 있어요!")
            "자바" -> println("자바 개발자가 공부를 하고 있어요!")
            "파이썬" -> println("파이썬 개발자가 공부를 하고 있어요!")
            "자바스크립트" -> println("자바스크립트 개발자가 공부를 하고 있어요!") // 추가
            else -> throw Error("으잉?")
        }
    }
}

fun main() {
    val 코틀린_개발자 = 개발자("코틀린")
    val 자바_개발자 = 개발자("자바")
    val 파이썬_개발자 = 개발자("파이썬")
    val 자바스크립트_개발자 = 개발자("자바스크립트")

    코틀린_개발자.공부()
    자바_개발자.공부()
    파이썬_개발자.공부()
    자바스크립트_개발자.공부() // 추가
}

클래스 내부에서 자바스크립트 에 대한 분기를 하나 더 추가해주는 모습을 확인할 수 있다. 물론 지금은 공부() 라는 메소드만 클래스 내부에 존재하기 때문에 한 줄의 코드 수정으로 변경사항에 대응할 수 있었지만, 만약 개발(), 기획(), 설계() 등의 메소드가 붙어있던 클래스였다면, 변경점이 굉장히 많이 발생했을 것이다.

나아가 만약 개발자 클래스가 다른 클래스와 연관되는 요소가 있었더라면, 해당 클래스까지 변경해야 하는 결과가 나왔을지도 모른다. 그렇게 된다면 개발자는 이렇게 이야기할 것이다.

🤬 A를 고쳤더니 B를 고쳐야하고.. B를 고쳤더니 C를 고쳐야하고.. C를 고치니 A를 다시 고쳐야하네..?


위의 간략한 시나리오를 보면서 확실히 클래스 내부를 변경하는 일은 최대한 줄여야 유지보수가 수월해질 것이라는 것을 알게 됐다.

하지만 프로젝트를 진행하며 변경사항이 생기는 것은 어쩔 수 없다. 그렇다면 어떤 식으로 변경사항을 대처하는 것이 좋은 방식일까?

바로 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리하는 것이다. 아래의 예제를 보자.

interface 개발자 {
    fun 공부()
}

class 코틀린_개발자 : 개발자 {
    override fun 공부() {
        println("코틀린 공부 중 . . .")
    }
}

class 자바_개발자 : 개발자 {
    override fun 공부() {
        println("자바 공부 중 . . .")
    }
}

class 파이썬_개발자 : 개발자 {
    override fun 공부() {
        println("파이썬 공부 중 . . .")
    }
}

class 자바스크립트_개발자 : 개발자 {
    override fun 공부() {
        println("자바스크립트 공부 중 . . .")
    }
}


fun main() {
    val 코틀린_개발자 = 코틀린_개발자()
    val 자바_개발자 = 자바_개발자()
    val 파이썬_개발자 = 파이썬_개발자()
    val 자바스크립트_개발자 = 자바스크립트_개발자()

    코틀린_개발자.공부()
    자바_개발자.공부()
    파이썬_개발자.공부()
    자바스크립트_개발자.공부()
}

공부() 메소드의 전개는 각 개발자의 언어 에 따라 달라질 수 밖에 없다. 그렇기 때문에 interface로 따로 분리하고, 공부() 메소드가 필요한 클래스에서 이를 상속받아 오버라이딩 메소드로 구현하며 확장하는 방식으로 리팩터링을 진행했다.

이런 코드 전개가 진행되면, 나중에 다른 언어의 전문 개발자가 생기더라도 interface 를 변경하지 않고 확장할 수 있게 된다. 즉, 새로운 개발자가 생겼다는 변경사항에도 유연하게 대처가 가능해지는 것이다.

개방 폐쇄 원칙은 사실 객체 지향 프로그래밍과 디자인 패턴의 핵심 원칙이라고 보아도 무방하다. 다른 원칙도 중요하지만, 개방 폐쇄 원칙은 객체 지향 프로그래밍의 근간이 되는 원칙이니 최대한 머릿 속에 각인하며 잊지 않도록 노력하자.


✍️ TL;DR

  • 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.
  • 변경을 위한 비용은 최소화, 확장을 위한 비용은 극대화
profile
즐겁게 하자 🤭

0개의 댓글