1. 클래스의 다형성

1) 클래스의 다형성

→ 부모타입의 인스턴스로 자식 타입을 대입받거나 호출하는 것이 가능한 것

※ 자료형이 다른 변수는 대입이 불가능

부모 자료형과 자식 자료형도 같은 자료형이 아니기 때문에 원칙적으로는 대입이 불가능하다.

그러나 상속을 통한 인스턴스 생성 시 부모 영역과 자식 영역이 구분되어 저장되기 때문에 일부 영역은 통제 가능한 상태가 된다.

ex) 부모클래스 사람(이름,나이,자기소개()) & 자식클래스 학생(전공, 공부하기())

      부모타입인 사람 타입의 변수에 학생 인스턴스를 대입하는 것이 가능

☆ 부모쪽에 존재하는 함수를 오버라이딩한 경우만 자식의 함수로 호출이 가능하므로 인터페이스를 이용해 다형성을 구현한다.

 

2) 다형성(Polymorphism)

- 하나의 객체가 여러가지 유형으로 사용되는 것

- 상속을 전제조건으로 함

- 자식클래스가 부모클래스의 타입을 가질 수 있도록 허용 → 부모 타입에 모든 자식객체가 대입될 수 있음

 

① 다형성이 적용되지 않은 코드 예시

package noinheri;

public class Rabbit {
	// 토끼 몬스터 hp, atk, def
	private int hp;
	private int atk;
	private int def;

	// 생성자에서 void 파라미터로 각각 3, 0, 0으로 초기화
	public Rabbit() {
		this.hp = 3;
		this.atk = 0;
		this.def = 0;
	}
	
	// setter / getter 자동생성
	// alt + shift + s 후 Generate getters and setters 선택

	public int getHp() {
		return hp;
	}

	public void setHp(int hp) {
		this.hp = hp;
	}

	public int getAtk() {
		return atk;
	}

	public void setAtk(int atk) {
		this.atk = atk;
	}

	public int getDef() {
		return def;
	}

	public void setDef(int def) {
		this.def = def;
	}
}
package noinheri;

public class Warrior {
	// 정보은닉 적용
	private String id;
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 생성자 id 입력받고 나머지 필드 초기화
	public Warrior(String id) {
		this.id = id;
		this.hp = 20;
		this.atk = 3;
		this.def = 1;
		this.exp = 0;
	}
	
	// 캐릭터 상태 조회
	public void showStatus() {
		System.out.println("아이디 : " + this.id);
		System.out.println("체력 : " + this.hp);
		System.out.println("공격력 : " + this.atk);
		System.out.println("방어력 : " + this.def);
		System.out.println("경험치 : " + this.exp);
		System.out.println("------------------");
	}
	
	// 단독 사냥을 하도록 메서드 생성
	public void huntRabbit(Rabbit rabbit) {
		if(rabbit.getHp() <= 0) {
			System.out.println("이미 죽은 토끼입니다.");
			return; // 죽은 토끼에 대해서는 추가 로직이 필요없음
		}
		// 1. 내가 공격한 토끼의 체력 -3
		rabbit.setHp(rabbit.getHp() - this.atk);
		// 2. 방금 공격으로 죽었다면 경험치 5 증가
		if(rabbit.getHp() <= 0) {
			System.out.println("토끼를 죽였습니다.");
			this.exp += 5;
		} else {
			System.out.println("토끼를 공격했습니다.");
		}
	}
}
package noinheri;

public class MainClass1 {

	public static void main(String[] args) {
		// 전사 하나 생성
		Warrior w1 = new Warrior("전사1");
		// 생성 직후 정보 조회
		w1.showStatus();
		// 토끼 생성
		Rabbit r1 = new Rabbit();
		// 토끼와 교전
		w1.huntRabbit(r1);
		// 죽은 토끼 한 번 더 공격
		w1.huntRabbit(r1);
		// 사냥 후 정보 조회
		w1.showStatus();
	}
}

