동적 바인딩과 스타크래프트로 보는 오버라이딩

yuzu sim's avatar
Jan 04, 2024
동적 바인딩과 스타크래프트로 보는 오버라이딩

🍊 동적 바인딩이란?

오버라이드 된 메서드 호출이 컴파일 시간이 아닌 실행 시간에 결정 되는 메커니즘을 의미한다.
오버라이드 된 메서드가 부모 클래스 참조를 통해 호출되는 경우, 객체의 타입에 따라
서로 다른 메서드가 호출 되게하는 메커니즘이다.
즉, 실제 타입이 호출 되는 메서드를 결정하는 것이다.
 

🍊 동적 바인딩의 장점

  • 코드의 유연성과 확장성을 높인다.
  • 런타임 다형성을 가능하게 한다.
  • 코드 재사용과 유지보수 용이성을 증진 시킨다.
  • 디버깅 개선
 
따라서 동적 바인딩은 객체 지향 프로그래밍의 핵심 개념 중 하나로, 다양한 객체를 다루며 유연하고 확장 가능한 소프트웨어를 작성하는데 기여한다.
 
예제코드
스타그래프트 게임을 그림과 같이 코드로 구성했다고 해보자.
notion image
 
부모 Protoss에 상속된 클래스들이다.
프로토스라는 종족에 다양한 객체들이 생성이 되어있다. 이 다양한 객체들이 전투에 나가서 싸우는 것을 가정으로 프로그래밍을 한다면?? 실제로 네 개의 객체가 있는 것 만으로도 전투에서 2의 4승, 즉 16가지의 경우의 수를 만들어 낸다.
이를 코드로 표현한다면 이런 모습이 된다.
package ex06.example3; public class StarApp { // 메서드 만들기 public static void gameStart(Zealot u1, Dragoon u2) { u1.attack(); // 질럿 u2.attack(); // 드라군 } // 메서드 이름이 동일해도 타입 이름이 다르면 오버로딩 // Dragoon 타입을 --> u1으로, Zealot 타입을 --> u2로 바꿔준다. // public static void gameStart(Dragoon u1, Zealot u2) { u1.attack(); // 여기서 호출 될때마다 동적바인딩으로 재정의 됨 u2.attack(); } public static void gameStart(Zealot u1, Zealot u2) { u1.attack(); u2.attack(); } public static void gameStart(Dragoon u1, Dragoon u2) { u1.attack(); u2.attack(); } public static void gameStart(River u1, River u2) { u1.attack(); u2.attack(); } public static void gameStart(River u1, Zealot u2) { u1.attack(); u2.attack(); } public static void gameStart(River u1, Dragoon u2) { u1.attack(); u2.attack(); } public static void gameStart(Zealot u1, River u2) { u1.attack(); u2.attack(); } public static void gameStart(Dragoon u1, River u2) { u1.attack(); u2.attack(); } public static void main(String[] args) { // 질럿이랑 드라군 만들어서 띄움 Zealot z1 = new Zealot("z1"); // 질럿 Dragoon d1 = new Dragoon("d1"); // 드라군 Zealot z2 = new Zealot("z2"); gameStart(z1, d1); gameStart(d1, z1); // gameStart(z1, z2); // gameStart(z2, z1); } }
이 4개의 객체만 있어서 이런 경우의 수를 만들어 내는데, 스타크래프트 게임에는 수많은 객체가 출현한다.
그럼 그 많은 경우의 수를 다 이렇게 일일이 코드로 작성이 해야 되는 걸까???
완전 번거롭지 않나?!!… 이것을 효율적으로 해주는 작업이 동적 바인딩이다!
위 코드를 어떻게 동적 바인딩으로 해야 될까??
 
동적 바인딩에는 상속을 해주는 부모 클래스의 메서드를 다양한 형태로
자식 클래스에서 오버 라이딩이 되어야 한다. (다양한 형태이지만 오버 라이딩의 문법은 부모의 메서드와 같은 이름으로 연결된다. 연결고리라고 이해하면 되겠다.)
package ex06.example4; public class Protoss { public void attack() { // 오버라이딩을 위한 부모 클래스의 메서드 } }
부모 Protoss 클래스
  • 부모 “Protoss”클래스를 생성해서 오버라이딩 될 수있는 메서드를 만들어야 된다.
  • attack()메서드에 아무것도 없어서… 이게 뭘까?? 싶을 수 있지만 이는 오버라이딩을 위한 문법이라 생각하면 되겠다.
 
package ex06.example4; // 상속은 재정의 하기 위해서 하는 것이 아니라 타입을 일치시켜서 // 동적 바인딩을 하기 위해 하는 것이다. public class Dragoon extends Protoss { String name; public Dragoon(String name) { this.name = name; } public void attack() { System.out.println("드라군이 공격합니다 : " + name); } }
자식 Dragoon 클래스
  • 자식인 “Dragoon”클래스에서 부모인 “Protoss”extends한 모습을 볼 수 있다.
  • 그리고 부모 클래스에 정의되어 있던 attack()메소드를 같은 이름으로 다시 정의 되었다.
  • 이는 힙 메모리에 해당 객체가 호출이 될 때 attack()메소드가 재정의 되어서 동적 바인딩이 런타임에 결정이 된다.
 
package ex06.example4; public class StarApp { // 정적 메서드 오버라이드 만들기 public static void gameStart(Protoss u1, Protoss u2) { u1.attack(); // 여기서 호출 될때마다 동적바인딩으로 재정의 됨 u2.attack(); System.out.println(); } public static void main(String[] args) { // 자식 클래스는 부모 클래스로 업캐스팅 가능하다. Protoss z1 = new Zealot("z1"); // [Zealot, Protoss] Protoss d1 = new Dragoon("d1"); // [Dragoon, Protoss] Protoss r1 = new River("r1"); // [River, Protoss] Protoss dark1 = new Dark("dark1"); // [Dark, Protoss] gameStart(z1, d1); // 동적 바인딩으로 코드가 간결해진다. gameStart(dark1, d1); } }
 
  • new키워드로 객체 생성시 자식클래스는 부모클래스로 업 캐스팅 (Up casting)이 가능하여, 일괄적으로 gameStart의 생성자에 변수로 대입이 가능하게 된다. 즉, 객체 생성할 때마다 초기화 해야 되는 일이 사라졌다.
  • u1.attack() u2.attack()이 호출 되는 부분에서 동적바인딩이 사용된다. 프로그램이 실행 될 때 JVM은 ‘u1’ ‘u2’가 실제로 가리키고 있는 객체의 타입을 확인하고, 해당 타입에 오버라이드된 ‘attack()’ 메소드를 실행한다.
  • 이제 아까 복잡했던 코드는 아래 코드로 간결해짐으로서 가독성이 좋아졌다. 이제는 간단히 호출만 해주면 되는 것이다.
 
💡
스타크래프트 게임에서는 상속은 재정의 하기 위해서 하는 것이 아니라 타입을 일치 시켜서 동적 바인딩을 하기 위해 하는 것이라는 것을 알 수 있다.
 
 
 
 
 
 
 
 
 
 
Share article

Coding_study