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);
}
}
}
- 실행 결과
<참고 사이트>