Instanceof 연산자 사용은 객체지향적이지 못하다?
이 글을 쓰게된 계기는 체스 미션을 진행하면서 많은 크루분들과 대화하다가
instanceof를 사용하면 객체지향적이지 못하다는 말을듣고
왜 객체지향적이지 못하지? 라는 의문이 들었기 때문입니다.
먼저 instanceof를 사용한 코드를 보겠습니다.
interface Employee {
int salary();
}
class Manager implements Employee {
private static final int managerSalary = 40000;
public int salary() {
return managerSalary;
}
}
class Programmer implements Employee {
private static final int programmerSalary = 50000;
private static final int programmerBonus = 10000;
public int salary() {
return programmerSalary;
}
public int bonus() {
return programmerBonus;
}
}
class Pay {
public int calculatePay(Employee employee) {
int money = employee.salary();
if (employee instanceof Programmer)
money += ((Programmer) employee).bonus();
return money;
}
public static void main(String[] args) {
Pay pay = new Pay();
Employee manager = new Manager();
Employee programmer = new Programmer();
System.out.println("Manager's pay is : " + pay.calculatePay(manager));
System.out.println("Programmer's pay is : " + pay.calculatePay(programmer));
}
}
Pay 클래스의 calculatePay 메서드를 보면 employee라는 변수가 Programmer인스턴스일 때 money에 Bonus를 더하고있습니다.
이런 흐름이 된 이유는 Manager에는 bonus메서드가 없고 Programmer에는 bonus메서드가 존재하기 때문입니다.
그래서 프로그래머가 직접 employee의 타입이 Programmer인지 검사하는 instanceof 연산자를 사용하고 있습니다.
다형성을 이용하면 instanceof 연산자를 없애고 자바가 런타임시에 타입을 체크하게 할 수 있습니다.
interface Employee {
int salary();
int bonus();
}
class Manager implements Employee {
private static final int managerSalary = 40000;
private static final int managerBonus = 0;
public int salary() {
return managerSalary;
}
public int bonus() {
return managerBonus;
}
}
class Programmer implements Employee {
private static final int programmerSalary = 50000;
private static final int programmerBonus = 10000;
public int salary() {
return programmerSalary;
}
public int bonus() {
return programmerBonus;
}
}
class Pay {
public int calculatePay(Employee employee) {
return employee.salary() + employee.bonus();
}
public static void main(String[] args) {
Pay pay = new Pay();
Employee manager = new Manager();
Employee programmer = new Programmer();
System.out.println("Manager's pay is : " + pay.calculatePay(manager));
System.out.println("Programmer's pay is : " + pay.calculatePay(programmer));
}
}
처음 코드와 다르게 Employee 인터페이스에 bonus() 메서드를 명시했습니다.
이제 따로 프로그래머가 타입체크를 하지않아도 bonus() 메서드를 사용할 수 있게됐습니다.
만약 Manager에도 bonus를 주게 된다면 간단하게 Manager의 필드만 변경해줘도 가능해집니다.
이번 체스미션에서(이전 블랙잭 미션에서도) 현재 선택된 체스말이 폰인지 확인하거나 킹인지 확인하는 기능이 필요했습니다.
public abstract class Piece {
public abstract void move();
public boolean isKing() {
return this instanceof King;
}
}
class Pawn extends Piece{
@Override
public void move() {
System.out.println("폰을 움직인다.");
}
}
class King extends Piece{
@Override
public void move() {
System.out.println("킹을 움직인다.");
}
}
class Queen extends Piece{
@Override
public void move() {
System.out.println("퀸을 움직인다.");
}
}
class Test {
public static void catchPiece(Piece piece) {
if (piece.isKing()) {
System.out.println("킹이 잡혀서 게임이 끝났습니다!");
}
}
public static void main(String[] args) {
Piece piece = new King();
piece.move();
}
}
위의 코드에서 Piece 추상클래스의 isKing 안에서 instanceof 연산자를 사용해서 이 객체가 King인스턴스인지 판단하고 있습니다.
현재 상태에서는 King이라는 클래스만 삭제되도 프로그램에 에러가 발생합니다.
아래와 같이 코드를 바꿀 수 있습니다.
public abstract class Piece {
public abstract void move();
public boolean isKing() {
return false;
}
}
class Pawn extends Piece{
@Override
public void move() {
System.out.println("폰을 움직인다.");
}
}
class King extends Piece{
@Override
public void move() {
System.out.println("킹을 움직인다.");
}
@Override
public boolean isKing() {
return true;
}
}
class Queen extends Piece{
@Override
public void move() {
System.out.println("퀸을 움직인다.");
}
}
class Test {
public static void catchPiece(Piece piece) {
if (piece.isKing()) {
System.out.println("킹이 잡혀서 게임이 끝났습니다!");
}
}
public static void main(String[] args) {
Piece piece = new King();
piece.move();
Test.catchPiece(piece);
}
}
Piece 추상클래스에서는 isKing 메서드의 반환값을 false를 반환하고 상속받은 King 클래스에서 isKing 메서드가 true를 반환하도록 합니다.