TIL

[멋쟁이사자처럼 부트캠프 TIL회고] 49일차 : 디자인 패턴

Cadi 2025. 1. 9. 02:26

디자인 패턴 : 전략 패턴

 

https://febelo0524.tistory.com/68

 

디자인 패턴 : 전략 패턴

전략 패턴객체의 전략(행동)을 정의하는 알고리즘을 외부에서 선택하고 주입하여 유연성과 확장성을 높이는 설계 방식'전략'을 캡슐화하여 런타임에 동적으로 교체할수 있도록 설계하는 방법DI

febelo0524.tistory.com

정리한 내용을 바탕으로 간단하게 생각해보자면, 

같은 DoSomething() 메서드를 호출해도, SetStrategy로 설정한 strategy에 따라 다른 기능이 호출되는 것이다.

이는 DoSomething메서드가 구체적인 클래스를 참조하지 않고, Strategy 인터페이스를 참조하기 때문이다. 

 

정리하면서 느낀 것은, 아직 나는 다형성 개념에 대해 완벽히 이해하고 있지 못하다는 것이다. 

https://yeko90.tistory.com/entry/c-%EB%8B%A4%ED%98%95%EC%84%B1polymorphism-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9

 

[c#] 다형성(polymorphism) 이란? | 왜 사용하는가? + 오버라이딩

이번 포스팅에서는 다형성에 대해 알아보도록 하겠습니다. (포스팅을 읽기전 반드시 상속에 관한 해당 포스팅을 읽고 와주시기 바랍니다.) 1. 다형성이란? 우리에게는 두개의 클래스가 있습니다

yeko90.tistory.com

 

 

이어서 말해보자면, 구체적인 클래스를 참조하지 않고, Strategy 인터페이스를 참조하고, 이는 다형성으로 virtual로 

호출하고자 하는 함수를 작성한다면, 인터페이스로 객체를 생성하고, 다형성 개념을 이용해 인터페이스를 상속받은 자식의 함수를 호출할 수 있기 때문이다. 

// 1. Strategy 인터페이스
public interface IStrategy
{
    void DoSomething(); // 공통 메서드
}

// 2. 구체적인 전략 클래스 A
public class ConcreteStrategyA : IStrategy
{
    public void DoSomething()
    {
        Debug.Log("전략 A가 실행되었습니다.");
    }
}

// 3. 구체적인 전략 클래스 B
public class ConcreteStrategyB : IStrategy
{
    public void DoSomething()
    {
        Debug.Log("전략 B가 실행되었습니다.");
    }
}

// 4. Context 클래스
public class Context
{
    private IStrategy strategy; // 현재 사용 중인 전략

    // 전략 설정 메서드
    public void SetStrategy(IStrategy strategy)
    {
        this.strategy = strategy;
    }

    // 전략 실행 메서드
    public void ExecuteStrategy()
    {
        if (strategy != null)
            strategy.DoSomething();
        else
            Debug.Log("전략이 설정되지 않았습니다.");
    }
}

// 5. 클라이언트 코드
public class Client : MonoBehaviour
{
    void Start()
    {
        Context context = new Context();

        // 전략 A 설정 및 실행
        context.SetStrategy(new ConcreteStrategyA());
        context.ExecuteStrategy();

        // 전략 B 설정 및 실행
        context.SetStrategy(new ConcreteStrategyB());
        context.ExecuteStrategy();
    }
}

 

 

 

의문 : 이렇게 전략을 설정할 때마다 새로운 클래스를 생성해주고 실행하면,
예를 들어 전략 A - B - A 순으로 실행할 때 구체적인 전략 클래스를 두 번 만드는 것은 비효율 아닌가? 

수많은 구체적인 A 전략 클래스가 많이 생성되는 것이 아닌가  ?

 

이는 우리가 배웠던 방식으로 해결할 수 있다.

  • 각각의 전략을 싱글톤으로 만듦
  • 전략 객체를 미리 생성해두고 캐싱 (팩토리 패턴을 이용함)
public class StrategyFactory
{
    private static readonly Dictionary<string, IStrategy> strategies = new Dictionary<string, IStrategy>();

    // 정적 생성자를 통해 초기화
    static StrategyFactory()
    {
        strategies["A"] = new ConcreteStrategyA();
        strategies["B"] = new ConcreteStrategyB();
    }

    public static IStrategy GetStrategy(string key)
    {
        return strategies.ContainsKey(key) ? strategies[key] : null;
    }
}
public class Client : MonoBehaviour
{
    void Start()
    {
        Context context = new Context();

        // 팩토리를 통해 전략 A 가져오기
        context.SetStrategy(StrategyFactory.GetStrategy("A"));
        context.ExecuteStrategy();

        // 팩토리를 통해 전략 B 가져오기
        context.SetStrategy(StrategyFactory.GetStrategy("B"));
        context.ExecuteStrategy();

        // 다시 전략 A 실행
        context.SetStrategy(StrategyFactory.GetStrategy("A"));
        context.ExecuteStrategy();
    }
}
  • 단일 인스턴스 사용 : 전략 클래스가 내부 상태를 가지지 않는 경우, 단일 인스턴스를 생성해서 공유

 

 

디자인 패턴 : 빌더 패턴 

https://febelo0524.tistory.com/69

 

디자인 패턴 : 빌더 패턴

빌더 패턴 복잡한 객체의 생성 과정을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고, 마지막에 통합하며 객체를 생성예를

febelo0524.tistory.com

빌더 패턴은 단순히 생각하면 , 객체의 복잡한 생성 과정을 분리하여 다양한 구성의 인스턴스를 만드는 것이다.

예를 들어 변수가 10개라면, 단순히 생성자를 통해서 만드는 것은 한계가 있다. 

public static void main(String[] args) {

    Student student = new StudentBuilder()
                .id(2016120091)
                .name("임꺽정")
                .grade("Senior")
                .phoneNumber("010-5555-5555")
                .build();

    System.out.println(student);
}
class StudentBuilder {
    private int id;
    private String name;
    private String grade;
    private String phoneNumber;

    public StudentBuilder id(int id) {
        this.id = id;
        return this;
    }

    public StudentBuilder name(String name) {
        this.name = name;
        return this;
    }

    public StudentBuilder grade(String grade) {
        this.grade = grade;
        return this;
    }

    public StudentBuilder phoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
        return this;
    }
}

