커플링 현상
커플링 현상은 여러 클래스들이 코드 상에서 복잡하게 연결되어 있는 것을 말한다. 커플링 현상이 심해질 수록 유지보수가 어려운 코드가 된다.
델리게이트
델리게이트(delegate)의 사전적 의미는 '대표자', '대리자' 라는 뜻이다.
델리게이트는 일종의 메서드 리스트라고 생각하면 이해하기 쉽다.(메서드 리스트라는 용어는 단순히 이해를 쉽게 하는 것을 돕기 위해 사용한 비유적 표현이다. 공식 문서에서는 메서드 참조, 메서드 포인터로 설명하는 경우가 많다.) 델리게이트 객체에는 추가된 메서드들의 포인터(참조)가 저장되고, 이를 통해 추가된 메서드들을 코드 상에서 커플링 현상을 줄이면서 여러 기능들을 실행할 수 있다.
여기에서 중요한 점은 델리게이트 자체는 자신의 델리게이트 객체에 어떤 메서드가 추가되었는 지 전혀 신경쓰지 않고 그저 실행만 한다는 점이다. 이런 특징 때문에 커플링 현상을 줄일 수 있다.
델리게이트는 형식을 지정해 특정 형태의 메서드만 델리게이트 객체에 추가될 수 있게 할 수 있다.
delegate float Calculate(float a, float b); // float값 리턴, 매개변수 float로 두 개 받는 형태만 적용
Calculate onCalculate; // 델리게이트 객체, 이 객체가 형식에 맞는 메서드들을 대신 수행 가능
private void Start()
{
// 메서드들을 델리게이트 (일종의 실행할 메서드 리스트라고 생각)에 등록
onCalculate = Sum;
onCalculate += Subtract; // 덧셈으로 체이닝 가능
onCalculate += Multiply;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
onCalculate(1, 10); // 형식에 맞게 매개변수 받아서 등록된 메서드들 모두 순차적으로 실행
}
}
public float Sum(float a, float b)
{
Debug.Log(a + b);
return a + b;
}
public float Subtract(float a, float b)
{
Debug.Log(a - b);
return a - b;
}
public float Multiply(float a, float b)
{
Debug.Log(a * b);
return a * b;
}
delegate 반환자료형 델리게이트이름(형식)
델리게이트이름 델리게이트객체이름
ex) delegate float Calculate(float a, float b)
Calculate onCalculate;
델리게이트를 먼저 선언하고, 그에 맞는 델리게이트 객체를 만든다.
델리게이트 객체에는 메서드 이름으로 추가(+=), 삭제(-=), 대입(=)이 가능하다.
+=와 -=는 메서드 리스트에서 해당 메서드를 추가하거나 삭제할 수 있고, =는 모든 정보를 지우고 해당 메서드만 집어넣는다.
델리게이트가 실행되면 +=로 리스트에 추가된 모든 메서드들이 순차적으로 실행되는 데, 이렇게 연속적으로 메서드들을 호출하는 것을 델리게이트 체인(chain)이라고 한다.
델리게이트 객체의 이름으로 일반 메서드처럼 호출하면 된다.
ex) onCalculate(1, 10);
액션
액션은 델리게이트의 한 종류이다. 리턴 값이 void이고 매개변수가 없는 메서드에 대한 델리게이트를 작성하는 일이 매우 많기 때문에, C#에서는 그것을 Action이라는 형태로 미리 만들어 두었다.
사용하기 위해서는 using System;으로 System 클래스를 가져와야 한다. 델리게이트와 델리게이트 객체를 따로 선언한 필요 없이 Action 객체만 선언하면 된다.
// delegate void Action(); 이 미리 선언된 것과 같음
Action work;
이벤트의 개념과 델리게이트와의 차이
이벤트란?
이벤트는 델리게이트를 기반으로 구현되며, 델리게이트의 기능을 확장해 더 엄격한 규칙을 적용할 수 있게 한다.
이벤트에서는 이벤트 객체를 선언한 주체 publisher와 이벤트 객체에 메서드를 추가하는 개체 subscriber가 있다. subscriber는 이벤트 객체에 메서드의 추가와 삭제만 가능하고, 호출이나 대입은 불가능하다. 이를 통해 캡슐화 개념을 적용하고 안정성을 높일 수 있다.
델리게이트 객체를 선언할 때 event 키워드를 추가해 이벤트 객체를 만들 수 있다.
델리게이트와의 차이
기본적으로 매우 정교하게 설계된 코드라면, 델리게이트만으로도 이벤트를 구현할 수 있다. event 키워드가 따로 있는 이유는 프로그래머의 실수로 인해 델리게이트가 이벤트가 아닌 다른 방향으로 구현되는 것을 방지하기 위함이다.
상기 내용처럼, 델리게이트는 델리게이트 객체를 자유롭게 제어할 수 있었다. 하지만 이벤트는 publisher와 subscriber라는 개념이 있기 때문에 이벤트 객체 제어를 아무나 자유롭게 해서는 안 된다. 그렇기 때문에 event 키워드를 사용해 이벤트 객체를 만들어 해당 객체가 선언된 클래스를 publisher, 나머지 클래스를 subscriber로 만들어 버릴 수 있다.
좀 복잡한 개념인데 정리하자면
- publisher, subscriber 구분 없이 모든 클래스에서 델리게이트 객체를 통해 델리게이트를 제어 가능 (추가, 삭제, 대입, 호출 등)
- publisher와 subscriber가 엄격하게 나누어짐
- publisher만 델리게이트의 자유로운 제어가 가능하고, subscriber는 추가(+=), 삭제(-=)만 가능
예시
public class Character : MonoBehaviour // publisher
{
public string playerName = "player";
public float hp = 100;
public float defense = 50;
public float damage = 30;
public delegate void Boost(Character target);
public event Boost onPlayerUseBoost;
private void Start()
{
onPlayerUseBoost(this);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
onPlayerUseBoost(this);
}
}
}
public class Booster : MonoBehaviour // subscriber
{
private void Awake()
{
Character player = FindObjectOfType<Character>();
player.onPlayerUseBoost += HealthBoost;
player.onPlayerUseBoost += ShieldBoost;
player.onPlayerUseBoost += DamageBoost;
}
public void HealthBoost(Character target)
{
Debug.Log(target.playerName + "의 체력을 강화했다!");
target.hp += 10;
}
public void ShieldBoost(Character target)
{
Debug.Log(target.playerName + "의 방어력을 강화했다!");
target.defense += 10;
}
public void DamageBoost(Character target)
{
Debug.Log(target.playerName + "의 방어력을 강화했다!");
target.damage += 10;
}
}