# 리플렉션

리플렉션(Reflection)은 프로그램이 실행 중에 자신의 구조를 검사하고 수정할 수 있는 메커니즘을 의미한다. Dart에서 리플렉션은 주로 `dart:mirrors` 라이브러리를 통해 이루어지며, 이를 통해 클래스, 메소드, 변수 등의 구조를 런타임에 분석할 수 있다. 리플렉션을 사용하면 컴파일 시점에 알 수 없는 동적 구조를 다루거나, 런타임에 동적으로 객체를 생성하고 메소드를 호출할 수 있다.

#### 리플렉션의 개념과 동작 방식

리플렉션은 프로그램이 컴파일된 이후에도 클래스의 정의, 메소드, 필드 등의 정보를 조회하고 조작할 수 있도록 한다. Dart의 리플렉션 기능은 런타임 시점에 다양한 구조를 탐색할 수 있는 기능을 제공하며, 이 과정에서 `Mirror` 객체를 사용하여 접근하게 된다.

예를 들어, 클래스의 모든 메소드를 조회하거나 객체에 동적으로 메소드를 호출하는 기능을 구현할 수 있다.

#### Mirrors API

Dart의 `dart:mirrors` 패키지를 통해 리플렉션을 사용할 수 있다. 이 패키지는 다음과 같은 핵심 컴포넌트를 포함한다:

* **`Mirror`**: 모든 미러 객체의 기본 인터페이스이다. 미러는 클래스, 메소드, 인스턴스 등의 메타데이터를 표현하는 역할을 한다.
* **`ClassMirror`**: 클래스에 대한 미러로, 클래스의 메소드, 필드 등을 조회할 수 있다.
* **`InstanceMirror`**: 인스턴스에 대한 미러로, 인스턴스의 상태나 메소드를 호출할 수 있다.
* **`MethodMirror`**: 메소드에 대한 미러로, 메소드의 이름, 매개변수 등을 조회할 수 있다.

#### 리플렉션을 활용한 클래스 정보 탐색

리플렉션을 사용하여 클래스의 구조를 탐색하는 예제를 살펴보자. Dart에서 특정 클래스의 메소드 목록을 조회하고 해당 메소드들을 동적으로 호출할 수 있다.

```dart
import 'dart:mirrors';

class Sample {
  void sayHello() {
    print('Hello!');
  }

  void sayGoodbye() {
    print('Goodbye!');
  }
}

void main() {
  var instance = Sample();
  var mirror = reflect(instance);
  
  mirror.type.declarations.forEach((symbol, declaration) {
    if (declaration is MethodMirror && !declaration.isConstructor) {
      print(MirrorSystem.getName(symbol));
    }
  });
}
```

위 코드에서 `reflect()` 함수를 사용하여 `Sample` 클래스의 인스턴스를 미러링하고, `declarations`를 통해 클래스의 모든 메소드를 나열할 수 있다.

#### 리플렉션의 한계

Dart의 리플렉션은 매우 강력하지만 몇 가지 한계점이 존재한다:

* **성능 이슈**: 리플렉션은 런타임 시점에 프로그램의 구조를 분석하기 때문에 성능에 영향을 줄 수 있다. 특히 대규모 애플리케이션에서 리플렉션을 남용하면 프로그램이 느려질 수 있다.
* **플랫폼 제약**: Dart의 `dart:mirrors` 패키지는 웹에서 사용할 수 없다. 이 때문에 Flutter 웹이나 Dart 웹 애플리케이션에서는 리플렉션을 사용할 수 없다는 제약이 있다.
* **강한 타입 검사 부족**: 리플렉션을 사용하면 컴파일 타임에 타입 검사를 우회할 수 있다. 이는 프로그램의 안정성을 떨어뜨릴 수 있는 요인 중 하나이다.

#### 리플렉션과 동적 메소드 호출

리플렉션을 사용하여 런타임에 메소드를 동적으로 호출하는 방식도 가능한다. 런타임에 메소드의 이름을 문자열로 받아 해당 메소드를 호출할 수 있다. 예를 들어, 아래 코드는 `sayHello`와 `sayGoodbye` 메소드를 문자열을 통해 호출하는 방법을 보여준다.

```dart
import 'dart:mirrors';

class Sample {
  void sayHello() {
    print('Hello!');
  }

  void sayGoodbye() {
    print('Goodbye!');
  }
}

void main() {
  var instance = Sample();
  var mirror = reflect(instance);
  
  var methodName = 'sayHello';  // 런타임에 문자열로 메소드 이름을 정의
  
  mirror.invoke(Symbol(methodName), []);  // 메소드 호출
}
```

위의 코드에서 `invoke()` 메소드를 사용해 `sayHello`라는 메소드를 동적으로 호출하고 있다. 이처럼 Dart의 리플렉션을 통해 런타임에 메소드 호출을 유연하게 처리할 수 있다.

#### 리플렉션을 통한 필드 접근