위 상황에서 huntRabbit() 함수는 Rabbit 자료형만을 받아올 수 있게 되어 있으므로 Rat 자료형 요구 시 오류가 발생한다.

 

package noinheri;

public class Rat {
	// 쥐 몬스터 hp, atk, def
	private int hp;
	private int atk;
	private int def;

	// 생성자에서 void 파라미터로 각각 3, 0, 0으로 초기화
	public Rat() {
		this.hp = 5;
		this.atk = 1;
		this.def = 0;
	}
	
	// setter / getter 자동생성
	// alt + shift + s 후 Generate getters and setters 선택

	public int getHp() {
		return hp;
	}

	public void setHp(int hp) {
		this.hp = hp;
	}

	public int getAtk() {
		return atk;
	}

	public void setAtk(int atk) {
		this.atk = atk;
	}

	public int getDef() {
		return def;
	}

	public void setDef(int def) {
		this.def = def;
	}
}
package noinheri;

public class Warrior {
	// 정보은닉 적용
	private String id;
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 생성자 id 입력받고 나머지 필드 초기화
	public Warrior(String id) {
		this.id = id;
		this.hp = 20;
		this.atk = 3;
		this.def = 1;
		this.exp = 0;
	}
	
	// 캐릭터 상태 조회
	public void showStatus() {
		System.out.println("아이디 : " + this.id);
		System.out.println("체력 : " + this.hp);
		System.out.println("공격력 : " + this.atk);
		System.out.println("방어력 : " + this.def);
		System.out.println("경험치 : " + this.exp);
		System.out.println("------------------");
	}
	
	// 단독 사냥을 하도록 메서드 생성
	public void huntRabbit(Rabbit rabbit) {
		if(rabbit.getHp() <= 0) {
			System.out.println("이미 죽은 토끼입니다.");
			return; // 죽은 토끼에 대해서는 추가 로직이 필요없음
		}
		// 1. 내가 공격한 토끼의 체력 -3
		rabbit.setHp(rabbit.getHp() - this.atk);
		// 2. 방금 공격으로 죽었다면 경험치 5 증가
		if(rabbit.getHp() <= 0) {
			System.out.println("토끼를 죽였습니다.");
			this.exp += 5;
		} else {
			System.out.println("토끼를 공격했습니다.");
		}
	}
	
	// Rat은 공격을 받고 죽지 않으면 1회 반격
	// 경험치는 80
	public void huntRat(Rat rat) {
		if(rat.getHp() <= 0) {
			System.out.println("이미 죽은 쥐입니다.");
			return;
		}
		rat.setHp(rat.getHp() - this.atk);
		if(rat.getHp() <= 0) {
			System.out.println("쥐를 죽였습니다.");
			this.exp += 80;
		}
		else {
			System.out.println("쥐를 공격했습니다.");
			System.out.println("쥐가 반격합니다.");
			this.hp = this.hp - rat.getAtk();
		}
	}
}
package noinheri;

public class MainClass2 {

	public static void main(String[] args) {
		// 전사 생성
		Warrior w1 = new Warrior("전사2");
		// 상태 조회
		w1.showStatus();
		// 쥐 생성
		Rat rat1 = new Rat();
		// 쥐와 교전 (3번)
		for(int i = 0; i < 3; i++) {
			w1.huntRat(rat1);
		}
		// 상태조회
		w1.showStatus();
	}
}

 

 

② 다형성이 적용된 코드 예시

package inheri;

