객체지향 5원칙(SOLID) - 개방 폐쇄 원칙

2022. 2. 15. 15:45PHP

객체지향 5원칙

올바른 객체지향 설계를 위해 수립한 원칙이 있으며, 이 다섯 가지의 원칙을 통틀어 객체지향 5원칙(SOLID)이라 명명한다. 필수로 적용하지 않지만, 적어도 이 규칙을 준수하면 준수할 수록 올바르게 설계된 객체지향이라 할 수 있습니다.

 

이 다섯 가지 원칙은 아래와 같습니다.

1. 단일 책임 원칙 (Single Responsibility Principle)

2. 개방 폐쇄 원칙 (Open-Closed Principle)

3. 리스코프 치환 원칙 (Liskov Substitution Principle)

4. 인터페이스 분리 원칙 (Interface Segregation Principle)

5. 의존성 역전 원칙 (Dependency Inversion Principle)

 

각 원칙의 영어 앞글자를 따서 SOLID원칙이라고 합니다.

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

개방 폐쇄 원칙이란 객체를 다룸에 있어서 객체의 확장은 개방적으로, 객체의 수정은 폐쇄적으로 대하는 원칙입니다.

쉽게 말해서, 기능이 변하거나 확장을 필요로 하는 경우 해당 기능의 코드는 수정하면 안 된다는 뜻입니다. 그런데 이 원칙은 말이 좀 헷갈립니다. 기능이 변하는 것 O, 확장되는 것 O 근데 코드를 수정하면 안된다? 이 부분에 대해서 설명해보겟습니다. 

만약, 객체 하나를 수정한다고 가정해 봅시다. 이 때 단순히 해당 객체만 수정하는 것 뿐만 아니라 해당 객체에 의존하는 다른 객체들의 코드까지 줄줄이 고쳐야한다면 좋은 설계로 보기 어렵습니다. 대표적으로 라이브러리를 예로 들겠습니다.

라이브러리를 사용하는 객체의 코드가 변경된다고 해서 라이브러리 코드까지 변경하지는 않습니다.

 

이처럼 개방-폐쇄 원칙은 각 객체의 모듈화와 정보 은닉의 올바른 구현을 추구하며, 이를 통해 객체 간의 의존성을 최소화하여 코드 변경에 따른 영향력을 낮추기 위한 원칙입니다.

간단한 코드로 예시를 구현해보겠습니다.(PHP)

상황)

3개의 카드사별로 각각의 다른 결과물을 나타내는 객체가 있다고 가정해봅니다.

만약 카드사가 추가되어야 하는 상황을 적용해 개방 폐쇄의 원칙을 적용해보겠습니다.

 

개방 폐쇄의 원칙을 지키지 않은 코드

/**
 * 포스 클래스
 *
 * @author RWB
 * @since 2021.08.14 Sat 02:10:12
 */
class Pos{
    /**
     * 결제 및 결과 반환 함수
     *
     * @param card : [Array] 카드 배열
     * @param price: [int] 금액
     *
     * @return [boolean] 결제 결과
     */

    public function purchase($card, $price){
        
        $result=array();
        switch (strtoupper($card)){
            case "A": array_push($result,array("신한카드"=>$price));break;
            case "B": array_push($result,array("농협카드"=>$price));break;
            case "C": array_push($result,array("카카오카드"=>$price));break;
            default :
                print("유효하지 않은 카드사");
                $result = false;
                break;
        }
        return $result;
    }
}
$A = new Pos();
$result = $A -> purchase("C",30000);
print($result[0]."사 ".$result[1]."원 결제 요청");
//출력 
카카오카드사 30000원 결제 요청

 

위의 코드를 개방 폐쇄의 원칙을 적용한 코드로 변경해보겠습니다.

/**
 * 포스 클래스
 *
 * @author RWB
 * @since 2021.08.14 Sat 02:10:12
 */
class Pos{
    /**
     * 결제 및 결과 반환 함수
     *
     * @param purchasable : [Purchasable] Purchasable 인터페이스
     * @param price: [int] 금액
     *
     * @return [boolean] 결제 결과
     */
    public function purchase(Purchasable $purchasable,$price){
        return $purchasable->send($price);
    }
}

/**
 * 결제 인터페이스
 *
 * @author RWB
 * @since 2021.08.14 Sat 02:28:22
 */
interface Purchasable{
    /**
     * 카드사 정보 전송 및 결과 반환 함수
     *
     * @param price: 금액
     *
     * @return [boolean] 전송 결과
     */
    public function send($price);
}

/**
 * A 카드 객체
 *
 * @author RWB
 * @since 2021.08.14 Sat 02:36:11
 */
class CardA implements Purchasable{
    /**
     * 카드사 정보 전송 및 결과 반환 함수
     *
     * @param price: 금액
     *
     * @return [boolean] 전송 결과
     */
    //@Override
    public function send($price){
        print("신한카드사 ".$price."원 결제 요청<br><br>");
        return true;
    }
}

/**
 * B 카드 객체
 *
 * @author RWB
 * @since 2021.08.14 Sat 02:36:11
 */
class CardB implements Purchasable{
    /**
     * 카드사 정보 전송 및 결과 반환 함수
     *
     * @param price: 금액
     *
     * @return [boolean] 전송 결과
     */
    //@Override
    public function send($price){
        print("농협카드사 ".$price."원 결제 요청<br><br>");
        return true;
    }
}


/**
 * C 카드 객체
 *
 * @author RWB
 * @since 2021.08.14 Sat 02:36:11
 */
class CardC implements Purchasable{
    /**
     * 카드사 정보 전송 및 결과 반환 함수
     *
     * @param price: 금액
     *
     * @return [boolean] 전송 결과
     */
    //@Override
    public function send($price){
        print("카카오카드사 ".$price."원 결제 요청");
        return true;
    }
}

$A = new Pos();
$A -> purchase(new CardC,50000);

//출력
카카오카드사 50000원 결제 요청

 

요약

이전 코드와 리팩토링한 코드를 비교했을 때 기능이 변하거나 확장 가능하지만, 해당 기능의 코드는 수정하면 안된다는 의미를 여기에서 찾을 수 있었습니다.

이전 코드의 경우 새로운 카드를 추가하기 위해선 코드의 추가가 요구되었습니다. 즉, 기능을 확장하기 위해선 코드의 수정이 필요하다는 의미이지만

리팩토링한 코드를 보았을 때 Purchasable라는 통합된 인터페이스를 사용하기 때문에 카드 추가에 따라 코드 단계에서 대응 할 필요없이 새로는 카드에 대한 객체만 생성하여 코드의 변경 없이 기능이 확장되고 있습니다.

 

단일 책임 원칙과 마찬가지로, 비슷한 형태의 분기가 반복될 경우 개발-폐쇄 원칙을 준수하지 않았을 경우가 높습니다. 이는 곧 높은 리팩토링 비용으로 직결되니 잘 준수해서 독립적인 모듈을 설계하도록 권장합니다.

 

참고자료

자바를 예시로 든 자료 https://blog.itcode.dev/posts/2021/08/14/open-closed-principle