리플렉션을 사용하여 객체의 필드를 동적으로 읽거나 수정하는 것도 가능한다. 예를 들어, 특정 클래스의 필드 값을 리플렉션을 통해 조회하거나 변경할 수 있다. 이를 통해 개발자는 런타임에 필드 값에 동적으로 접근할 수 있다.

```dart
import 'dart:mirrors';

class Person {
  String name = 'John';
  int age = 30;
}

void main() {
  var person = Person();
  var mirror = reflect(person);
  
  // 필드 조회
  var nameField = mirror.getField(#name).reflectee;
  print('Name: $nameField');
  
  // 필드 수정
  mirror.setField(#name, 'Alice');
  var updatedNameField = mirror.getField(#name).reflectee;
  print('Updated Name: $updatedNameField');
}
```

위 코드에서 `getField()`를 통해 객체의 필드 값을 읽을 수 있으며, `setField()`를 사용해 필드 값을 수정할 수 있다. `Symbol` 타입을 사용해 필드의 이름을 나타낸다.

#### 메타프로그래밍과 리플렉션의 관계

메타프로그래밍은 프로그램이 자신을 변경하거나 프로그램의 일부를 생성하는 것을 의미하며, 리플렉션은 메타프로그래밍의 중요한 도구 중 하나이다. Dart에서 리플렉션은 런타임 시점에 객체의 구조를 분석하고 이를 동적으로 조작하는 기능을 제공한다. 이는 코드의 유연성을 높이고, 특히 대규모 애플리케이션에서 다양한 컴포넌트를 동적으로 처리할 수 있게 한다.

예를 들어, 플러그인 시스템에서 동적으로 컴포넌트를 로드하거나 다양한 객체를 유연하게 처리할 때 리플렉션이 유용하게 사용될 수 있다.

#### 런타임 타입 검사와 리플렉션

리플렉션은 타입 검사를 런타임에 수행할 수 있는 기능도 제공한다. Dart의 리플렉션을 사용하여 객체의 타입을 확인하고 해당 타입에 따른 동작을 다르게 구현할 수 있다.

```dart
import 'dart:mirrors';

void printType(dynamic value) {
  var mirror = reflect(value);
  print('Type: ${mirror.type.reflectedType}');
}

void main() {
  var name = 'Alice';
  var age = 30;
  
  printType(name);
  printType(age);
}
```

이 코드는 객체의 타입을 런타임에 출력하는 예시이다. `reflectedType`을 사용하면 런타임에 객체의 타입을 확인할 수 있다.

#### 객체 생성과 리플렉션

리플렉션은 클래스의 인스턴스를 동적으로 생성할 때도 유용하게 사용된다. Dart에서 리플렉션을 사용해 클래스의 생성자를 호출하고 객체를 생성할 수 있다. 다음은 리플렉션을 사용해 클래스의 인스턴스를 동적으로 생성하는 예제이다.

```dart
import 'dart:mirrors';

class Car {
  String model;
  int year;
  
  Car(this.model, this.year);
}

void main() {
  var carClassMirror = reflectClass(Car);
  
  // 생성자를 호출하여 인스턴스 생성
  var carInstance = carClassMirror.newInstance(Symbol(''), ['Model S', 2020]);
  print(carInstance.reflectee.model);
  print(carInstance.reflectee.year);
}
```

위 코드에서 `newInstance()` 메소드를 사용하여 동적으로 객체를 생성하고 있다. 생성자에 전달할 인자를 리스트로 제공하여 객체가 초기화된다.

#### 리플렉션과 JSON 직렬화

리플렉션은 객체를 JSON으로 직렬화하거나 역직렬화할 때도 유용하게 사용될 수 있다. 특히 객체의 필드들을 자동으로 탐색하여 JSON 형식으로 변환하거나, 반대로 JSON 데이터를 객체로 변환할 때 리플렉션을 사용하면 편리한다.

```dart
import 'dart:mirrors';

class Person {
  String name;
  int age;
  
  Person(this.name, this.age);
  
  Map<String, dynamic> toJson() {
    var instanceMirror = reflect(this);
    var data = {};
    
    instanceMirror.type.declarations.forEach((symbol, declaration) {
      if (declaration is VariableMirror) {
        var fieldName = MirrorSystem.getName(symbol);
        var fieldValue = instanceMirror.getField(symbol).reflectee;
        data[fieldName] = fieldValue;
      }
    });
    
    return data;
  }
}

void main() {
  var person = Person('Alice', 25);
  var json = person.toJson();
  print(json);  // 출력: {name: Alice, age: 25}
}
```

이 예시에서는 리플렉션을 사용하여 클래스의 모든 필드를 탐색하고 이를 JSON 형식으로 변환하고 있다. `declarations`를 통해 객체의 필드 정보를 얻고, 이를 바탕으로 JSON 데이터를 생성한다.

#### 리플렉션과 플러그인 시스템

리플렉션을 활용하여 런타임에 다양한 모듈을 동적으로 로드할 수 있는 플러그인 시스템을 구현할 수 있다. 플러그인 시스템에서는 여러 모듈을 미리 정의하지 않고, 런타임에 필요에 따라 동적으로 컴포넌트를 로드하여 확장성을 제공한다. 리플렉션을 통해 각 모듈의 메타데이터를 탐색하고 동적으로 객체를 생성하여, 새로운 기능을 추가할 수 있다.

