28.3.2. 파생 클래스(Derived Classes)의 계층적 상속 구조 및 알고리즘

28.3.2. 파생 클래스(Derived Classes)의 계층적 상속 구조 및 알고리즘

PX4의 FlightTask 프레임워크는 단순히 부모-자식의 1차원적인 상속(Inheritance) 관계에 머물지 않는다. 실리콘밸리의 거대 소프트웨어 기업들이 즐겨 쓰는 다단계 계층적 상속(Multi-level Hierarchical Inheritance) 구조를 도입하여, 수십 개의 비행 모드 사이에 겹치는 중복 코드를 극단적으로 쥐어짜 내는(DRY: Don’t Repeat Yourself) 아키텍처의 진수를 보여준다.

1. 다단계 상속 트리 (Multi-level Inheritance Tree)

예를 들어 조종사의 조종기 스틱을 치면 드론이 그 방향으로 등속도 비행을 하는 ’포지션(Position) 모드’를 상상해 보자. 이 모드를 구현하는 클래스 FlightTaskManualPosition은 최상위 기저 클래스인 FlightTask를 직접 다이렉트로 상속받지 않는다. 그 사이에는 FlightTaskManual이라는 중간 관리자(Intermediate Class)가 끼어 있다.

상속의 족보(Tree)를 그려보면 다음과 같다.

  • 1세대 (최상위): FlightTask (센서 교류, OS 통신 뼈대)
  • 2세대 (중간 관리자): FlightTaskManual, FlightTaskAuto 등 (입력 소스별 분류)
  • 3세대 (실무자): FlightTaskManualPosition, FlightTaskManualAltitude, FlightTaskAutoMission 등 (실제 개별 비행 모드)

이러한 수직적 다단계 계층 구조는 각 세대가 자신이 가장 잘하는 **전문 분야(Domain of Concern)**에만 책임을 집중할 수 있도록 완벽한 분업 체계를 강제한다.

2. 중간 관리자 클래스의 역할: 공통 알고리즘의 캐싱

2세대 클래스인 FlightTaskManual 클래스를 뜯어보면, 이 녀석 자체도 직접 인스턴스화되지 않는 절반의 추상 클래스 성격을 띤다. 이 클래스의 유일한 존재 이유는 **“인간 조종사가 조종기 스틱을 어느 방향으로 얼마나 세게 치고 있는가?”**에 대한 인간-기계 인터페이스(HMI) 데이터를 전처리하는 것이다.

  • 스틱 역치(Stick Input) 디코딩: 인간이 손가락으로 미는 RC 조종기의 원시 PWM 신호나 MAVLink 조이스틱 신호를 수신하여, 이를 -1.0에서 +1.0 사이의 정규화된 실수(Normalized Float) 변수(_sticks)로 이쁘게 가공한다.
  • 데드존(Deadzone) 및 엑스포(Expo) 필터링: 조종기 스틱 중앙의 기계적 떨림을 무시하는 데드존(Deadzone) 필터 알고리즘이나, 스틱을 살짝 칠 때는 둔감하고 끝까지 쳤을 때 확 꺾이게 만드는 엑스포넨셜(Exponential) 비선형 곡선 변환 로직이 바로 이 2세대 클래스에서 공통으로 처리된다.

따라서 3세대 자식들인 수동 고도 모드(FlightTaskManualAltitude)나 수동 위치 모드(FlightTaskManualPosition) 클래스 개발자들은 조종기의 데드존이나 노이즈 필터 따위는 전혀 신경 쓸 필요가 없다. 그저 부모가 물려준 깔끔한 -1.0 ~ 1.0 사이의 _sticks 변수 하나만 가져다가 비례 상수(Gain)를 곱해주면 끝이다.

3. 알고리즘 오버라이딩과 협업 (Virtual Override & Super Call)

계층적 상속 구조에서 가장 아름다운 순간은 각 세대의 알고리즘이 바통 터치(Baton Touch)를 하며 협업할 때다.

앞서 설명한 템플릿 메서드 패턴에 의해 update() 가상 함수가 호출되면, 최하단 3세대 클래스는 자신의 고유 수식을 전개하기에 앞서 의무적으로 부모의 update() 함수를 먼저 호출(super() 호출 개념)하여 알고리즘 체인을 형성한다.

// 3세대: 수동 포지션 모드의 update() 의사 코드
bool FlightTaskManualPosition::update() {
    // 1. 2세대 부모(FlightTaskManual)의 함수를 먼저 호출
    // 부모가 조종기 스틱 데이터(_sticks)를 알아서 계산해 준다.
    bool ret = FlightTaskManual::update();
    
    // 2. 3세대(나만의 고유 로직) 실행
    // 부모가 계산해 준 스틱 값에 최대 속도(MPC_VEL_MAN)를 곱해 목표 궤적 산출
    _trajectory_setpoint.velocity[0] = _sticks.x * _param_mpc_vel_man.get();
    _trajectory_setpoint.velocity[1] = _sticks.y * _param_mpc_vel_man.get();
    
    return ret;
}

이처럼 PX4의 파생 클래스 설계는 수백 가지의 복잡한 물리 법칙과 노이즈 필터링 로직들을 한 파일에 몰아넣는 대신, 상속의 계단을 타고 내려오면서 한 겹씩 한 겹씩 순차적으로 필터링하도록 찢어놓았다. 이 거대한 모듈의 분리(Separation of Concerns)야말로 복잡계 무인기 시스템을 인간의 머리로 이해하고 디버깅할 수 있게 만들어주는 핵심 추상화 마법이다.