'상속'에 해당되는 글 1건

  1. 2007.12.04 자바공부-5.상속과 폴리모피즘 1

 상속을 폴리모피즘 측면에서 한 번 다시 살펴보겠습니다.

 
스타크래프트 유닛의 계층구조 I


스타크래프트에 등장하는 3가지 종족이 있습니다.

3가지 종족은 프로토스,테란,저그종족입니다. 또 각 종족별로 유닛이 등장합니다.

이를 곰곰히 살펴보면 개념이 계층적인 구조로 되어 있습니다. , 가장 큰개념은

스타크래프트라는 것이고 그 아래 3가지 종족, 그 아래 각각의 종족별 유닛이

있습니다. 스타크래프트와 같이 객체들 간에 계층구조의 형태를 띄고 있다면 이들을

설계할 때 상속의 개념을 도입하는 것도 좋은 선택이 됩니다.

 프로토스 유닛은 프로토스 종족으로서 공통의 개념을 갖고 있고, 저그 유닛은

저그 종족으로서 공통의 개념을 갖고 있으며, 테란종족은 테란으로서 공통의 개념을 갖고

있습니다. 이를 이용해서 충분히 클래스를 구성할 수 있습니다.

질럿은 프로토스 유닛이고, 드라군도 프로토스 유닛입니다. 히드라는 저그 유닛이구요.

물론 질럿은 스타크래프트유닛이고 히드라도 스타크래프트 유닛입니다.

이를 생각해보면 스타크래프트에 등장하는 유닛들은 4 - 4처럼 계층적인 구조를 나타낼

수 있습니다.


사용자 삽입 이미지
 

그림 4 - 4 [(starcraft class)]

스타크래프트 유닛들의 상속

 

그림 4 - 4 UML에서 나타난 클래스 다이어그램을 살펴보겠습니다.

 맨 위칸에 있는 것은 클래스의 이름이고, 둘째칸에 있는 것은 클래스가 가지는 멤버변수를

의미합니다.

멤버 변수앞에 private 정자를 의미하고 + public 지정자를 나타냅니다.

세번째 칸에있는 것은 클래스가 갖고 있는 메소드를 의미합니다. + public지정자

의미하고 ()안에는 메소드의 인자형을 의미하며, 맨뒤에 있는 것은 리턴형을 말합니다.

 

그림 4 - 4 를 보면 StarcraftUnit 클래스가 가장 기본적인 클래스입니다.

ZergUnit 클래스와  ProtossUnit 클래스, TerranUnit클래스는 StarcraftUnit 클래스를

상속했습니다.

Hydra클래스는 ZergUnit클래스를 상속했고 Zealot클래스는 ProtossUnit 클래스를 상속했고,

ProtossUnit 클래스는 StarcraftUnit 클래스를 상속했습니다.

Marine클래스는 TerranUnit 클래스를 상속했고, TerranUnit 클래스는 StarcraftUnit 클래스를

상속했습니다. 스타크래프트에 등장하는 객체를 생각해 볼 때 적적한 상속이라고

생각합니다.

기본적으로 기본 클래스 A를 상속한 클래스가 B 이면 B클래스는 B이기도 하지만

동시에 A 이기도 하기 때문입니다.

, Hydra클래스는 ZergUnit을 상속한 클래스입니다. 이것은 Hydra클래스는 Hydra이기도

하지만 동시에 ZergUnit이기도 합니다.물론 ZergUnit StarcraftUnit이기도 하구요.

결국 Hydra StarcraftUnit이기도 하니까 그림 4 - 4는 적절한 계층구조를 가진 상속의

관계입니다.

 

그림 4 - 4를 분석해보면 StarcraftUnit클래스는 이름과 체력을 의미하는 strength,name

멤버 변수를 갖고 있는데, 이들에게 private지정자를 붙여서 함부로 읽고,쓰지 못하게

했습니다. 대신 이름과 체력을 알아볼 수 있도록 pubilc 메소드, String getName()

int getStrength()를 정의했습니다. 그리고 유닛의 개요를 설명하는 String getDescription()

메소드를 추가했습니다. 정의된 메소드는 모두 public지정자를 붙여서 어디서든 사용할

수 있게 했습니다. 이것으로 보아 StarcraftUnit클래스 설계자는 모든 스타크래프트 유닛