예를 들어, 다음은 간단한 플러그인 시스템을 리플렉션으로 구현한 예제이다.

```dart
import 'dart:mirrors';

abstract class Plugin {
  void execute();
}

class PluginA implements Plugin {
  @override
  void execute() {
    print('PluginA executed');
  }
}

class PluginB implements Plugin {
  @override
  void execute() {
    print('PluginB executed');
  }
}

void loadAndExecutePlugin(String className) {
  var classMirror = reflectClass(Symbol(className));
  var pluginInstance = classMirror.newInstance(Symbol(''), []);
  pluginInstance.invoke(Symbol('execute'), []);
}

void main() {
  loadAndExecutePlugin('PluginA');
  loadAndExecutePlugin('PluginB');
}
```

위 예제에서 `PluginA`와 `PluginB`는 `Plugin` 인터페이스를 구현하는 플러그인이다. `loadAndExecutePlugin()` 함수는 리플렉션을 사용하여 런타임에 클래스 이름을 문자열로 받아 해당 클래스의 인스턴스를 생성하고 메소드를 호출한다. 이를 통해 플러그인을 동적으로 로드하고 실행할 수 있다.

#### 리플렉션의 장단점

**장점**

1. **유연성**: 리플렉션을 사용하면 프로그램의 동작을 런타임에 변경할 수 있으며, 컴파일 타임에 정의되지 않은 동작도 유연하게 처리할 수 있다.
2. **동적 모듈 로드**: 리플렉션은 플러그인 시스템처럼 동적으로 모듈을 로드하거나 메소드를 호출할 수 있도록 지원하여 확장성을 높인다.
3. **메타프로그래밍 지원**: 프로그램 자체를 분석하고 수정할 수 있는 기능을 제공하여, 메타프로그래밍과 같은 고급 기법을 쉽게 구현할 수 있다.

**단점**

1. **성능 저하**: 리플렉션은 런타임에 많은 리소스를 소비한다. 메소드 호출이나 클래스 탐색이 컴파일 타임에 비해 느릴 수 있으며, 이는 대규모 애플리케이션에서 성능 문제로 이어질 수 있다.
2. **타입 안전성 저하**: 리플렉션은 동적으로 메소드와 필드를 호출하므로, 타입 검사나 호출 오류가 컴파일 타임에 감지되지 않는다. 이는 프로그램의 안정성을 떨어뜨릴 수 있다.
3. **플랫폼 제약**: Dart의 리플렉션은 웹 애플리케이션에서는 사용할 수 없으며, Flutter 웹에서는 `dart:mirrors` 패키지를 사용할 수 없다.

#### 리플렉션 사용 시 주의 사항

리플렉션은 강력한 도구이지만, 남용하면 프로그램의 성능과 유지보수성에 부정적인 영향을 줄 수 있다. 특히 다음 사항을 염두에 두고 사용해야 한다:

1. **성능 최적화**: 리플렉션은 런타임에 동작하는 만큼, 가능한 한 최소한으로 사용해야 한다. 성능이 중요한 부분에서는 리플렉션 대신 다른 방법을 고려해야 한다.
2. **플랫폼 의존성**: 리플렉션은 Dart의 웹 환경에서 지원되지 않으므로, 웹 기반 애플리케이션에서는 사용할 수 없다. 이를 염두에 두고 플랫폼에 맞는 코드를 작성해야 한다.
3. **타입 안전성**: 리플렉션은 컴파일 타임 타입 검사를 우회할 수 있으므로, 런타임 오류를 방지하기 위해 코드에 대한 철저한 검증이 필요하다.

#### 리플렉션과 테스트

리플렉션은 테스트 자동화에도 활용될 수 있다. 예를 들어, 여러 메소드를 일괄적으로 호출해 테스트하는 경우, 리플렉션을 통해 메소드 이름을 동적으로 확인하고 자동으로 테스트를 실행할 수 있다. 이는 대규모 테스트 환경에서 매우 유용하게 사용될 수 있다.

```dart
import 'dart:mirrors';

class TestSuite {
  void testA() {
    print('Test A passed');
  }
  
  void testB() {
    print('Test B passed');
  }
}

void runAllTests(Object instance) {
  var instanceMirror = reflect(instance);
  
  instanceMirror.type.declarations.forEach((symbol, declaration) {
    if (declaration is MethodMirror && !declaration.isConstructor) {
      instanceMirror.invoke(symbol, []);
    }
  });
}

void main() {
  var tests = TestSuite();
  runAllTests(tests);
}
```

위 코드에서 `runAllTests()` 함수는 `TestSuite` 클래스에 정의된 모든 메소드를 동적으로 호출하여 테스트를 실행한다. 이를 통해 테스트 케이스를 자동화할 수 있으며, 테스트 코드를 관리하는 데 도움을 준다.