출처 : https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EB%B9%8C%EB%8D%94Builder-%ED%8C%A8%ED%84%B4-%EB%81%9D%ED%8C%90%EC%99%95-%EC%A0%95%EB%A6%AC

 

💠 빌더(Builder) 패턴 - 완벽 마스터하기

Builder Pattern 빌더 패턴(Builder Pattern)은 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴이다. 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아

inpa.tistory.com

생성자에 필수 메서드를 넣어줄 수도, 멤버 변수에 기본 값을 할당할 수도 있어 좋고

밖에서 Setter로 Student의 변수들을 직접 설정할 수 없기에 사용하는 패턴이다. 

 

 

디자인 패턴 : 연쇄 책임 패턴

 

Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
Handler handler3 = new ConcreteHandler3();

// 메서드 체이닝으로 연결
handler1.setNext(handler2).setNext(handler3);

// 체인의 첫 번째 핸들러를 실행
handler1.run("some-url");

핸들러를 연결하고, 체인의 첫 번째 핸들러를 실행함으로  순차적으로 기능을 수행하게 한다. 
https://febelo0524.tistory.com/70

 

디자인 패턴 : 연쇄 책임 패턴 (Chain Of Responsibility)

연쇄 책임 패턴 클라이언트의 요청에 대한 처리를 하나의 객체가 하는 것이 아닌, 여러개의 처리 객체들로 나누고 사슬로 묶어 연쇄적으로 처리하는 방식처리 객체들은 핸들러(Handler)라고 불리

febelo0524.tistory.com

디자인 패턴 : 중재자 패턴

아직 정확히 이해하지는 못했지만 써 보면서 이해해야겠다. 
https://febelo0524.tistory.com/71

 

디자인 패턴 : 중재자 패턴

중재자 패턴다대 다 (N : N)의 관계에서 객체 간의 직접 통신을 막고, 모든 통신을 중재자를 통해서 해결객체들 간의 상호작용은 직접적으로 연결되지 않음객체는 오직 중재자만 참조함예를 들자

febelo0524.tistory.com

 

 

 

코딩테스트 : 문자열 계산하기 

my_string은 "3 + 5"처럼 문자열로 된 수식입니다. 문자열 my_string이 매개변수로 주어질 때, 수식을 계산한 값을 return 하는 solution 함수를 완성해주세요.

 

 

using System;

public class Solution {
    public int solution(string my_string)
    {
        int answer = 0;
        string tempt = "";
        foreach (char c in my_string)
        {
            if (char.IsDigit(c))
            {
                tempt += c;
            }
            else if (c == '+')
            {
                answer += Int32.Parse(tempt);
                tempt = "";
            }
            else if (c == '-')
            {
                answer -= Int32.Parse(tempt);
                tempt = "";
            }
            
            
        }
        return answer;
    }
}

 

 

일단 이렇게 작성했는데,  남은 것들을 처리해 주어야 한다.

