Java

Instanceof 연산자 사용은 객체지향적이지 못하다?

에드박 2021. 3. 22. 23:01

이 글을 쓰게된 계기는 체스 미션을 진행하면서 많은 크루분들과 대화하다가

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를 반환하도록 합니다.