(객체)들이 기본적으로 갖고 있어야 한다고 생각하는 멤버변수를 strength,name으로 정하고

공통적으로 수행해야하는 멤버메소드를 getName(),getStrength(),getDescription()등으로

정했습니다.

 

ZergUnit StarcraftUnit입니다. ZergUnit 클래스는 StarcraftUnit클래스를 상속할려고

합니다.

왜냐하면 모든 ZergUnit StarcraftUnit이므로 이미 만들어져 있는 StarcraftUnit클래스를

이용하는 것이 효율적이고 안정성이 좋을 수 있기 때문입니다.

그리고 ZergUnit클래스는 종족을 의미하는 clan 이라는 멤버변수를 더 필요로 합니다.

StarcraftUnit이라고 했을 때는 종족을 나누는 의미가 없지만 ZergUnit의 입장에서는

"저그"라는 자신의 종족의 이름을 추가로 갖는 것이 필요하다고 판단합니다.

그리고 String getDescription() 메소드를 다시 정의하고 있습니다. 이것은 아주 중요한

의미를 가집니다.

 

메소드 오버라이딩 (Method Overriding)

 

ZergUnit클래스가 String getDescription()을 정의한 것은 상당한 의미가 있습니다.

ZergUnit클래스는 StarcraftUnit클래스를 상속했기 때문에 StarcraftUnit에서 선언한

멤버변수,멤버메소드를 읽고,쓰고, 호출할 수 있습니다.

물론 상속되는 기본 클래스(여기서는 StarcraftUnit)에서 멤버변수,멤버메소드 앞에

씌여진 지정자의 종류에 따라 상속한 클래스에서 멤버변수, 멤버메소드를 읽고,쓰고,

호출하는데 제한이 있긴합니다만 적어도 StarcraftUnit String getDescription() 메소드

에 사용된 지정자 public이므로 ZergUnit에서 굳이 String getDescription() 메소드를

만들지 않더라도 StarcraftUnit에서 만들어 놓은 String getDescription()을 자기 것 인양

호출해서 사용할 수 있기 때문에 ZergUnit클래스가 굳이 String getDescription()

새롭게 정의한 데는 틀림없이 어떤 꿍꿍이가 있습니다.

 

ZergUnit클래스에서 String getDescription()메소드를 새롭게 정의했는데 이처럼 상속한

클래스에서 기본 클래스가 정의한 메소드를 다시 정의하는 것을 메소드 오버라이딩(Method

Overriding) 이라고 합니다.

ZergUnit클래스에는 StarcraftUnit클래스에서 이미 정의한 String getDescription()

사용할 수 있는데 굳이 메소드 오버라이딩을 한 이유는 StarcraftUnit클래스에서

만들어 놓은 String getDescription()이 마음에 들지 않기 때문입니다.

, ZergUnit 객체에게 String getDescription()을 호출하면 메소드 오버로딩을 하지 않은

ZergUnit객체는 StarcraftUnit 에서 정의한 String getDescription()을 수행합니다.

하지만 메소드 오버라이딩을 한 ZergUnit객체는 자신이 새롭게 정의한 (오버라이딩 한)

메소드를 수행합니다. 만약 ZergUnit클래스 설계자가  StarcraftUnit에서 정의한 String

getDescription()의 내용이 마음에 들지 않고, ZergUnit객체를 더욱 잘 설명하는 개요가

필요하다고 판단했다면 ZergUnit클래스에 String getDescription()메소드를 오버라이딩

하는 것을 선택할 수 있습니다.

 

 

스타크래프트를 해본 독자분들은 아시겠지만 프로토스 유닛은 다른 종족의 유닛과 달리

체력이라는 변수외에 쉴드라는 변수가 더 있습니다. 이 쉴드가 프로토스 유닛의 독특한

특성입니다. 이를 반영하기 위해서 프로토스 유닛 클래스에서는 shield라는 변수가

새롭게 추가 되었습니다.

상속의 기본적인 생각은 만들어져 있는 기본 클래스의 기능을 사용하고 모자라는 부분은

추가해서 사용하는 것이므로 아주 적절한 선택이라고 생각합니다.

그리고 ZergUnit과 마찬가지로 ProtossUnit을 좀 더 잘 설명하는 개요을 나타낼 필요가

