본문 바로가기
Study/디자인 패턴

[C#/Unity][디자인패턴] 방문자 패턴(Visitor Pattern)

by 스테디코디스트 2023. 12. 31.
반응형

1. 방문자 패턴이란?

- 알고리즘을 객체 구조에서 분리시키는 패턴.

- 데이터 구조와 데이터 처리를 분리하여, 데이터 구조 내부에 방문자 클래스를 만들어 각각을 처리한다.

- 방문자와 방문 공간을 분리하여, 방문 공간이 방문자를 맞이하고, 이후에 대한 행동은 방문자에게 위임하는 패턴.

 

2. 방문자 패턴을 사용하는 경우

- 자료구조(데이터)와 자료구조를 처리하는 로직(알고리즘)을 분리해야 할 경우

- 데이터 구조보다 알고리즘이 더 자주 바뀌는 경우

- 복잡한 객제 구조(ex. 객체 트리)의 모든 요소에 대해 작업을 수행해야 하는 경우

 

3. 방문자 패턴의 장점

- 구조를 수정하지 않고, 새로운 동작을 기존의 객체 구조에 추가할 수 있다.

- 작업 대상(방문 공간)과 작업 항목(방문 공간을 가지고 하는 일)을 분리시킨다.(데이터의 독립성 향상)

- 인터페이스를 통일시켜, 사용자에게 동일한 인터페이스를 제공한다.

 

4. 방문자 패턴의 단점

- 새로운 작업 대상(방문 공간)이 추가될 때마다 작업 주체(방문자)도 이에 대한 로직을 추가해야 한다.

- 두 객체(방문자와 방문 공간)의 결합도가 높아진다.

 

5. 코드 구현

1) 구조

- Visitor

: 방문자 클래스의 인터페이스

: Visit(Element)을 공용 인터페이스로 사용

 

- Element

: 방문 공간 클래스의 인터페이스

: Accept(Visitor)를 공용 인터페이스로 사용

: 내부적으로 Visitor.visit(this)를 호출

 

- Concrete Visitor

: Visitor를 구체적으로 구현한 클래스

 

- Concrete Element

: Element를 구체적으로 구현한 클래스

 

2) 소스코드(C#)

헬스장에서 운동하는 사람이 오늘 운동할 부위를 골라 루틴으로 저장하고 해당 루틴으로 운동을 하게하는 프로그램을 만든다고 해보자.

 

- 패턴 적용 코드

using System;

namespace StudyCSharp
{
    // Element
    public interface IPart
    {
        string Name { get; }

        void Accept(IVisitor visitor);
    }

    // Concret Element 1
    public class Chest : IPart
    {
        public string Name { get => "가슴"; }

        public void Accept(IVisitor visitor)
        {
            visitor.visit(this);
            Console.WriteLine("벤치프레스 70kg x 10, 4set");
            Console.WriteLine("체스트플라이 15kg x 20, 4set");
            Console.WriteLine();
        }
    }

    // Concret Element 2
    public class Back : IPart
    {
        public string Name { get => "등"; }

        public void Accept(IVisitor visitor)
        {
            visitor.visit(this);
            Console.WriteLine("데드리프트 100kg x 5, 4set");
            Console.WriteLine("랫풀다운 40kg x 15, 4set");
            Console.WriteLine();
        }
    }

    // Concret Element 3
    public class Leg : IPart
    {
        public string Name { get => "하체"; }

        public void Accept(IVisitor visitor)
        {
            visitor.visit(this);
            Console.WriteLine("스쿼트 90kg x 8, 4set");
            Console.WriteLine("런지 40kg x 10, 4set");
            Console.WriteLine();
        }
    }

    // Visitor
    public interface IVisitor
    {
        // 자신의 루틴
        List<IPart> myRoutine { get; set; }

        string Name { get; }

        void visit(Chest chest);

        void visit(Back back);

        void visit(Leg leg);
    }

    // Concrete Visitor
    public class User : IVisitor
    {
        public List<IPart> myRoutine { get; set; }

        public string Name { get; }

        public User(string name)
        {
            myRoutine = new List<IPart>();
            Name = name;
        }

        public void visit(Chest chest)
        {
            Console.WriteLine("가슴 운동 시작");
        }

        public void visit(Back back)
        {
            Console.WriteLine("등 운동 시작");
        }

        public void visit(Leg leg)
        {
            Console.WriteLine("하체 시작...");
        }
    }

    // Client
    public class Activist
    {
        public static void SelectRoutine(IVisitor visitor, IPart part)
        {
            // 루틴 설정 시작
            if (visitor.myRoutine.Count == 0)
            {
                Console.WriteLine();
                Console.WriteLine("[{0}]님의 루틴을 설정합니다.", visitor.Name);
                Console.WriteLine();
            }

            // 루틴 추가
            visitor.myRoutine.Add(part);
            Console.WriteLine(">>> " + part.Name + " 루틴 추가");
        }

        public static void StartRoutine(IVisitor visitor)
        {
            // 오늘 루틴으로 운동시작
            Console.WriteLine("-----------------------------");

            // 운동 부위 표시
            for (int i = 0; i < visitor.myRoutine.Count; i++)
            {
                Console.Write(visitor.myRoutine[i].Name + " ");

                if (i != visitor.myRoutine.Count - 1)
                    Console.Write("+ ");
            }

            Console.WriteLine("운동을 시작합니다. ");
            Console.WriteLine("-----------------------------\n");

            // 선택한 루틴에 따라 운동 시작
            foreach (IPart part in visitor.myRoutine)
            {
                part.Accept(visitor);
            }

            Console.WriteLine("-----------------------------");
            Console.WriteLine("운동을 마칩니다. ");
            Console.WriteLine("-----------------------------");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 방문자 1 -> 헬스장 방문
            IVisitor user1 = new User("헬린이");

            // 방문자 1 -> 오늘의 루틴 선택           
            Activist.SelectRoutine(user1, new Chest());
            Activist.SelectRoutine(user1, new Leg());

            // 방문자 1 -> 오늘 루틴으로 운동시작
            Activist.StartRoutine(user1);

            // 방문자 2 -> 헬스장 방문
            IVisitor user2 = new User("헬창");

            // 방문자 2 -> 오늘의 루틴 선택
            Activist.SelectRoutine(user2, new Chest());
            Activist.SelectRoutine(user2, new Back());
            Activist.SelectRoutine(user2, new Leg());

            // 방문자 2 -> 오늘 루틴으로 운동시작
            Activist.StartRoutine(user2);
        }
    }
}

 

- 실행 결과


<참고 사이트>

 

C#으로 작성된 비지터 / 디자인 패턴들

 

refactoring.guru

 

[디자인 패턴 15편] 행동 패턴, 방문자 (Visitor)

1. 개념 비지터 패턴은 방문자와 방문 공간을 분리하여, 방문 공간이 방문자를 맞이할 때, 이후에 대한 행동을 방문자에게 위임하는 패턴이다. 간단하게 설명하기 참 어려운 패턴이다. 보통 OOP에

dailyheumsi.tistory.com

 

[디자인패턴] 방문자패턴(Visitor Pattern)

Visitor는 사전적인 의미로 어떤 장소에 찾아오는 사람이라는 의미를 갖고 있다. 방문자 패턴에서는 데이터 구조와 처리를 분리한다. 데이터 구조 안을 돌아다니는 주체인 방문자를 나타내는 클

velog.io

 

비지터 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org