오늘은 객체 지향의 마지막인 다형성에 대해 공부를 해볼 것이다.
다향성을 기준으로
추상클래스, 추상메소드를 배우고 인터페이스까지 알아볼 것이다.
목차
1. 다향성이란??
1. 업캐스팅(Up casting)
2. 다운캐스팅(Down casting)
3. 배열에 사용해보기
2. 추상(abstract)
1. 추상 클래스
2. 추상 메소드
3. 인터페이스(interface)
1. 다향성이란??
다형성(polymorphism)이란 부모-자식 상속 관계에 있는 클래스에서
상위 클래스가 동일한 메시지로 하위 클래스들을 서로 다르게 동작시키는 객체 지향 원리이다.
사실 이렇게 정의만 보고 이해하기가 쉽지 않다.
다향성은 객체나 인터페이스 등 철학적인 느낌을 자아내는 용어이기 때문에
실제로 해보면서 이해하는 게 좋다.
위의 사진처럼 생긴 클래스가 있다.
또 퍼스트 클래스를 상속한 세컨드 클래스가 있다.
그럼 원래 배운 대로
객체를 생성(캐스팅)하면 위의 사진처럼 할 수 있다.
1. 업캐스팅(Up casting)
이때 만약
상속이 없다면
f = new Second();는 당연히 에러가 날 것이다.
다른 클래스의 객체를 참조했기 때문이다.
그러나 상속관계의 클래스에서
부모참조변수로 자식 객체를 참조한 경우엔
에러가 나지 않고 정상 작동이 된다.
이를 업캐스팅이라고 한다.
이렇게 업캐스팅을 통해서
부모참조변수로 자식객체를 참조하여 제어가 가능하다.!!
그러나 자식객체의 고유기능은 쓸 수가 없다.(참조는 됨)
이를 실생활의 예로 들어보자면
채널을 바꾸고, 음량만 조절 가능한 티비 1세대가 있다고 가정하자.
그리고 1세대의 기능에다가 넷플릭스 기능도 들어간 2세대 티비가 출시했다.
그럼 1세대의 리모컨으로도 채널, 음량 조절이 가능하지만
넷플릭스 기능은 컨트롤할 수 없는 것과 같은 맥락이다.
2. 다운캐스팅(Down casting)
그런데 분명 자식객체의 고유기능을 사용하고 싶은 경우도 존재할 것이다.
이 경우엔
새로운 자식참조변수를 만들고
First 참조변수 f에다가
참조값(주소)을 대입해 주면 된다.
이때 주소를 대입해 주기 위해서
s2 = f; 라고 적으면 에러가 난다.
자식이 부모를 참조한다고 오해하기 때문이다.
그래서 형변환이 필요하다.
s2 = (Second)f;
이렇게 작성해 주면
자식객체의 고유기능을 사용할 수 있다.
그리고 주의할 사항이 있다.
업캐스팅이 되어 있지 않은 상태에서 다운캐스팅만 시도하면 에러가 난다.!!
이런 업캐스팅과 다운캐스팅은 다양하게
활용이 가능하다.
가장 쉬운 예로는
부모참조변수 1개로 자식객체 모두를 제어할 수 있게 된다.
위 사진과 같이
하나의 참조변수로
자식, 손주객체인 Second, Test, Third를 제어할 수 있다.
3. 배열에 사용해 보기
만약 강아지, 고양이, 돼지가 동물캐릭터로 나오는 앱이 있다고 해보자.
그리고 say()라는 메소드를 치면 각각 울음소리를 낸다.
그럼 각 동물캐릭터를 클래스로 만들고
이렇게 울음소리를 출력할 수 있다.
그러나 만일 동물캐릭터가 너무 많다면?
적어야 할 양도 많아지지만
제어할 때도 같은 캐릭터 종류별로만 묶을 수 있어서
제어할 때 짜증이 난다.
하지만 우리가 배운 업캐스팅을 쓴다면
하나의 배열로 묶을 수 있다.
이를 위해서 동물캐릭터의 부모클래스가 필요하다.
위의 사진처럼
부모클래스 용으로
캐릭터를 묶을 수 있는 부모 클래스가 필요하다.
그리고 위의 사진처럼
강아지, 고양이, 돼지에 해당하는
클래스가 있다.
그럼 이제
동물캐릭터의 부모클래스인
Animal 객체를 사용해
업캐스팅을 할 수 있다.
위의 사진을 보면
Animal이라는 참조변수를 사용했지만
뒤엔 다 자식인
강아지, 고양이, 돼지 객체를 생성했다.
그리고 각각의 기능을 사용했다.
여기서 분명 어? 뭐지라고 하는 사람이 있을 것이다.
분명 자식객체의 고유기능은 사용할 수 없다고 했기 때문이다.
그러나 여기선 오버라이드라는 기술을 이용했기에 가능했다.
(그래서 부모 클래스에도 say라는 메소드가 있고
자식 클래스들에도 say라는 메소드가 있다.)
다시 메인함수로 돌아와서
배열객체를 우선 생성하고
배열에 각각 업캐스팅을 해주었다.
그럼 우리가 왜 배열로 만들었는가?
제어하기 편하기 위해서이다.
바로 반복문을 통해서
say메소드를 편하게 쓸 수 있다.
2. 추상(abstract)
추상을 설명하기 전에
한 가지 예로 이해를 해보자.
A, B, C, D로 이뤄진 게임 제작 팀이 있다고 생각해 보자.
이 팀은 스타크래프트 게임을 만들려고 한다.
A는 팀장
B는 마린 유닛 설계를
C는 탱크 유닛 설계를
D는 레이스 유닛 설계를 맡았다.
이때 유닛들이 가진 기본 능력(이동, 공격)을
같은 이름으로 설정하지 않으면
제어하기가 어려워진다.
B는 move라고 설정한 것을
C는 run으로
D는 fly로 설정할 수 있으니까 말이다.
반면 만일
이동을 move로 통일하고
공격을 attack으로 통일한다면
배열이나 ArrayList로 제어할 때 편하게 제어가 가능할 것이다.
우리는 이런 통일 작업을 할때
추상 클래스와 추상 메소드를 사용한다.
1. 추상 클래스
우선 추상 클래스는
바로 객체 생성이 불가능한 클래스이다.
그럼 왜 사용하지?
라는 의문이 들 텐데
바로 상속을 하기 위해 존재하는 것이다.
위의 사진처럼 생성자도 변수도 일반메소드도 추상메소드도 만들 수 있지만
일반적으로 추상 메소드만 사용한다.
2. 추상 메소드
그리고 추상 메소드는
이름만 있고 기능은 없는 메소드이다.
또 추상 메소드를 보유하고 있는 클래스는
반드시 추상 클래스로 선언이 돼야 한다.
이 추상 메소드는 어떻게 쓰이냐고?
바로 다른 클래스를 만들어서 사용한다.
위의 사진을 보면 추상 클래스인 Test를 상속받은
Nice 클래스가 있다.
Nice 클래스는 추상 클래스를 상속받았기 때문에
꼭 Test 클래스에 포함된
추상 메소드를 오버라이드 해야 한다.
(안 하면 에러)
이 추상이라는 개념은
인터페이스와 연결이 된다.
3. 인터페이스(interface)
인터페이스는 추상 메소드만 가지는 class이다.
즉, 완전히 같지는 않지만
interface == abstract class라고 생각하면 된다.
interface를 만드는 방법은 class를 만들어 고칠 수도 있지만
아래처럼 만들면 빠르다.
바로 class 만들기 밑에 있는
interface를 누르면 된다.
그럼 위의 사진과 같은 것이 나타난다.
다시 한번 말하자면
인터페이스는 오로지 추상메소드만 가지는 클래스로
기능의 이름만 정하는 설계도라고 생각하면 쉽다.
위의 사진처럼
일반 메소드를 정의하면 에러가난다. ( { } 가 포함되면 일반메소드 취급)
인터페이스에서 추상 메소드를 쓰는 방법은 두 가지인데
void bbb();라고 간단하게 작성해도 된다.
(사실 앞에 public abstract가 생략된 것이다.)
혹은 생략된 부분을 다 써주면 된다.
인터페이스 클래스를 구현(상속)할 땐
우리가 지금까지 썼던 extends를 쓰지 않는다.
대신 implements를 사용한다.
이를 구현한 순간
인터페이스의 추상 메소드를 구현하지 않으면
에러가 나니 반드시 오버라이드 해줘야 한다.
인터페이스에는 특이한 점이 있다.
상속할 때는 다중 상속이 되지 않았다.
그러나 인터페이스는 다중 구현이 가능하다.
위의 사진처럼 AAA, BBB라는 두 개의 인터페이스를
받아 구현이 가능하다.
그리고 상속받은 클래스에
인터페이스 클래스를 받아 구현도 가능하다.
조금 어렵지만 앞으로 배울 내용을 +해서
예제를 하나 해보자면...