있어서 String getDescription() 메소드를 오버라이딩 했습니다.

 

상속과 폴리모피즘

 

스타크래프트에 참여하는 클래스들의 계층구조를 체계적으로 정의해 놓게 되면 새로운

클래스들 그러니까 질럿과 히드라와 같은 클래스를 만드는 것은 아주 쉽습니다.

 

ProtossUnit을 상속하게 되면 ProtossUnit이 가지는 모든 특성 상태를 갖게 되고

모든 메소드를 호출할 수 있으니까요.

ProtossUnit은 또한 StarcraftUnit이기도 하므로 StarcraftUnit 이 할 수 있는 모든 행위를

할 수 있습니다.

, Zealot등의 클래스를 설계할 때 Zealot 클래스가 ProtossUnit클래스를 상속하는 것으로

설계를 하면 Zealot클래스가 기본적으로 가져야하는 멤버변수, 멤버메소드의 대붑분을

StarcraftUnit, ProtossUnit클래스에서 만들어 놓았기 때문에 매우 가벼운 마음으로

Zealot클래스를 설계할 수 있습니다.

이와 같은 생각을 가지고 그림 4 - 4에 참여하는 모든 클래스들을 자바로 표현해보겠습니다.

 

StarcraftUnit 클래스입니다.

 

/**

* 스타크래프트에 등장하는 유닛에 대한 설명입니다

*/

public class StarcraftUnit {

             /**

             *            유닛들의 체력

             */

             private int strength;

             /**

             *            유닛들의 이름

             */

             private String name;

             /**

             * 유닛을 만들고 체력과 이름을 지어줍니다.

             */

             public StarcraftUnit(int strength, String name) {

                           this.strength = strength;

                           this.name = name;

             }

            

             public int getStrength() {

                           return strength;

             }

             public String getName() {

                           return name;

             }

             public String getDescription() {

                           return "체력 " + strength + " , " + "이름 " + name;

             }

}

예제 4 - 13 StarcraftUnit.java

 

ZergUnit 클래스입니다.

 

/**

*            스타크래프트에 등장하는 저그유닛입니다.

*/

public class ZergUnit extends StarcraftUnit {

             /**

             * "저그" 라는 종족이름

             */

             private String clan;

             /**

             * 유닛을 만들고, 체력과 이름을 지어줍니다. 종족명을 "저그"로 합니다.

             */

             public ZergUnit(int strength, String name) {

                           super(strength,name);         // 생성자에서 가장먼저 호출

                           clan = "저그";

             }

             public String getClan() {

                           return clan;

             }

             public String getDescription() {

                           return super.getDescription() + " , " + "종족 " + clan;

             }

}

예제 4 - 14 ZergUnit.java

 

ProtossUnit 클래스입니다.

 

/**

*            스타크래프트에 등장하는 프로토스 유닛입니다.

*/

public class ProtossUnit extends StarcraftUnit {

             /**

             * "프로토스"라는 종족이름

             */

            private String clan;

            /**

            * 프로토스 고유의 특성이 쉴드값

            */

             private int shield;

            

            public ProtossUnit(int strength, String name) {

                         super(strength,name);   // 생성자에서 가장먼저 호출

                           shield = strength;                // 체력의 값과 쉴드의 값을 갖게함

          clan = "프로토스";

            }

            public ProtossUnit(int strength,int shield, String name) {

          super(strength,name);   // 생성자에서 가장먼저 호출

                           this.shield = shield;            // 체력의 값과 쉴드의 값을 갖게함

          clan = "프로토스";

             }

            public String getClan() {

          return clan;

            }

             public int getShield() {

                           return shield;

             }

            public String getDescription() {

          return super.getDescription() + " , 종족 " + clan + " , 쉴드 " + shield;

            }

}

예제 4 - 15 ProtossUnit.java

 

TerranUnit 클래스입니다.

/**

*            스타크래프트에 등장하는 테란 유닛입니다.

*/

public class TerranUnit extends StarcraftUnit {

             /**

             * 프로토스라는 종족이름

             */

             private String clan;

            

             public TerranUnit(int strength, String name) {

                           super(strength,name);         // 생성자에서 가장먼저 호출

                           clan = "테란";

             }

             public String getClan() {

                           return clan;

             }

             public String getDescription() {

                           return super.getDescription() + " , " + "종족 " + clan;

             }

}

