ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SOLID] 개방 폐쇄 원칙이란 (Open-Closed Principle, OCP)
    공부 2021. 6. 13. 18:17

    개방 폐쇄 원칙은 열림 닫힘 원칙이라고도 불리며다섯 가지 애자일 원칙(SOLID) 중 1개입니다. 해당 원칙은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 합니다. 즉, 기능을 추가/변경은 가능해야 하지만 이 기능을 사용하는 코드(기존 코드)는 변경하지 않아야 한다는 의미입니다. 만약 새로운 기능을 추가하려고 하는데 기존 코드를 변경해야 된다면 좋지 않은 설계이므로 OCP에 따라 분리를 진행해야 합니다.

    아래와 같은 코드가 있다고 가정합니다.

    from abc import ABCMeta, abstractmethod
    
    class Character(metaclass=ABCMeta):
        def __init__(self, name: str):
            self.name = name
    
        @abstractmethod
        def set_strength(self):
            pass
    
    class Army(Character):
        def __init__(self, name: str):
            super().__init__(name)
            self.strength = None
    
        def set_strength(self):
            self.strength = 9
    
    class Official(Character):
        def __init__(self, name: str):
            super().__init__(name)
            self.strength = None
    
        def set_strength(self):
            self.strength = 4
    
    class Jobless(Character):
        def __init__(self, name: str):
            super().__init__(name)
            self.strength = None
    
        def set_strength(self):
            self.strength = 0
    
    class CharacterSelection:
        def __init__(self, name: str):
            self.name = name신규
    
            self.selection_dict = {
                'army': Army(self.name),
                'official': Official(self.name),
                'jobless': Jobless(self.name)
            }
    
        def selection(self, data: str):
            character = self.selection_dict.get(data)
            if character:
                return character
            raise ValueError

    위 코드는 게임에서 캐릭터를 고르고 이름을 명시하는 클래스 입니다. CharacterSelection 클래스에서 입력된 정보에 따라 군인, 공무원, 백수를 선택하도록 하고 있습니다. 이러한 코드는 OCP 기법에 위배되는 코드입니다.

    개발자 캐릭터가 추가되었다고 가정을 하면 신규 클래스를 생성(확장)하는데 문제가 없지만 캐릭터 선택이라는 기존 CharacterSelection 클래스의 코드를 수정해야 합니다. 즉, 신규 기능에 대해 기존 코드의 수정이 닫혀있지 않고 열려있게 된 상황입니다. 이러한 문제는 캐릭터가 늘어날 수록 계속 발생하게 됩니다. 이러한 문제를 해결하기 위해 OCP 기법을 적용해 아래와 같이 변경할 수 있습니다.

    from abc import ABCMeta, abstractmethod
    
    class Character(metaclass=ABCMeta):
        def __init__(self, name: str):
            self.name = name
    
        @abstractmethod
        def set_strength(self):
            pass
    
        @classmethod
        @abstractmethod
        def get_character_name(cls):
            pass
    
    class Army(Character):
        def __init__(self, name: str):
            super().__init__(name)
            self.strength = None
    
        def set_strength(self):
            self.strength = 9
    
        @classmethod
        def get_character_name(cls):
            return 'army'
    
    class Official(Character):
        def __init__(self, name: str):
            super().__init__(name)
            self.strength = None
    
        def set_strength(self):
            self.strength = 4
    
        @classmethod
        def get_character_name(cls):
            return 'official'
    
    class Jobless(Character):
        def __init__(self, name: str):
            super().__init__(name)
            self.strength = None
    
        def set_strength(self):
            self.strength = 0
    
        @classmethod
        def get_character_name(cls):
            return 'jobless'
    
    class CharacterSelection:
        def __init__(self, name: str):
            self.name = name
    
            self.selection_dict = {
                sub_class.get_character_name(): sub_class(self.name)
                for sub_class in Character.__subclasses__()
            }
    
        def selection(self, data: str):
            character = self.selection_dict.get(data)
            if character:
                return character
            raise ValueError

    여기서 상위 추상 클래스에 get_charcter_name 이라는 추상 클래스 메소드를 추가하고 각 캐릭터 클래스에서 이를 구현하고 있습니다. 다음 CharacterSelection 클래스에서 Character 클래스를 상속받은 하위 클래스들을 순회하면서 get_charcter_name()을 키로 갖는 인스턴스를 선언하는 형식으로 OCP를 위배하지 않도록 변경했습니다.

    여기서 경찰이라는 캐릭터가 추가된다면 기존 코드의 수정없이 경찰 클래스만 선언을 하면 됩니다.

    class Police(Character):
        def __init__(self, name: str):
            super().__init__(name)
            self.strength = None
    
        def set_strength(self):
            self.strength = 7
    
        @classmethod
        def get_character_name(cls) -> str:
            return 'police'

    이렇게 OCP를 잘 지키면서 개발을 하게 된다면 신규 기능 추가에 대해 기존 로직을 변경하는 일이 없어져 시스템이 유연하고 안정적으로 운영될 수 있습니다.

    댓글