앞서 추상을 설명할 때
게임 만들기 예를 들었다.
그 설정을 가져와서 예제를 진행해 보겠다.
우선

공통적으로 유닛들이 기능을 가지고 있으니
Unit이라는 인터페이스를 만들어
move와 attack이라는 추상메소드를 만들자.



위의 사진들은 3가지 유닛들은
인터페이스로부터 구현한 것이다.

앞서 우리가 이렇게
다형성, 추상, 인터페이스 등의 개념을 익힌 것은
편하게 관리하기 위해서이다.
유닛 하나하나를 객체로 참조변수를 이용해 만들기엔
수가 많아지면 귀찮고 관리도 어려워지기 때문이다.
그럼 우리는 배열로 쉽게 묶을 수 있다.
그러나 실제 게임에서는 유닛들이 만들어지고
없어지는 것이 빈번하게 일어난다.
전에도 한번 배웠지만
대량의 객체를 다룰 땐 유동적 배열을 더 선호한다.
이 유동적 배열을 ArrayList라고 한다. (후에 다시 배울 예정)

유동적 배열은 위와 같은 형태로
사용해서 객체를 생성하고 출력해 볼 수 있다.

그리고 좀 더 편하게
공통된 기능들을 써보려면
반복문을 위와 같이 사용해서
쓸 수 있다.
그런데 만약
우리가 특정 유닛만 레벨업이 가능하도록
설정하고 싶다면 어떻게 해야 할까?
앞서 인터페이스는 다중 구현이 가능하다고 했다.
즉 레벨업 인터페이스를 만들어서
원하는 유닛에다 구현을 하면 된다.