예제 4 - 16 TerranUnit.java

 

 

예제 4 - 13 부터 예제 4 - 16 까지는 StarcraftUnit클래스와 ZergUnit클래스,ProtossUnit

클래스, TerranUnit 클래스를 설계한 것을 보여줍니다.

ZergUnit,ProtossUnit,TerranUnit은 모두 공통의 특성을 갖는 부분은 StarcraftUnit을 상속

함으로써 구현을 하였고, 그외 각각의 클래스에 필요한 것들은 멤버변수, 멤버메소드등을

추가함을 써 구현했습니다. 각 종족의 이름을 갖는 clan 멤버변수, String getClan()메소드

등이 대표적이고, ProtossUnit의 경우 shields라는 고유의 멤버변수,int getShields()라는

고유의 멤버메소드를 추가했습니다. 상속을 사용하는데 있어서 아주 대표적인 패턴이라고

생각합니다.

 

예제 4 - 17 StarcraftUnit클래스,ZergUnit클래스와 계층구조를 이루고 있는 Hydra클래스

를 설계하고 있습니다.

 

/**

*            스타크래프트에 등장하는 저그유닛 Hydra입니다.

*/

public class Hydra extends ZergUnit {

             /**

             * 체력 80이고 이름이 "히드라"인 저그유닛을 만듭니다.

             */

             public Hydra() {

                           super(80,"히드라");

             }

}

예제 4 - 17 Hydra.java

 

Hydra클래스는 ZergUnit을 상속했습니다.

Hydra클래스는 스타크래프트 유닛의에 Hydra객체를 의미하므로 체력과 이름을 각각 80

"히드라"로 정합니다. Hydra클래스에는 멤버변수와 멤버메소드가 눈에 보이지는 않지만

실제로 ZergUnit 클래스, StarcraftUnit클래스에 지정한 멤버메소드를 모두 사용할 수

있습니다. 만약 Hydra클래스를 ZergUnit 클래스를,StarcraftUnit클래스를 상속하지 않고

설계 했다면 ZergUnit클래스에서 정의된 멤버변수, 멤버메소드의 내용과 StarcraftUnit

클래스에서 정의된 멤버변수,멤버메소드 내용을 모두 써주어야 합니다.

대부분의 독자분은 아시겠지만 스트크래프트에 등장하는 유닛은 Hydra만 있는데

아닙니다. 굉장히 많습니다. 그 많은 유닛들에 대해서 멤버변수,멤버메소드의 내용을 모두

써 주어야 한다고 생각해 보세요. 끔찍하지 않습니까?

이런 경우 ZergUnit들간의 공통된 특성을 모아 기본 클래스를 구성하고, 그 클래스를  상속

한다면 공통된 많은 부분의 코드를 아껴 쓸수 있는 것이고 덕분에 체계적으로 설계된다는

장점도 얻을 수 있습니다.

 

/**

*            스타크래프트에 등장하는 질럿입니다.

*/

public class Zealot extends ProtossUnit {

             /**

             * 체력 100이고 쉴드 100이고 이름이 "질럿"인 프로토스유닛을 만듭니다.

             */

             public Zealot() {

                           super(100,100,"질럿");

             }           

}

예제 4 - 18 Zealot.java

 

예제 4 - 18  상속을 이용해서 Zealot클래스를 좀더 쉽게 체계적으로 설계할 수

있다는 것을 보여줍니다.

예제 4 - 19 TerranUnit 클래스를 상속한 Marine 클래스입니다.

 

/**

*            스타크래프트에 등장하는 마린입니다.

*/

public class Marine extends TerranUnit {

             /**

             * 체력 40이고 이름이 "마린"인 테란유닛을 만듭니다.

             */

             public Marine() {

                           super(100,"마린");

             }           

}

예제 4 - 19 Marine.java

 

예제 4 - 17 Hydra클래스, 예제 4 - 18 Zealot클래스, 예제 4 - 19 Marine

클래스의 설계에서 볼 수 있듯이 상속한 클래스의 정의가 상당히 간단하다는 것을

알 수 있습니다. 이것은 스타크래프트에 참여하는 객체들의 개념을 잘 파악해서 공통의

특성을 의미하는 클래스를 만들고 이 클래스를 상속 받아 재활용하기 때문인데

