Unity/C#

[C#] SOLID 원칙 : 개방 폐쇄 원칙 - OCP (Open Closed Principle)

HungryK 2025. 3. 20. 23:28

공부용으로 작성되는 페이지입니다. 틀린 부분이나 환경에 따라 오류가 발생할 수 있습니다. 

 

전편-  단일 책임 원칙 SRP

 

[C#] SOLID 원칙 : 단일 책임 원칙(SRP)

공부용으로 작성되는 페이지입니다. 틀린 부분이나 환경에 따라 오류가 발생할 수 있습니다.  객체 지향을 접하면서 몇 번이고 들어본 SOLID 원칙, 머리로는 알고 있는데 잘 익혔는지는 모르겠

hungrykang.tistory.com

 

 


 

 

1. 개방 폐쇄 원칙(OCP)의 개념 

개방 폐쇄 원칙이란?

 

기존의 코드를 변경하지 않으면서 기능을 추가할 수 있어야한다는 원칙이다.

확장에 대해서는 open 되어있고, 수정에 대해서는 closed 되어있다는 의미이다.

즉, 객체를 직접적으로 수정하는 것을 제한해야한다는 원칙이다. 

 

OCP = 추상화?

 

OCP는 결국 추상화를 권장하는 원칙이나 다름없다.

추상화를 통해 상속 받는 클래스에서 공톡적으로 구현해야 하는 클래스나 메소드를 만들 수 있다.

이런 특성을 통해 수정이 필요할때 직접적으로 객체를 수정하지 않고, 상속 관계에 맞춰 추가하는 방식으로 코드를 확장할 수 있다. 

 

2.개방 폐쇄 원칙(OCP)의 사례 

OCP가 지켜지지 않은 케이스 

 

앞서 언급했듯이 OCP 원칙은 객체를 직접적으로 수정하는 것을 제한해야한다고 했다.

아래는 전화오는 대상에 따라 감정 상태가 바뀌는 내용이다.

#전화 타입에 따라 긴장도가 달라지는 예제 

public enum UserTension
 {
     Idle,
     annoyed,
     comfortable,
     tension
 }

 private UserTension _state = UserTension.Idle;
 private String[] Calltype = ["friend", "family", "boss"];


 public void Getcall(string type)
 {
     if(type == "friend")
     {
         switch(_state)
         {
             case UserTension.comfortable:
                  break;
         }
     }

     if (type == "family")
     {
         switch (_state)
         {
             case UserTension.annoyed:
                 break;
         }
     }

     if (type == "boss")
     {
         switch (_state)
         {
             case UserTension.tension:
                 break;
         }
     }

      }

 

해당 코드는 ocp를 위반했다고 볼 수 있는데, 새로운 유형의 전화나 감정 상태가 추가될 때마다 Getcall 메서드의 코드가 변경되어야 하기 때문이다. if문과 switch문을 끊임없이 작성해야하게 된다는 것이다. 

 

OCP 원칙을 지키기 위해서는 두 모듈이 만나는 지점에 추상클래스나 인터페이스를 정의하여 추상화를 해주고, 추상화에 의존하도록 코드를 작성해야한다. 

 


class CallManager
{
    #유저의 감정 상태를 정의하는 enum
    public enum UserTension
    {
        Idle,          
        Annoyed,       
        Comfortable,   
        Tension       
    }

    #걸려오는 전화 타입을 정의하는 enum
    public enum CallType
    {
        Friend,   
        Family,   
        Boss      
    }

     #초기 유저의 감정 상태
    private UserTension _state = UserTension.Idle;

    #전략 패턴을 위한 인터페이스 
    #각 전화 유형 클래스는 이 인터페이스를 상속받아 메서드를 구현
    public interface ICallStrategy
    {
        void HandleCall(ref UserTension state);
    }

   #인터페이스 상속
    public class FriendCall : ICallStrategy
    {
        public void HandleCall(ref UserTension state)
        {
            state = UserTension.Annoyed;
            Console.WriteLine("친구에게서 전화가 왔습니다. 상태: Annoyed");
        }
    }

  #인터페이스 상속
    public class FamilyCall : ICallStrategy
    {
        public void HandleCall(ref UserTension state)
        {
            state = UserTension.Comfortable;
            Console.WriteLine("가족에게서 전화가 왔습니다. 상태: Comfortable");
        }
    }

     #인터페이스 상속
    public class BossCall : ICallStrategy
    {
        public void HandleCall(ref UserTension state)
        {
            state = UserTension.Tension;
            Console.WriteLine("상사에게서 전화가 왔습니다. 상태: Tension");
        }
    }

    #전화 타입과 전략을 매칭하는 Dictionary
    #!!새로운 전화 타입을 추가할 때 이 딕셔너리에만 등록하면 됨!!
    private readonly Dictionary<CallType, ICallStrategy> _strategies;

    #생성자에서 전략 객체들을 미리 Dictionary에 등록
    public CallManager()
    {
        _strategies = new Dictionary<CallType, ICallStrategy>
        {
            { CallType.Friend, new FriendCall() },
            { CallType.Family, new FamilyCall() },
            { CallType.Boss, new BossCall() }
        };
    }

    #전화가 왔을 때 처리하는 메서드
    #전략을 Dictionary에서 가져와 실행하며 상태를 변경
    public void GetCall(CallType callType)
    {
        if (_strategies.ContainsKey(callType))
        {
            _strategies[callType].HandleCall(ref _state);
        }
        else
        {
            Console.WriteLine("알 수 없는 전화 유형");
        }
    }

     #현재 상태를 출력하는 메서드
    public void PrintState()
    {
        Console.WriteLine($"현재 상태: {_state}");
    }
}

class User
{
    static void Main()
    {
   
        var callManager = new CallManager();


        callManager.GetCall(CallManager.CallType.Friend);  // 친구 전화 -> Annoyed 출력
        callManager.PrintState();

        callManager.GetCall(CallManager.CallType.Family);  // 가족 전화 -> Comfortable 출력
        callManager.PrintState();

        callManager.GetCall(CallManager.CallType.Boss);    // 상사 전화 -> Tension 출력
        callManager.PrintState();

    }
}

 

 

Interface, Strategy,Dictionary를 사용하여서 기존의 코드를 수정하지 않고 확장할 수 있도록 만들었다.

여기서 새로운 전화 타입이 생긴다면, calltype enum에 새 type을 작성해준 다음 새 Strategy class를 만든 뒤 Dictionary에 추가해주기만 하면 된다. 즉, 추가 외의 작업은 이루어지지 않았다. 

 

다만, 내가 생각했던 것보다 상당히 어려워서... 조금 더 간략하게 짤 수 있는 방법을 생각해봐야겠다.

 

 

 

참고자료

 

 

💠 완벽하게 이해하는 OCP (개방 폐쇄 원칙)

개방 폐쇄 원칙 - OCP (Open Closed Principle) 개방 폐쇄의 원칙(OCP)이란 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙을 말한다. 보통 OCP를 확장에 대해서는

inpa.tistory.com

 

'Unity > C#' 카테고리의 다른 글

[C#] Delegate : Action, Func  (0) 2025.03.16
[C#] SOLID 원칙 : 단일 책임 원칙(SRP)  (0) 2025.03.06
[C#] Delegate(대리자) : Event, Lambda  (0) 2025.03.05