위와 같은 마린은 레벨업이 가능하도록
설정하고 싶다면 레벨업 인터페이스 역시 구현시키면 된다.
그리고 레벨업 메소드를 오버라이드 해줘야 한다.
(같은 방식으로 탱크도 해주자)

그리고 레벨업도 가능한 유닛들만 설정해서
레벨업 기능도 출력해보려 한다.
우리가 이제 아는 방법으로는
if문을 사용해서 instanceof가 Marine인지 Tank인지 확인해줘야 한다.
(instanceof 연산자는 객체가 어떤 클래스인지,
어떤 클래스를 상속받았는지 확인할때 사용하는 연산자이다.)
하지만 이 방법은
수가 적을 때만 가능하다.
유닛이 100개라면 하나하나 다
if문을 쓰고 있을 수는 없다.
그래서 더 나은 방법은
레벨업 인터페이스를 구현했는지 확인하는 방법이다.
이를 통해 하나하나 경우를 적지 않아도
자동으로 레벨업이 이뤄진다.

'# 개발 > Java' 카테고리의 다른 글
Java #10 -Generic, Collection API (국비22일차) (0) | 2023.02.01 |
---|---|
Java #9 -오브젝트 클래스, 예외(Exception) (국비21일차) (0) | 2023.01.31 |
Java #7 - 상속, 오버라이드, Final (국비19일차) (0) | 2023.01.27 |
Java #6 - 생성자, Static, 이너클래스, 지역클래스 (국비18일차) (0) | 2023.01.26 |
Java #5 - class 위치, 패키지, 접근제한자 (국비17일차) (0) | 2023.01.25 |