밤에 쓴 코드

OOP ) OCP -개방폐쇄 원칙(Open-closed principle) 본문

OOP

OOP ) OCP -개방폐쇄 원칙(Open-closed principle)

붱이🦉 2019. 5. 18. 21:26

OCP -개방폐쇄 원칙

(Open-closed principle)

확장에 개방적이고 , 변경에 폐쇄적이어야한다.

음? 처음에 확장과 변경이 그렇게 다른 것인지 의문을 가졌었다.

일단 확장은 기능의 추가이고 , 변경은 기능구현의 변경이다.

기능을 추가할때는 딱 기능만 추가할 수 있게 , 즉 기존의 다른 부분들에 수정이 필요 없게 ,

기능이 변경이 , 이 기능을 가져다 쓰는 다른 코드들에 영향을 주지않게 해야한다는 것이다.

개발을 접하지 않은 사람들이 보기에 어찌보면 당연하게 들릴 것이다.

기능을 추가하면 기능만 추가 되면 그만이지. 변경되면 변경만 되면 그만이지 라고 생각 할 수 있다 .

이 과정은 생각보다 쉽지 않다 . 하지만 우리는 저 어렵지만 당연한 생각 을 실천해야한다.

변경에 폐쇄적이려면 어떻게 해야할까?

일단 변경될수 있는 부분과 변경이 안될 부분을 나누어 보자.

일반적으로 인터페이스는 무엇일까?

나는 인터페이스는 변하지 않을 것이라고 외부에게 알려주는 일종의 약속이라고 생각한다.

통용적으로 이해할 수 있는 인터페이스는 UI (User Interface ) 같은 것이 있을 것이다.

스크린샷 2019-05-18 오후 5 54 04

파란색으로 표시된 영역을 보자 . 아마 이 홈페이지를 처음보는 사람들도 알 수 있을 것이다 .

IOS_Training 이라는 파란색글자는 클릭시 , 저기와 관련된 어떤 페이지로. 이동할 것이라는 것을 누가 알려주지 않아도 알 수있다.

또 그 밑에 꼼꼼한 재은씨 - 실전편 공부 라고 주황색으로 표시된 부분을 보면 , 클릭시 이동은 하지않고 , 저 노란색으로 감싸진 영역에 대한 설명 을 하고 있다는 것을 누가 알려주지 않아도 모두 알 것이다.

이것이 인터페이스이고 , 이것은 변하지 않는 약속일 것이다 .

하지만 페이지가 이동되는 원리에 대해 이 페이지를 보는 사람은 알 필요가 없다.

또 페이지를 이동하는 원리가 바뀐다해도 , 이동은 무조건 할 것이기 때문에 사용자는 원리에 대해 자세히 알 필요가 없고 ,

누르면 이동한다는 사실만 알면된다.

여기서 변하지 않는 부분저 파란색글씨를 누르면 이동할 것이다라는 약속이다.

변하는 부분은 어디일까 ? 브라우저가 페이지를 이동하는 원리는 , 기술의 발전에 따라서 더 좋은 성능을 위해 다른 방식을 사용 할 수도있다. 저 원리가 바뀐다고 , 파란색글씨를 누르면 이동한다는 약속을 바뀌지않는다.

위의 예가 바로 변하는 부분과 변하지 않는 부분이 잘 나뉘어있어서 사용자가 혼란스럽지 않은 예라고 생각한다.

코드로도 비슷한 적용이 가능하다.

롤 캐릭터로 설명할 것이니 , 모른다면 한번해보고 마저 읽어보자.

struct 블리츠크랭크{
    public func 그랩(){
        print("👋")
    }
    public func 전자기장(){
        print("⚡️⚡️⚡️⚡️")
    }
}
// InGame
func 게임하기(내캐릭터: 블리츠크랭크){
    내캐릭터.그랩()
    내캐릭터.정전기장()
}

이런 식으로 게임을 하고있다.

하지만 어느날 업데이트를 통해 캐릭터의 수정에 생겼다.

