
Visitor 1. Visitor
소프트웨어 개발에서 객체 구조가 복잡해지면 그 구조에 따라 다양한 연산을 수행해야 하는 경우가 많습니다. 이때, 객체의 구조는 그대로 두고 새로운 연산을 추가해야 하는 상황이 발생할 수 있습니다. Visitor 패턴은 이러한 요구를 해결해주는 디자인 패턴으로, 객체 구조와 그 위에서 수행할 연산을 분리하여 객체 구조를 수정하지 않고도 새로운 연산을 쉽게 추가할 수 있도록 해줍니다.
Visitor 패턴이란?
Visitor 패턴은 객체 구조를 변경하지 않고도 새로운 연산을 추가할 수 있는 디자인 패턴입니다. 이를 통해 복잡한 조건문 없이 객체 타입에 따라 다르게 동작하는 연산을 분리된 Visitor 클래스에 정의할 수 있습니다. 객체가 연산을 받아들이는 방식은 Visitor 객체가 처리하며, 객체 구조와 연산이 분리되기 때문에 유지보수와 확장성이 크게 향상됩니다.
Code Smell: Visitor 패턴이 필요한 순간
Visitor 패턴은 다음과 같은 Code Smell을 해결하는 데 특히 유용합니다.
-
Complex Conditions
여러 타입에 따라 다른 동작을 수행하는 복잡한if-else
나switch
문이 존재할 때, Visitor 패턴을 사용하면 조건문을 제거하고 각 타입에 맞는 Visitor 클래스를 작성하여 문제를 해결할 수 있습니다. -
Difficulty in Adding New Operation
새로운 연산을 추가할 때마다 기존 객체 클래스를 수정해야 하는 경우, Visitor 패턴을 사용하면 객체 구조를 변경하지 않고도 새로운 Visitor 클래스를 추가하여 쉽게 연산을 확장할 수 있습니다. -
산재된 연산 로직 (Scattered Operation Logic)
연산 로직이 여러 곳에 흩어져 있어 코드의 응집도가 낮은 경우, Visitor 패턴을 사용하면 연산 로직을 Visitor 클래스에 집중시켜 코드의 가독성과 유지보수성을 높일 수 있습니다.
Visitor 패턴 구현
다음은 Visitor 패턴의 간단한 구현 예시입니다. HealthComponent와 ManaComponent 같은 객체에 StatModifier
라는 Visitor를 적용해 각각의 상태 값을 증가시키는 연산을 수행합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface IVisitor
{
void Visit(HealthComponent health);
void Visit(ManaComponent mana);
}
public interface IVisitable
{
void Accept(IVisitor visitor);
}
public class StatModifier : IVisitor
{
private readonly int healthBonus;
private readonly int manaBonus;
public StatModifier(int health, int mana)
{
healthBonus = health;
manaBonus = mana;
}
public void Visit(HealthComponent health)
{
health.Value += healthBonus;
}
public void Visit(ManaComponent mana)
{
mana.Value += manaBonus;
}
}
위 코드는 StatModifier
라는 Visitor가 객체의 타입에 알맞은 연산을 수행하는 모습을 보여줍니다. 각 객체는 자신을 방문한 Visitor 객체를 받아들이고, 방문한 Visitor가 적절한 연산을 수행하도록 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class HealthComponent : IVisitable
{
public int Value;
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
public class ManaComponent : IVisitable
{
public int Value;
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
Visitor 패턴의 장점
-
객체 구조 변경 없이 새로운 연산 추가 가능 Visitor 패턴을 사용하면 객체의 구조를 변경하지 않고도 새로운 연산을 추가할 수 있습니다. 새로운 Visitor 클래스를 추가하는 방식으로 쉽게 확장할 수 있습니다.
-
복잡한 조건문 제거 Visitor 패턴을 사용하면 객체 타입에 따라 다른 연산을 수행하는 복잡한
if-else
또는switch
문을 없앨 수 있습니다. 이는 코드를 간결하고 명확하게 만들어줍니다. -
연산 로직 집중화 연산 로직을 각 객체에서 분리하여 Visitor 클래스로 집중시킬 수 있기 때문에, 코드의 응집도가 높아지고 유지보수도 더 쉬워집니다.
Visitor 패턴의 단점
-
객체 구조 변경이 어려움
Visitor 패턴은 객체 구조가 고정되어 있을 때 매우 유용하지만, 객체 구조에 변경이 생기면 모든 Visitor 클래스에 영향을 미치므로 유지보수가 어려울 수 있습니다. -
객체 간의 강한 결합
객체와 Visitor 사이의 결합도가 높아질 수 있어, 객체 구조와 연산 간의 의존성이 증가할 수 있습니다.
Simple Visitor : Visitor 패턴 단순화
Simple Visitor는 전통적인 Visitor 패턴을 단순화한 변형입니다. 기본적으로 객체 타입에 따라 다른 연산을 수행하는 점은 동일하지만, 기존 Visitor 패턴에서 사용하는 각 객체 타입별 Visit
메서드를 일일이 정의하지 않고, 단일 Visit()
메서드 내에서 객체 타입을 판별하는 방식으로 연산을 처리합니다.
Simple Visitor의 핵심 차이점
-
단일 메서드로 처리
전통적인 Visitor 패턴에서는 각각의 객체 타입에 대응하는Visit()
메서드를 별도로 정의합니다. 하지만 Simple Visitor에서는 하나의Visit()
메서드를 사용해is
키워드로 객체의 타입을 확인하고, 그에 맞는 연산을 처리합니다. 이를 통해 코드가 단순해지며, 각 타입에 대한Visit()
메서드를 별도로 만들 필요가 없어집니다. -
유연성 대신 간결성
Simple Visitor 패턴은 간결하게 객체 타입을 판별하고 연산을 수행할 수 있지만, 이는 연산 로직이 다시 분산될 수 있는 가능성을 내포합니다. 복잡한 로직이 필요한 상황에서는 Simple Visitor 패턴이 적합하지 않을 수 있습니다. 그러나, 간단한 연산이나 조건에 따라 처리가 필요한 경우에는 매우 유용할 수 있습니다.
코드 설명
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface IVisitor
{
void Visit(IVisitable visitable);
}
public class StatModifier : IVisitor
{
private readonly int healthBonus;
private readonly int manaBonus;
public StatModifier(int health, int mana)
{
healthBonus = health;
manaBonus = mana;
}
public void Visit(IVisitable visitable)
{
if (visitable is HealthComponent health)
{
health.Value += healthBonus;
}
else if (visitable is ManaComponent mana)
{
mana.Value += manaBonus;
}
}
}
이 예시에서 StatModifier는 Visit()
메서드 내에서 is
키워드를 사용하여 HealthComponent와 ManaComponent를 구분하고, 각각의 타입에 따라 보너스를 적용합니다. 이를 통해 연산이 간결하게 처리되며, 여러 객체 타입을 위한 별도의 Visit()
메서드를 정의할 필요가 없습니다.
마무리
Visitor 패턴은 객체 구조와 그 위에서 수행할 연산을 분리함으로써, 새로운 연산을 쉽게 추가하고 복잡한 조건문을 제거할 수 있는 강력한 디자인 패턴입니다. 특히 연산 로직을 여러 클래스에 분산시키는 대신, 하나의 Visitor 클래스로 모을 수 있어 코드의 응집도와 가독성이 향상됩니다.
Simple Visitor는 기존 Visitor 패턴의 복잡성을 줄이고, 다양한 타입을 하나의 메서드에서 처리할 수 있는 간결한 대안입니다. 그러나 객체 타입에 따라 연산 로직이 복잡해질 경우, 코드의 유지보수성이 떨어질 수 있으므로, 상황에 맞게 패턴을 적용하는 것이 중요합니다.