public class Monster {
	// 모든 몬스터 클래스의 부모타입으로 설계된 클래스
	private String name; // 다형성 특성상 몬스터 종류를 식별하기 위한 변수
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 부모쪽 생성자로 초기화할 때 어떤 몬스터가 생성될지 사전에 알 수 없으므로
	// 고정값이 아닌 자식쪽에서 입력받은 자료를 대입하도록 생성자 설계
	public Monster(String name, int hp, int atk, int def, int exp) {
		this.name = name;
		this.hp = hp;
		this.atk = atk;
		this.def = def;
		this.exp = exp;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getHp() {
		return hp;
	}

	public void setHp(int hp) {
		this.hp = hp;
	}

	public int getAtk() {
		return atk;
	}

	public void setAtk(int atk) {
		this.atk = atk;
	}

	public int getDef() {
		return def;
	}

	public void setDef(int def) {
		this.def = def;
	}

	public int getExp() {
		return exp;
	}

	public void setExp(int exp) {
		this.exp = exp;
	}
}
package inheri;

public class Warrior {
	// 정보은닉 적용
	private String id;
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 생성자 id 입력받고 나머지 필드 초기화
	public Warrior(String id) {
		this.id = id;
		this.hp = 20;
		this.atk = 3;
		this.def = 1;
		this.exp = 0;
	}
	
	// 캐릭터 상태 조회
	public void showStatus() {
		System.out.println("아이디 : " + this.id);
		System.out.println("체력 : " + this.hp);
		System.out.println("공격력 : " + this.atk);
		System.out.println("방어력 : " + this.def);
		System.out.println("경험치 : " + this.exp);
		System.out.println("------------------");
	}
	
	// 파라미터로 모든 몬스터의 부모타입인 Monster 인스턴스를 요구하면
	// 다형성 원리에 의해 상속받은 모든 몬스터를 다 대입할 수 있음
	public void hunt(Monster monster) {
		// 죽은 몬스터는 교전 불가 및 메서드 즉시 종료
		if(monster.getHp() <= 0) {
			System.out.println(monster.getName() + "은 이미 죽어서 교전할 수 없습니다.");
			return;
		}
		// 다음 공격으로 몬스터가 죽는 경우
		if(monster.getHp() - (this.atk - monster.getDef()) <= 0){
			// 경험치부여
			monster.setHp(0);
			System.out.println(monster.getName() + "(이)가 죽었습니다.");
			this.exp += monster.getExp();
		} else {
			// 다음 공격으로 몬스터가 죽지 않아서 반격을 받는 경우
			// 몬스터 체력을 (내 공격력 - 몬스터 방어력)만큼 차감
			monster.setHp(this.atk - monster.getDef());
			// 내 체력을 (몬스터 공격력 - 내 방어력)만큼 차감
			this.hp -= monster.getAtk() - this.def;
			// **가 반격했습니다.
			System.out.println(monster.getName() + "(이)가 반격했습니다.");
		}
	}
}
// 해당 클래스의 인스턴스는 Monster 타입 변수에도 저장할 수 있음.
public class Rabbit extends Monster {
	public Rabbit() {
		// 부모인 Monster에 이름, 체력, 공격력, 방어력, 경험치 전달
		super("토끼", 3, 0, 0, 5);
	}
}
package inheri;

public class Rat extends Monster {
	public Rat() {
		// 부모인 Monster에 이름, 체력, 공격력, 방어력, 경험치 전달
		super("쥐", 5, 0, 0, 5);
	}
}
package inheri;

public class MainClass1 {

	public static void main(String[] args) {
		// 전사 생성
		Warrior w1 = new Warrior("전사1");
		// 상태 조회
		w1.showStatus();
		// 토끼 생성
		Rabbit r1 = new Rabbit();
		// 전사와 교전 2회
		w1.hunt(r1);
		w1.hunt(r1);
		
		// 쥐 생성
		Rat r2 = new Rat();
		w1.hunt(r2);
		w1.hunt(r2);
		w1.hunt(r2);
		w1.showStatus();
	}
}

위와 같이 상속을 통해 Monster 범주에 들어가는 객체를 모두 호출 가능하다.

원래라면 토끼나 쥐 객체를 생성한 후 Warrior 파일에서 hunt() 함수를 생성해줘야 했는데,

다형성을 이용하여 Monster 타입을 처리하는 hunt로 재설계함으로써 코드의 확장성을 높일 수 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

매개변수의 다형성  (0) 2024.01.11
배열의 다형성  (0) 2024.01.10
정보은닉, 캡슐화  (0) 2024.01.08
접근제한자  (0) 2024.01.08
상속  (0) 2024.01.05

+ Recent posts