데코레이터 노드의 단일 자식 제약 (Single Child Constraint of Decorator Nodes)
1. 개요
데코레이터 노드의 단일 자식 제약(single child constraint)은 데코레이터가 정확히 하나의 자식 노드만을 가져야 한다는 구조적 규칙이다. 이 제약은 데코레이터의 역할을 단일 자식에 대한 동작 수정으로 한정하며, 복수 자식 간의 흐름 제어는 제어 노드(Sequence, Fallback 등)의 역할로 명확히 분리한다. 본 절에서는 이 제약의 이론적 근거, 구현 메커니즘, 설계상의 함의를 다룬다.
2. 단일 자식 제약의 이론적 근거
2.1 행동 트리의 노드 분류
행동 트리의 노드 분류 체계에서 자식 노드의 수는 노드의 역할을 구분하는 핵심 기준이다.
| 노드 유형 | 자식 수 | 수학적 표현 |
|---|---|---|
| 리프 노드 | 0 | \text{arity} = 0 (0항 연산) |
| 데코레이터 | 1 | \text{arity} = 1 (단항 연산) |
| 제어 노드 | \geq 2 | \text{arity} \geq 2 (다항 연산) |
데코레이터는 단항 연산자(unary operator)에 해당하며, 자식의 상태를 변환하는 함수 f : \mathcal{S} \rightarrow \mathcal{S}로 모델링된다. 복수 자식에 대한 연산은 다항 연산자(n-ary operator)로, 이는 제어 노드가 담당한다.
2.2 관심사의 분리
단일 자식 제약은 관심사의 분리(Separation of Concerns) 원칙을 구현한다. 데코레이터는 “어떻게 실행할 것인가”(실행 방식의 수정)에 집중하고, 제어 노드는 “어떤 순서로 실행할 것인가”(실행 흐름의 제어)에 집중한다.
3. BehaviorTree.CPP에서의 구현
3.1 DecoratorNode 기반 클래스
class DecoratorNode : public TreeNode
{
public:
void setChild(TreeNode* child)
{
child_node_ = child;
}
TreeNode* child() { return child_node_; }
// 자식이 없으면 FAILURE 반환
void halt() override
{
if (child_node_)
{
haltChild();
}
setStatus(NodeStatus::IDLE);
}
protected:
TreeNode* child_node_ = nullptr;
};
DecoratorNode는 단일 child_node_ 포인터만을 유지하며, 복수의 자식을 설정하는 인터페이스를 제공하지 않는다.
3.2 XML 파서의 제약 강제
BehaviorTree.CPP의 XML 파서는 데코레이터 노드에 복수의 자식이 지정된 경우 파싱 오류를 발생시킨다.
<!-- 올바른 사용: 단일 자식 -->
<Inverter>
<Condition ID="IsObstacleDetected"/>
</Inverter>
<!-- 오류: 복수 자식 -->
<Inverter>
<Condition ID="A"/>
<Condition ID="B"/> <!-- 파싱 오류 발생 -->
</Inverter>
4. 복수 자식이 필요한 경우의 대안
데코레이터를 복수 자식에 적용하여야 하는 상황에서는 다음 대안을 사용한다.
4.1 Sequence/Fallback으로 감싸기
복수의 자식을 Sequence나 Fallback으로 감싼 후, 이를 데코레이터의 자식으로 지정한다.
<Inverter>
<Sequence>
<Condition ID="A"/>
<Condition ID="B"/>
</Sequence>
</Inverter>
위 구조는 \neg(A \wedge B)를 표현하며, 데코레이터의 단일 자식 제약을 준수하면서 복수 조건에 대한 반전을 구현한다.
4.2 개별 적용
각 자식에 데코레이터를 개별적으로 적용한다.
<Sequence>
<Inverter><Condition ID="A"/></Inverter>
<Inverter><Condition ID="B"/></Inverter>
</Sequence>
이 구조는 \neg A \wedge \neg B를 표현하며, 드모르간 법칙에 의해 \neg(A \vee B)와 동치이다.
5. 단일 자식 제약의 이점
5.1 구현의 단순성
데코레이터는 단일 자식만을 관리하므로, tick 전파, halt 전파, 상태 관리의 구현이 단순하다.
5.2 조합의 유연성
단일 책임 데코레이터를 중첩하여 복합 동작을 구현하면, 각 데코레이터를 독립적으로 추가/제거할 수 있어 유연성이 높다.
5.3 예측 가능한 동작
데코레이터가 단일 자식에 대해서만 동작하므로, 데코레이터의 효과가 어떤 범위에 적용되는지가 명확하다.
6. 설계 시 고려 사항
6.1 데코레이터와 서브트리의 결합
복잡한 하위 트리에 데코레이터를 적용할 때, 서브트리 전체를 단일 자식으로 취급하여 데코레이터의 효과가 서브트리 전체에 적용되도록 한다.
<Timeout msec="30000">
<SubTree ID="ComplexMission"/>
</Timeout>
6.2 데코레이터 내부의 자식 타입 제약
BehaviorTree.CPP에서 데코레이터의 자식은 모든 노드 타입(제어 노드, 다른 데코레이터, 리프 노드)이 될 수 있다. 자식 타입에 대한 제약은 데코레이터 자체에서 강제하지 않으며, 논리적 올바름은 설계자의 책임이다.
7. 참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Gamma, E., et al. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
- BehaviorTree.CPP 공식 문서. https://www.behaviortree.dev/
| 버전 | 날짜 | 변경 사항 |
|---|---|---|
| v0.1 | 2026-04-04 | 초안 작성 |