만약 Hydra클래스,Zealot클래스,Marine클래스가 계층적인 구조를 이용한 상속을 이용하지

않고 설계를 했다면 Hydra, Zealot,Marine등 스타크래프트에 참가하는 객체들의 클래스에는

반복되는 멤버변수 및 메소드가 상당량 중복될 것이고,꽤 많은 반복되는  프로그램 코드를

작성해 주어야 합니다. 종족별로 수십 개나 달하는 다른 유닛에게 일일이 중복되는 코드를

만드는 것은 바람직 하지 않습니다.

 

/**

* 마린과 질럿등 스타크래프트 유닛을 만들고 정보를 구하는 프로그램입니다.

*/

public class StarcraftPlayer {

             public static void main(String[] args) {

 

                           TerranUnit m = new Marine();

                           ProtossUnit z = new Zealot();

                          

                           System.out.println("이름 " + m.getName());

                           System.out.println("체력 " + m.getStrength());

                           System.out.println("종족 " + m.getClan());

            

                           System.out.println("이름 " + z.getName());

                           System.out.println("체력 " + z.getStrength());

                          System.out.println("종족 " + z.getClan());               

             }

}

4 - 20 StarcraftPlayer.java

 

 

예제 4 - 20은 폴리모피즘을 보여주는 좋은 예입니다.

TerranUnit m = new Marine();     을 이해한다면 폴리모피즘을 이해하는 것과 같습니다.

Marine객체는 Marine클래스형의 레퍼런스를 갖는 것이 보통입니다만 Marine 클래스형의

레퍼런스가 아닌 TerranUnit 클래스형이 Marine 객체의 레퍼런스로 쓰였습니다.

이런 폴리모피즘(다형성)이 성립할 수 있는 것은 Marine클래스가 바로 TerranUnit클래스

를 상속받았기 때문입니다. 두개의 클래스 사이에 상속의 관계에 있기 때문에 모든

Marine객체는 Marine이기도 하지만 TerranUnit이기도 하니까요.

예제 4 - 20의 예와 같이 상속등으로 이루어진 클래스들의 관계, 즉 계층구조의 상위에

있는 클래스의 형을 레퍼런스로 사용하는 경우 이를 Upcasting이라고 합니다.

Upcasting은 항상 안전하며 그 이면은 폴리포피즘에 바탕을 두고 있습니다.

 

사용자 삽입 이미지


그림 4 - 5 [run starcraft player ]

폴리모피즘에 바탕을 둔 Upcasting은 프로그램을 유연하게 만드는 데는 아주 도움이 되지

만 때로는 손해를 볼 수 도 있습니다.

 

             StarcraftUnit unit = new Marine();

             unit.getClan();                    // Oops !

 

예제 4 - 21

 

예제 4 - 21의 경우는 Marine객체를 만들고 레퍼런스를 StarcraftUnit 클래스형의 레퍼런스

로 했습니다. 이 경우 StarcraftUnit 레퍼런스로 사용할 수 있는 메소드는 StarcraftUnit

클래스에서 사용할 수 있는 메소드가 전부입니다. , Marine 객체에게 호출할 수 있는

메소드는 String getClan()등이 있지만 Marine객체와 결합한 레퍼런스가 StarcraftUnit

클래스이므로 String getClan()메소드를 호출 할 수 없습니다. String getClan() 메소드를

사용하고자 한다면 레퍼런스가 StarcraftUnit이 아니라 TerranUnit혹은 ProtossUnit

혹은 ZergUnit으로 나뉘어야 합니다.

StarcraftGame 클래스에서 처럼 지정한 메소드가 오버로딩 되어야 합니다.

예제 4 - 22와 같이 구성되었습니다.

 

public String getName(StarcraftUnit unit) {

          return unit.getName();

}

public String getClan(ProtossUnit unit) {

          return unit.getClan();

}

public String getClan(TerranUnit unit) {

          return unit.getClan();

}

public String getClan(ZergUnit unit) {

          return unit.getClan();

}

예제 4 - 22

 

스타크래프트에 등장하는 유닛들에 대한 클래스를 설계할 때 상속을 이용함으로써,

많은 클래스를 좀더 쉽게 만들수 있고, 체계적으로 정리할 수 있는 장점을 얻을 수

있었습니다.

Posted by
,