지금은 + 혹은 -시에만 더해주고 빼 주므로 마지막 남은 문자열은 처리되지 않는다. 

 

using System;

public class Solution {
    public int solution(string my_string)
    {
        int answer = 0;
        string tempt = "";
        string plusminus = "";
        for(int i = 0; i < my_string.Length; i++)
        {
            char c = my_string[i];
            if (char.IsDigit(c))
            {
                tempt += c;
            }
            else if (c == '+')
            {
                answer += Int32.Parse(tempt);
                tempt = "";
                plusminus = "+";
            }
            else if (c == '-')
            {
                answer -= Int32.Parse(tempt);
                tempt = "";
                plusminus = "-";
            }

            if (i == my_string.Length - 1)
            {
                if (plusminus == "+")
                {
                    answer += Int32.Parse(tempt);
                }

                if (plusminus == "-")
                {
                    answer -= Int32.Parse(tempt);
                }
            }
        }
        return answer;
    }
}

 

 

다음과 같이 했지만, 10문제중 3문제만 맞았다. 

아 ... 나는 처음에 -가 나오면 -를 해줬구나... 처음 숫자는 무조건 더하고, 그 다음부터 저장된 기호로 계산을 했어야 했다.

 

using System;

public class Solution {
    public int solution(string my_string)
    {
        int answer = 0;
        string tempt = "";
        string plusminus = "+";
        for(int i = 0; i < my_string.Length; i++)
        {
            char c = my_string[i];

            if (char.IsDigit(c))
            {
                tempt += c;
            }
            else if (c == '+' || c == '-')
            {
                if (plusminus == "+")
                {
                    answer += Int32.Parse(tempt);
                }
                else if (plusminus == "-")
                {
                    answer -= Int32.Parse(tempt);
                }

                tempt = "";
                plusminus = c.ToString();
            }
        }

        if (tempt != "")
        {
            if (plusminus == "+")
            {
                answer += Int32.Parse(tempt);
            }
            else if (plusminus == "-")
            {
                answer -= Int32.Parse(tempt);
            }
        }
        return answer;
    }
}

 

똑같이 안되어서 좀 더 고민하다 성공했다. 결국 처음에 설정한 +를 한 번 사용하고 초기화 해야 한다. 

 

 

 

코딩테스트 :  OX 퀴즈

덧셈, 뺄셈 수식들이 'X [연산자] Y = Z' 형태로 들어있는 문자열 배열 quiz가 매개변수로 주어집니다. 수식이 옳다면 "O"를 틀리다면 "X"를 순서대로 담은 배열을 return하도록 solution 함수를 완성해주세요.

 

딱 보자마자 위의 문제와 굉장히 유사한 형태의 문제라고 느꼈다. 

다만 다른 점은 X,Y,Z 앞에 -부호가 가능하다는 점 ? 이건 Int32.Parse하면 알아서 해결되지 않을까 한다. 

 

다시 생각해보니 음수는 isDigit로 바로바로 추가할 수가 없다. 공백을 이용해서 나누고 해야 할 듯 하다. 

using System;

public class Solution
{
    public string[] solution(string[] quiz)
    {
        string[] answer = new string[quiz.Length]; { };

        for (int i = 0; i < quiz.Length; i++)
        {
            
            string question = quiz[i];
            {
                string currentNumber = "";
                string lastOperation = "+";
                int answerI = 0;
                for (int j = 0; j < question.Length; j++)
                {
                   
                    if (question[j] == ' '|| question[j] =='=')
                    {
                        if (lastOperation == "+" )
                        {
                            answerI += Int32.Parse(currentNumber);
                        }

                        if (lastOperation == "-")
                        {
                            answerI -= Int32.Parse(currentNumber);
                        }
                        currentNumber = "";
                    }
                    else if (question[j] == '-'|| question[j] =='+')
                    {
                        if (question[j - 1] == ' ' ) 
                        {
                            lastOperation = question[j].ToString();
                        }

                        if (question[j - 1] != ' ')
                        {
                            currentNumber += question[j];
                        }
                    }
                    else if (char.IsDigit(question[j]))
                    {
                        currentNumber += question[j];
                    }
                    
                    if (i == question.Length - 1)
                    {
                        if (answerI == Int32.Parse(currentNumber))
                        {
                            answer[i] = "O";
                        }
                        else
                        {
                            answer[i] = "X";
                        }
                    }
                    
                }
            }
        }
        
        

        return answer;
    }
}

 

이런 식으로 코드를 작성했는데 하기도 어렵고, 제대로 작동하지도 않는다. (오류가 난다)

 

=을 기준으로 좌변과 우변을 나눠서, 위의 문자열 계산하기 식을 응용해서 풀어봐야겠다. 

 

