서론

 

객체 지향과 디자인 패턴 - YES24

객체 지향 안내서. 객체, 책임, 의존, 캡슐화 등 객체 지향의 주요 개념들을 쉬운 예제와 그림을 통해 이해하기 쉽게 설명한다.

www.yes24.com

안녕하세요 이번에는 최근에 읽었던 기술서적 객체 지향과 디자인 패턴의 내용중 OCP(개발 폐쇄 원칙)에 대해 개념을 예제코드들과 함께 정리해보려고 합니다 :)

 

 

 

개방-폐쇄 원칙(OCP: Open-Closed Principle)

먼저 개방 폐쇄 원칙이란 객체지향 개발 5대 원칙 중 하나로서  소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다는 원칙입니다.

개발 폐쇄 원칙은 곧 기존 코드를 변경하지 않으면서(Close) 기능을 추가(Open)할 수 있도록 설계가 되어야 한다는 원칙입니다.

이 원칙을 지키기 위해서 보편적으로 추상화와 다형성을 활용합니다.

 

아래에서 예제코드와 함께 살펴보겠습니다.

 

 

OCP 적용 전

class User {
    readonly userId: string

    constructor(userId: string){
        console.log(userId)

        this.userId = userId
    }
        
    toStr(): string{
        return `User(userId=${this.userId})`
    }
}

class Account {
    loginKakao(user: User){
        console.log(`Login [userId=${user.userId}]`)
    }

    logoutKakao(user: User){
        console.log(`Logout [userId=${user.userId}]`)
    }

    loginNaver(user: User){
        console.log(`Login [userId=${user.userId}]`)
    }

    logoutNaver(user: User){
        console.log(`Logout [userId=${user.userId}]`)
    }
}

const user = new User('lks')
const account = new Account()
account.loginKakao(user)
account.logoutKakao(user)

만약 이런 구조로 소프트웨어를 설계했다면 더많은 oauth(google 등등)를 사용해야 할 때

Account 클래스를 수정해야합니다.

곧 Account클래스가 확장과 수정 모두에 대해 열려있다는 것을 뜻하므로 OCP 원칙을 위반하는 코드입니다.

수정엔 닫혀있는 코드를 만들기 위해 OCP원칙을 지키는 코드를 만들어 보겠습니다.

 

 

OCP 적용 후

class User {
    readonly userId: string

    constructor(userId: string){
        console.log(userId)

        this.userId = userId
    }
        
    toStr(): string{
        return `User(userId=${this.userId})`
    }
}

abstract class Account {
    public abstract login(user: User): void;
    public abstract logout(user: User): void;

}

class KakaoAuth extends Account {
    public login(user: User){
        console.log(`Login [userId=${user.userId}]`)
    }

    public logout(user: User){
        console.log(`Logout [userId=${user.userId}]`)
    }
}

class NaverAuth extends Account {
    public login(user: User){
        console.log(`Login [userId=${user.userId}]`)
    }

    public logout(user: User){
        console.log(`Logout [userId=${user.userId}]`)
    }
}

const user = new User('lks')
const account = new NaverAuth()
account.login(user)
account.logout(user)

login(), logout 추상 메서드를 가지고 있는 Account 추상 클래스를 정의하고, 실제 로그인 로그아웃 로직을 구현할 KakaoAuth, NaverAuth 클래스(Account 클래스를 상속)를 정의합니다.

OCP를 위반하는 코드일 때는 구글 로그인 로그아웃을 구현해야한다면 Account에 새로운 클래스를 만들고 호출하는 곳에서의 변경도 불가피했습니다.

const user = new User('lks')
const account = new Account()
account.loginKakao(user)
account.logoutKakao(user)
const user = new User('lks')
const account = new Account()
account.loginGoogle(user)
account.logoutGoogle(user)

이런식으로 Account에 새로운 메서드를 정의하는 것은 물론 메서드가 호출되는 부분들은 모두 수정해야 했죠

 

이제는 Account를 상속하는 GoogleAuth 클래스만 구현하면 됩니다.

class GoogleAuth extends Account {
    public login(user: User){
        console.log(`Login [userId=${user.userId}]`)
    }

    public logout(user: User){
        console.log(`Logout [userId=${user.userId}]`)
    }
}

그리고 GoogleAuth 클래스를 사용하여 User의 로그인 로그아웃을 구현할 수 있게됩니다.

const user = new User('lks')
const account = new GoogleAuth()
account.login(user)
account.logout(user)

위와 같이 다형성을 사용한다면 Account클래스는 수정 없이 새로운 OAuth를 추가할 수 있게 되어 OCP를 따르게 할 수 있습니다.

 

 

정리

OCP는 해당 클래스의 기존 동작을 변경하지 않고(코드의 변경에 닫혀있어야 하고) 클래스의 동작을 확장(확장에 열려있어야)하는 것을 목표로 합니다.

그리고 유지 관리 및 수정이 쉬운 코드를 작성하는데 필수적입니다.

그리고 개인적으로 OOP의 원칙 중 비교적 떠올리기, 적용하기 쉬운 원칙이라고 생각합니다.

OCP가 코드의 재사용성을 높이고 유지보수를 용이하게 만들지만 과도하게 적용할 시에는 설계가 필요이상으로 복잡해질 수 있다는 점을 유의하고 적용하면 좋겠습니다 :)

 

반응형

+ Recent posts