스타그래프트 프로젝트 생성.
게임에 대한 기초이해
마린은 유닛중에 하나이다. (마린이 〈 유닛을 상속한다.)
종족은 항상 언제나 3개이다.
종족은 언제나 3개이기 때문에 열거형으로 만들면 좋을 것 같다.
[접근 제한자] enum [이름] { ... }
- 어떠한 대상이 프로그램 실행 중에 더 이상 추가되거나 삭제되지 않으며 그 종류나 개수가 상당히 고정되어있다고 판단되면 열거형으로 만드는 것이 좋다. (스타의 종족, 로그인 결과 등등)
- 열거형의 원소는 UPper Snake Case(전체 대문자, 단어간 구분은 언더스코어로) 방식으로 명명하여 콤마로 구분하여 열거한다.
종족이라는 특성은 유닛만 가지는 것이 아닌 건물도 가진다.
만약 테란을 setName을 하는데 개발자들이 모두 똑같이 쓸 기대를 하면 안된다.
Terran / TERRAN / 테란 ... 이런식으로 다 다르게 작성할수도 있다.
그러한 일들을 막기위해 열거형을 많이 사용한다.
열거형을 사용하면 개발자들끼리 협엽을 할 때 오타가 날 확률이 줄어든다.
- enum Race 열거형 생성
- Unit class 유닛들의 이름과 종족을 만든다.
- 어떤 유닛의 이름과 종족이 정해지면 바뀌지 않기 때문에 final로 한다.
- 이름과 종족을 다시 정해줄 수 없기 때문에 setter은 깔끔하게 지워주면된다.
final 붙이니 왜 빨간줄이 그일까? 멤버상수이기 때문에 조건을 충족하지 않기 때문이다.
private final String name; private final Race race;
반드시 아래 방법중 하나로 값 초기화가 일어나야한다.
1. 선언과 동시에 값 초기화private final String name = Race.TERRAN; 으로 하는 방법인데 모든 유닛의 이름이 TERRAN이 되기 때문에 지금은 사용하지 않는다.
- 생성자 호출시 멤버 상수 값 지정.
생성자 호출을 하는 방법을 사용할 것이다.
public Unit(String name, Race race) { this.name = name; // 전달 받은 name this.race = race; // 전달 받은 race }
- 전달 인자로 값을 전달받아(
()
) 위의 멤버 상수에 전달을 해준다.
상수는 setter를 통해서 값 지정을 못함으로 직접지정하거나 생성자를 통해서 지정한다.
=> 그랬더니 메인이 터졌다.
new Unit();
은 생성자 호출이다. 호출할 수 있는 생성자는 Unit 밖에 없는데 Unit은 무조건 전달인자(name, race)가 있어야하는데 아무것도 전달해주는게 없어서 오류가 발생했다.
전달을 해줘야만 객체화를 할 수 있다.
- 이렇게 전달인자로 전달을 해주면 객체화가 되며
결과적으로 멤버 상수에name : 마린
/race : TERRAN
이 들어가게 된다.
- 이렇식으로 들어가게 되는 것이다. (name만 참고)
-> 메인에서 전달받은 이름과 종족을 출력해보자.
- marine객체에 get을 해서 값을 불러온다.
- 그런데 이렇게 코드를 짜면 다른 개발자가 marine2를 만들어서 영어로 이름을 짓는거나 변경할 수 있는 가능성이 존재하기 때문에 별로 좋지 않은 코드이다.
유닛이 공격하는 방법(기능)이 다 다를 것이다.
유닛이 가진 기능을 구현하자.
- 유닛이 가진 기능을 가진 Unit Class에 attack메서드를 만든다.
- 메인에서 출력해보니 유닛마다 사운드가 다 달라야하는데 이렇게 한 메서드로 사용하면 다 똑같은 사운드를 낼 것이다.
그래서 사용하는게 추상 메서드 / 추상 클래스이다.
[접근 제한자] abstract [반환 타입] ...
- 메서드의 존재 자체만 명시하고 구현부의 구현을 본 클래스의 상속 받는 자식 클래스에게 맡기기위해 사용. (구현부가 없다.)
- 단, 추상 메서드를 한 개 이상 가지는 클래스는 반드시 추상 클래스(Abstract Class)여야 한다.
- 추상 메서드는 구현부가 없어야하기 때문에 빨간줄이 생긴다.
- 추상 메서드를 가진 클래스는 추상 클래스여야 하기 때문에 빨간줄이 생긴다. 다 수정해주자.
[접근 제한자] abstranct class [이름] {...}
- 반드시 추상 메서드를 가져야하는 것은 아니다. 주로 추상 메서드를 가질 때 클래스를 추상화한다.
- 추상 클래스는 일반적으로 개발자가 직접 객체화하지 못(안)한다. (가능은 하다.)
- 대신 추상클래스라고 해서 추상메서드를 꼭 가져야하는 것은 아니다.
- 결론적으로 추상 메서드가 뭐냐면 Unit 클래스는 attack이라는 추상 메서드가 호출되었을 때 유닛이라는 클래스는 모른다. 상속을 받는 자식클래스가 구현을 직접해야한다는 의미이다.
메인으로 가보자.
'Unit' is abstract; cannot be instantiated
: 유닛은 추상적이고 객체화 될 수 없다.- 유닛은 추상클래스이기 때문에 객체화가 될 수 없다는 의미이다.
추상클래스는 일반적으로 직접 객체화하지 못한다.(방법은 있다.)
추상화를 했기 때문에 attack 메서드를 입맛에 맞게끔 사용 할 수 있고 유닛을 상속받았기 때문에 마린을 마음대로 질럿이라는 이름으로 바꾸는 것을 막을 수 있다.
- (unists패키지안에 terran패키지에) 클래스를 만들고 유닛을 상속 받았다. (유닛에 있는 이름 / 종족 / 어택을 구현하기 위해서)
그런데Class 'Marine' must either be declared abstract or implement abstract method 'attack()' in 'Unit'
: 추상적이거나 attack 이라는 추상 메서드를 구현해야한다. 이라는 오류를 해결하기 위해서는 두가지의 방법이 있다.
- Unit을 상속받은 Marine클래스는 attack이라는게 이미 잠재되어있다.
attack은 추상적인데도 물구하고 Marine클래스가 추상적이지 않기에 오류가 뜨는 든다. Marine 클래스도 추상적이게 만들면 해결된다.- 상속받는 부모 Unit클래스가 가진 메서드들을 다 구현해야한다.
즉, attack메서드를 구현하면 된다.
attack메서드를 구현하자. Override로.
- 원래 Marine 클래스의 기능이 아니고 부모로부터 물려받은 메서드를 Marine 클래스에서 재정의해줘야한다.
: 클래스, 메서드, 변수 등의 상태나 속성을 나타내기 위해 사용한다.
: 해당 메서드가 부모나 인터페이스가 가진 메서드를 재정의(Override) 한다는 의미
this.getName()
Unit클래스가 가진 getName호출하는데 왜 this(나)일까 내가 가진게 아니라 부모가 가진 것인데 super가 아닐까? 라는 생각이 든다.
- 부모가 가진 기능을 나(Marine)도 구현을 하고 있기 때문에 this도 되고 super도 된다.
- name멤버변수가 있긴 하지만
name
은 private이기 때문에 그냥 name으로 접근할 수 없다.
Marin 클래스에 기본생성자만 존재하고 따로 생성자를 만들어준적없다.
- 기본생성자이다.
- super누르면 이쪽으로 오게 된다. Unit의 생성자에 매개변수 2개를 받고 있다.
- 여기서 super은 Unit을 의미하고 (Unit을 상속받으니)
super();
라고 되있으니 Unit(부모) 생성자를 호출하는 것이다.- Unit클래스(부모클래스)의 생성자를 호출할 때 매개변수 2개를 받고 있으니 적당한 값을 전달해줘야한다.
- name, race 이렇게 값은 전달은 할 수 있지만 아까와 같은 마린타입에 질럿이라는 이름이 들어갈수 있는 문제가 발생하게 된다.
그래서 값을 받는 것이 아닌 단 한번만 여기에다 지정을 해놓으면 된다.
이렇게 하면 Marine 클래스를 객체화할때마다 이름은 무조건 "마린"이 될 것이고 종족은 TERRAN이 될 것이다.
- 객체화할때마다 값을 전달해주는 것이 아닌 이렇게만 작성하면 된다. 마린이 필요할 때 마린타입을 가져다 쓰면 되는 것이다.
- units패키지안 protoss패키지 Zealot 클래스 생성, Unit 상속받고 attack 메서드 재정의.
- 마린과 다른 이름과 종족을 가지게 되었고 attack의 사운드마저 다르게 된다.
마린과 질럿의 부모는 유닛임으로 유닛 타입의 배열에 담을 수 있다.
- (Main클래스)
- 추상클래스인 Unit이란 타입으로 attack을 호출하는게 가능한가?
정상적으로 작동을 한다. 그 이유는 뭘까?
Unit이라는 클래스를 상속받아 만든 것은 마린과 질럿이 있다.
마린과 질럿이라는 타입을 만들 때 attack을 할 때 어떻게 공격을 하라고 지정을 해주었다. (상속을 받아 오버라이드를 통해)
마린과 질럿의 객체에 대해서 접근할 수 있는 범위를 Unit으로 제한한 것이다.
Marine marine = new Unit;
마린타입의 marine을 유닛으로 받게 되면 더 이상 new을 통해서는
마린이 가지고 있는 확장된 기능을 쓰지 못한다.
축소가 되긴하지만 attack을 했을 때 어떤 것을 해야된다 라는 것에 대해서는 날아간게 아니다.
Marine객체를 Unit타입으로 제한한다고 해서 Unit의 attack을 호출했을 때 추상적이라서 오류가 발생하는 것이 아닌 원래 할일을 하게 된다.
println을 통해 이해를 빠삭하게 해보자.
- 이렇게 적었다면 println에 마린과 질럿이 있어야
( );
안에 들어갈 수 있는게 아닐까? 매개변수의 타입과 맞지않는걸 넣으면 오류가 발생하는데 오류가 발생하지 않는다?
-> println에 들어가보자.
- 모든 참조타입의 최상위 부모는 Object이다.
public void println(object x ) {...}
( );
에 마린이란걸 넣으면 Object라는 변수에 Marine 객체가 들어가는데
즉, 부모 타입는 자식 객체을 받을 수 있기 때문에 가능한 것이다.
println에 "Hello"를 넣으면 String을 받는 것이고 5를 넣으면 int를 받는 것이다.
- 출력을 해보았더니 이렇게 이상한 값이 출력이 된다.
[TERRAN] 마린 [PROTOSS] 질럿
이렇게 출력이 되었으면 좋겠다.
String s = String.valueOf(x);
을 들여다보자.
toString()
을 호출하고 있다.
내가 println(여기에)
집어넣은 객체가 Object 로써 받아들여졌으며
이상하게 출력 내용을 보니 Object의 toString 메서드를 호출해서 출력된 값이 된 것이다. ( null이면 null이고 아니면 toString() )
- toString 으로 가보니 출력된 결과와 같은 형태를 가진 것을 확인 할 수 있다. toString은 Object클래스에 속해있다.
결론적으로 이러한 형태가 되는 것이다.
println(); ↑ object 넣음 => toString 출력
Unit unit = marine; unit.attack();
- 이 attack이 가지고 있는 Unit이 아닌 Marine의 내용이다.
- Object타입으로 마린과 질럿을 만들어봤다.
상속받고 있는 부모가 가진 메서드를 재정의하는 것 오버라이드라고 했다.
Object가 가진 toString을 재정의한다. (Unit은 상속받는게 없으니 부모가 Object이고 그래서 toString 오버라이드가 가능하다.)
toString을 Object의 toString을 두지않고 Unit의 toString으로 바꿀 수 있는 것이다.
정리하자면 Unit에게 너는 toString이 호출되면 반듯이 이 내용을 반환해라 라고 덮어씌워주는 것이다.
덮어씌우지 않으면 Unit에 toString 했을 때 Object가 가진 toString이 반환되는 것이고 재정의(덮어씌우는 것)를 하게 되면 Unit이 가진 toString이 반환이 되는 것이다.
object타입으로 변환한다고 해서 이상하게 나오지 않는다.
범위를 제한했을 뿐이고 원래 해야하는 일 (Marine의 toString을 호출하는 것)을 하는 것 뿐이라서 출력이 이렇게 되는 것이다.
만약 제한한 타입의 일을 했다면 Unit은 추상적이라서 호출할 수 없어서 오류가 터졌을 것이다.
object > toString :
~@~
형식 출력
Unit extends Object
Unit을 통해 toString 재정의