using System;

public class Solution
{
    public string[] solution(string[] quiz)
    {
        string[] answer = new string[quiz.Length];
        for (int i = 0; i < quiz.Length; i++)
        {
            string question = quiz[i];

            int leftside = 0;
            int rightside = 0;

            string[] parts = question.Split('=');

            if (parts.Length != 2)
            {
                answer[i] = "X";
            }
            else
            {
                leftside = Evaluate(parts[0]);
                rightside = Int32.Parse(parts[1]);
            }

            if (leftside == rightside)
            {
                answer[i] = "O";
            }
            else answer[i] = "X";
        }
        
        return answer;
    }
    int Evaluate(string question)
    {
        string tempt = "";
        string lastOperation = "+";
        int questionAnswer = 0;

        for (int i = 0; i < question.Length; i++)
        {
            if (question[i] == '+' || question[i] == '-')
            {
                if (question[i + 1] == ' ')
                {
                    if (lastOperation == "+")
                    {
                        questionAnswer += Int32.Parse(tempt);
                    }
                    else if (lastOperation == "-")
                    {
                        questionAnswer -= Int32.Parse(tempt);
                    }
                    lastOperation = question[i].ToString();
                    tempt = "";
                }
                else tempt += question[i];
            }
            else tempt += question[i];


            if (i == question.Length - 1)
            {
                if (lastOperation == "+")
                {
                    questionAnswer += Int32.Parse(tempt);
                }
                else if (lastOperation == "-")
                {
                    questionAnswer -= Int32.Parse(tempt);
                }
            }
        }

        return questionAnswer;
    }
}

 

일단 이정도 했다. 코드 자체는 혼자 짰는데 Evaluate 함수를 solotion 안에 넣어둬서 계속 오류가 뜨길래 물어봤더니..

문제 푸는 것이 길어지니까 스코프를 혼동했다. 앞으로 문제 풀 때에는 스코프도 조금 더 신경써야겠다. 

 

사실 이거 혼자 짜고 작동시켰을 때

이게 왜 되는거지 ?????????  했지만 성공했으니 OK...

 

 

다른 사람의 흥미로운 풀이


나는 하나하나 문자를 선택해가며 비교하고, 비교를 바탕으로 tempt 문자열에 더할지, 아니면 지금까지의 문자열을 

숫자로 바꿔서 계산을 할 지를 선택했다면 다른 분들 중에는 나누는 것을 효율적으로 나눠서 푸신 분들이 계시다. 

생각하지 못했던 방식이고 많이 배웠다. 

예를 들어, 지금의 문제에서 우리가 참 거짓을 판단해야 하는 문자열의 형태는

 'X [연산자] Y = Z' 으로 고정된다. 즉 , "X '공백' 연산자 '공백' Y '공백' = '공백' Z"의 구조이다. 

이 방식으로,  공백으로 문자열을 나눈다면, X는 0번째,  Y는 2번째, Z는 4번째의 숫자가 된다. 

각각 문자열을 변수로 할당해주고, 1번째 문자열, 즉 연산자를 기준으로 더하기 빼기를 한 뒤에 값을 비교해주면

간단하게 문제를 해결할 수 있다. 

 

문제를 조금 더 오래 읽었으면, 바로 떠올리지는 못했더라도 '공백'을 기준으로 문자열을 나눈다는 생각을 

조금은 더 오래하지 않았을까  ? 문제를 대충 훑고, 인풋 값과 아웃풋 값을 바탕으로 문제를 해결하려 하지 말고

조금 더 효율적인 방식으로 할 수 있게 노력해야겠다. 

 

 

 

코딩테스트 : K의 개수

1부터 13까지의 수에서, 1은 1, 10, 11, 12, 13 이렇게 총 6번 등장합니다. 정수 i, j, k가 매개변수로 주어질 때, i부터 j까지 k가 몇 번 등장하는지 return 하도록 solution 함수를 완성해주세요.

using System;

public class Solution {
    public int solution(int i, int j, int k) 
    {
        int answer = 0;
        string kk = k.ToString();
        char kkk = kk.ToCharArray()[0];


        for (int l = i; l <= j; l++)
        {
            string comparison =l.ToString();
            for (int r = 0; r < comparison.Length; r++)
            {
                if (comparison[r] == kkk)  answer++;
               
            }
        }
        return answer;
    }
}

 

한 번에 풀긴했지만 스코프와 세미콜론 사용에서 또 오류가 있었다.

 

다른 사람의 흥미로운 풀이

 

린큐를 사용해서 푸셨다. 메모해두고 나중에 린큐가 익숙해지면 다시 풀어봐야겠다.