struct 블리츠크랭크{
//    public func 그랩(){
//        print("👋")
//    }
    public func 강철주먹(){
        print("👊")
    }
    public func 정전기장(){
        print("⚡️⚡️⚡️⚡️")
    }
}

이 수정은 기존의 코드를 부서뜨릴 것이다.

// InGame
func 게임하기(내캐릭터: 블리츠크랭크){
    내캐릭터.그랩() // 컴파일에러
    내캐릭터.정전기장()
}

이것은 그랩() 이라는 변경될 수있는 부분을 public 으로 만들어둔 개발자의 잘못이다.

변경될 부분을 숨겨두고 , 변경되지 않는 부분만 밖에 노출시켜줘야하는 데 , 그걸 어긴 대가는 이런 코드의 수정을 야기한다.

인터페이스 즉 - 변하지 않을 부분을 분리해보자

protocol 롤캐릭터 {
    func Q()
    func R()
} // 롤캐릭터가 무조건 해야하는 약속 / 변경될 수 없는 부분
struct 블리츠크랭크: 롤캐릭터 {
    public func Q() {  
        그랩() 
    }

    public func R() {
        정전기장()
    }

    private func 그랩(){   
        print("👋")
    }

    private func 정전기장(){
        print("⚡️⚡️⚡️⚡️")
    }
}

코드의 양이 늘어났다. 여기서 주목해야할 것은 변하지 않을 부분과 변하는 부분을 나누었고 , 그에따른 접근제한자 또한 그에 맞게 설정 되었다는 것이다.

변하지 않을 부분에 대해서는 protocol 이라는 외부와의 약속으로 분리했고 , 변하는 부분에 대해서는 private 로 외부에서 모르게 만들어 주었다 .

스크린샷 2019-05-18 오후 6 38 26

이렇게 외부에서는 약속해둔곳 ( 변하지 않을 부분 ) 만 알 수있고 , 변할 부분은 모르게 되었다.

struct 블리츠크랭크: 롤캐릭터 {
    public func Q() {
        강철주먹()
    }

    public func R() {
        정전기장()
    }

//    private func 그랩(){
//        print("👋")
//    }
    private func 강철주먹(){
        print("👊")
    }

    private func 정전기장(){
        print("⚡️⚡️⚡️⚡️")
    }
}

이렇게 패치를 통해 스킬이 변경된 다고 해도 , 위처럼 코드가 고장나지는 않는다 .

약속한 메시지를 보내면 , 구현부가 달라져서 다른 구현을 할 뿐 이다.

이렇게 변하지 않는 부분과 변하는 부분을 나눔으로써 , 변경에 폐쇄적이게 되었다.

그런 데 또 이번에는 캐릭터가 추가가 되었다.

struct 티모: 롤캐릭터 {
    public func Q() {
        맹독다트()
    }

    public func R() {
        버섯함정()
    }

    private func 맹독다트(){
        print("🧪")
    }

    private func 버섯함정(){
        print("🍄")
    }
}
func 게임하기(내캐릭터: 블리츠크랭크){
    내캐릭터.Q()
    내캐릭터.R()
}

캐릭터가 추가됨에 따라서 위의 게임하기 메소드의 수정이 필요할 것으로 보인다.

티모로는 게임을 할 수가 없으니까 .

이것은 확장에 대해 개방적이어야 하는 원칙에 어긋난 것이다 .

너무 구체적인 타입에 의존해서 생긴 문제가 되겠다.

func 게임하기(내캐릭터: 롤캐릭터){
    내캐릭터.Q()
    내캐릭터.R()
}

위처럼 바꿈으로서 , 조금더 유연한 확장이 가능해졌다.

이건 무조건 적인 확장이 아니다. 약속을 지키고 있는 누구나 들어와도 된다는 것이다.

그렇다면 확장은 약속을 지키는 확장에 대해서는 개방적이게 된 것이다 .

처음보다 훨씬 더 유연해지고 확장에 개방적이고 내부구체적인것의 수정에 폐쇄적으로 보인다.

Comments