ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SOLID] 개방 폐쇠 원칙 (Open Closed Principle)
    Programming/CS 2021. 12. 16. 07:25

     

    OPEN CLOSED PRINCIPLE 개방 폐쇠 원칙 (OCP)

    확장에는 열려있으나 수정에는 닫혀있어야 하는 행동 원칙

     

     

    개방 폐쇠 원칙이 준수되지 않은 클래스 예시

    여러 씬들을 관리하는 

    namespace NO_OCP
    {
        class NormalScene
        {
        public:
            explicit NormalScene(std::string sceneName) : sceneName(std::move(sceneName))
            {
            }
    
            std::string GetName() const
            {
                return sceneName;
            }
    
        private:
            std::string sceneName;
        };
    
        class DungeonScene
        {
        public:
            explicit DungeonScene(std::string sceneName) : sceneName(std::move(sceneName))
            {
            }
    
            std::string GetName() const
            {
                return sceneName;
            }
    
        private:
            std::string sceneName;
        };
    
        class ArenaScene
        {
        public:
            explicit ArenaScene(std::string sceneName) : sceneName(std::move(sceneName))
            {
            }
    
            std::string GetName() const
            {
                return sceneName;
            }
    
        private:
            std::string sceneName;
        };
    
        class PVPScene
        {
        public:
            explicit PVPScene(std::string sceneName) : sceneName(std::move(sceneName))
            {
            }
    
            std::string GetName() const
            {
                return sceneName;
            }
    
        private:
            std::string sceneName;
        };
    
        void EnterNormalScene(std::string sceneName)
        {
            if (sceneName == "Normal")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Dungeon")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Arena")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else
            {
                Print("Empty Scene : " + sceneName);
            }
            // else if (sceneName == "PVP")
            // {
            //     Print("OnEnter : " + sceneName);
            //     Print("Do something ~~ ");
            // }
        }
    
        void EnterDungeonScene(std::string sceneName)
        {
            if (sceneName == "Normal")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Dungeon")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Arena")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else
            {
                Print("Empty Scene : " + sceneName);
            }
            // else if (sceneName == "PVP")
            // {
            //     Print("OnEnter : " + sceneName);
            //     Print("Do something ~~ ");
            // }
        }
        
        void EnterArenaScene(std::string sceneName)
        {
            if (sceneName == "Normal")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Dungeon")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Arena")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else
            {
                Print("Empty Scene : " + sceneName);
            }
            // else if (sceneName == "PVP")
            // {
            //     Print("OnEnter : " + sceneName);
            //     Print("Do something ~~ ");
            // }
        }
    };  // namespace NO_OCP
    int main(void)
    {
        // 개방 폐쇠 원칙이 준수되지 않을 경우
        {
            NO_OCP::NormalScene normalScene = NO_OCP::NormalScene("Normal");
            NO_OCP::DungeonScene dungeonScene = NO_OCP::DungeonScene("Dungeon");
            NO_OCP::ArenaScene arenaScene = NO_OCP::ArenaScene("Arena");
    
            NO_OCP::EnterNormalScene(dungeonScene.GetName());
            NO_OCP::EnterDungeonScene(arenaScene.GetName());
            NO_OCP::EnterArenaScene(normalScene.GetName());
        }
        return 0;
    }

     

     

    각각의 씬 클래스가 존재하며 해당 씬에 들어왔을 때에 대해서 처리하는 함수를 만들었다.

    여기서 새로운 씬 클래스를 생성했을 때 추가되는 곳은 다음과 같이 해당 씬과 연관된 모든 함수들에 각각 분기문을 달아서 처리해줘야 된다.

    이러한 방식은 초반은 괜찮을 지라도, 추후 유지보수를 고려하면 좋은 방법은 아니다.

    또한 분기문 코드가 점점 길어지게 되면, 가독성이 상당히 떨어지게 된다.

    namespace NO_OCP
    {
        // ------------------- NEW -------------------
        class PVPScene
        {
        public:
            explicit PVPScene(std::string sceneName) : sceneName(std::move(sceneName))
            {
            }
    
            std::string GetName() const
            {
                return sceneName;
            }
    
        private:
            std::string sceneName;
        };
        // ------------------- NEW -------------------
    
    
        void EnterNormalScene(std::string sceneName)
        {
            if (sceneName == "Normal")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Dungeon")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Arena")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            // ------------------- NEW -------------------
            else if (sceneName == "PVP")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            // ------------------- NEW -------------------
            else
            {
                Print("Empty Scene : " + sceneName);
            }
        }
    
        void EnterDungeonScene(std::string sceneName)
        {
            if (sceneName == "Normal")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Dungeon")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Arena")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            // ------------------- NEW -------------------
            else if (sceneName == "PVP")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            // ------------------- NEW -------------------
            else
            {
                Print("Empty Scene : " + sceneName);
            }
        }
        
        void EnterArenaScene(std::string sceneName)
        {
            if (sceneName == "Normal")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Dungeon")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            else if (sceneName == "Arena")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            // ------------------- NEW -------------------
            else if (sceneName == "PVP")
            {
                Print("OnEnter : " + sceneName);
                Print("Do something ~~ ");
            }
            // ------------------- NEW -------------------
            else
            {
                Print("Empty Scene : " + sceneName);
            }
        }
    };  // namespace NO_OCP
    
    int main(void)
    {
        // 개방 폐쇠 원칙이 준수되지 않을 경우
        {
            NO_OCP::NormalScene normalScene = NO_OCP::NormalScene("Normal");
            NO_OCP::DungeonScene dungeonScene = NO_OCP::DungeonScene("Dungeon");
            NO_OCP::ArenaScene arenaScene = NO_OCP::ArenaScene("Arena");
    
            NO_OCP::EnterNormalScene(dungeonScene.GetName());
            NO_OCP::EnterDungeonScene(arenaScene.GetName());
            NO_OCP::EnterArenaScene(normalScene.GetName());
            
            // ------------------- NEW -------------------
            NO_OCP::PVPScene pvpScene = NO_OCP::PVPScene("PVP");
            NO_OCP::EnterNormalScene(pvpScene.GetName());
            // ------------------- NEW -------------------
        }
    
        return 0;
    }

     

    새로운 씬(PVP) 추가시 해당 씬과 연관된 모든 함수(EnterNormalScene, EnterDungeonScene, EnterArenaScene)들을 수정해야 한다.
    확장에 열려있고(Open) 수정에는 닫혀있어야(Closed) 하는데 매번 씬을 만들 때마다 모든 함수를 수정해야 되니깐 개방 폐쇠 원칙(Open Closed Principle)에 어긋난다.

     

    개방 폐쇠 원칙이 적용된 예시를 보도록 하자.

     

    개방 폐쇠 원칙이 준수 된 클래스

    namespace OK_OCP
    {
        class BaseScene
        {
        public:
            explicit BaseScene() = default;
            explicit BaseScene(std::string sceneName) : sceneName(std::move(sceneName)) {}
    
            virtual ~BaseScene() = default;
    
            virtual void OnEnter()
            {
                Print("OnEnter : " + sceneName);
            }
            virtual void OnExit()
            {
                Print("OnExit : " + sceneName);
            }
    
            std::string GetName() const
            {
                return sceneName;
            }
    
        protected:
            std::string sceneName;
        };
    
        class NormalScene : public BaseScene
        {
        public:
            explicit NormalScene(std::string sceneName)
            {
                this->sceneName = std::move(sceneName);
            }
    
    
            virtual void OnEnter() override
            {
                BaseScene::OnEnter();
    
                Print("Do something Enter ~~ ");
            }
    
            virtual void OnExit() override
            {
                BaseScene::OnExit();
                
                Print("Do something Exit ~~ ");
            }
        };
    
        class DungeonScene : public BaseScene
        {
        public:
            explicit DungeonScene(std::string sceneName)
            {
                this->sceneName = std::move(sceneName);
            }
    
            virtual void OnEnter() override
            {
                BaseScene::OnEnter();
    
                Print("Do something Enter ~~ ");
            }
    
            virtual void OnExit() override
            {
                BaseScene::OnExit();
                
                Print("Do something Exit ~~ ");
            }
        };
    
        class ArenaScene : public BaseScene
        {
        public:
            explicit ArenaScene(std::string sceneName)
            {
                this->sceneName = std::move(sceneName);
            }
    
            virtual void OnEnter() override
            {
                BaseScene::OnEnter();
    
                Print("Do something Enter ~~ ");
            }
    
            virtual void OnExit() override
            {
                BaseScene::OnExit();
                
                Print("Do something Exit ~~ ");
            }
        };
    
    } // namespace OK_OCP
    
    
    int main(void)
    {
        // 개방 폐쇠 원칙이 준수된 경우
        {
            OK_OCP::BaseScene* normalScene = new OK_OCP::NormalScene("Normal");
            OK_OCP::BaseScene* dungeonScene = new OK_OCP::DungeonScene("Dungeon");
            OK_OCP::BaseScene* arenaScene = new OK_OCP::ArenaScene("Arena");
    
            OK_OCP::EnterScene(*dungeonScene);
            OK_OCP::EnterScene(*arenaScene);
            OK_OCP::EnterScene(*normalScene);
    
            OK_OCP::ExitScene(*dungeonScene);
            OK_OCP::ExitScene(*arenaScene);
            OK_OCP::ExitScene(*normalScene);
    
            SAFE_DELETE(arenaScene);
            SAFE_DELETE(dungeonScene);
            SAFE_DELETE(normalScene);
        }
    
        return 0;
    }

    이전보다 훨씬 깔끔해졌다.

    모든 씬들에 대한 공통 부모 클래스(BaseScene)를 만들고 이를 하위 씬에게 상속시켜서 사용했다.

     

    이제 여기서도 새로운 씬을 추가해보자.

     

    namespace OK_OCP
    {
    	// ------------------- NEW -------------------
        class PVPScene : public BaseScene
        {
        public:
            explicit PVPScene(std::string sceneName)
            {
                this->sceneName = std::move(sceneName);
            }
    
            virtual void OnEnter() override
            {
                BaseScene::OnEnter();
    
                Print("Do something Enter ~~ ");
            }
    
            virtual void OnExit() override
            {
                BaseScene::OnExit();
                
                Print("Do something Exit ~~ ");
            }
        };
        // ------------------- NEW -------------------
        
        void EnterScene(BaseScene& scene)
        {
            scene.OnEnter();
        }
    
        void ExitScene(BaseScene& scene)
        {
            scene.OnExit();
        }
    
    
    } // namespace OK_OCP
    
    
    int main(void)
    {
        // 개방 폐쇠 원칙이 준수된 경우
        {
            OK_OCP::BaseScene* normalScene = new OK_OCP::NormalScene("Normal");
            OK_OCP::BaseScene* dungeonScene = new OK_OCP::DungeonScene("Dungeon");
            OK_OCP::BaseScene* arenaScene = new OK_OCP::ArenaScene("Arena");
    
            OK_OCP::EnterScene(*dungeonScene);
            OK_OCP::EnterScene(*arenaScene);
            OK_OCP::EnterScene(*normalScene);
    
            OK_OCP::ExitScene(*dungeonScene);
            OK_OCP::ExitScene(*arenaScene);
            OK_OCP::ExitScene(*normalScene);
    		
            // ------------------- NEW -------------------
            OK_OCP::BaseScene* pvpScene = new OK_OCP::PVPScene("PVP");
            OK_OCP::EnterScene(*pvpScene);
            OK_OCP::ExitScene(*pvpScene);
    		// ------------------- NEW -------------------
            
            // ------------------- NEW -------------------
            SAFE_DELETE(pvpScene);
            // ------------------- NEW -------------------
            
            SAFE_DELETE(arenaScene);
            SAFE_DELETE(dungeonScene);
            SAFE_DELETE(normalScene);
        }
    
        return 0;
    }

    그전보다 훨씬 추가해야 되는 일이 줄어들었다.

    새로운 씬을 만들었을 때, 따로 씬 이름에 따른 분기문을 처리할 필요가 없고 각각의 씬만 추가해주면 된다.

    이는 확장에는 열려있고 수정에는 닫혀있는 개방페쇠원칙을 지켰기 때문에 가능했다.

    추후에 100개, 1000개의 씬들을 추가한다고 하면 새로운 클래스만 추가해주면 되기 때문에 유지보수 측면에서도 전보다 많이 개선되었다.

     

     

    전체 코드

    OpenClosed.cpp
    0.01MB

     

Designed by Tistory.