instanceof에 대해

학습동기

체스 미션을 구현하던 중 pawn의 특이한 성질 때문에 체스판의 기물이 pawn인지 확인해야 하는 로직이 필요했다.

그때 고민이 생겼는데

  1. 추상 클래스인 Piece에 추상 메서드인 isPawn()을 만들어 pawn인지 확인한다.
  2. instanceof를 사용해 Piece구현체들이 pawn인지 확인한다.

처음에 두 방법이 생각났고 얼마전에 알게된 instanceof를 쓰는 방법이 클래스의 메서드도 줄일 수 있을 뿐더러 더 고급진 방법이라는 생각이 들었다.
그래서 2번 방법으로 구현해서 리뷰요청을 했다.

하지만 결과는.....

위와 같은 피드백을 받고 말았다...🥲👍🏻

추상화가 제대로 된건지 의심?? 추상화를 했으니까 instanceof를 쓸 수 있는거 아닌가요???
어쨋든 그래서 instanceof를 왜 지양해야 하는지에 대해 조사해보게 되었다.

학습 내용

instanceof란

: 부모를 상속해서 만들어진 자식 객체가 여러 타입인 경우에 특정 클래스가 맞는지 확인하기 위해 사용될 수 있다.

    if (!(piece instanceof Nothing)) {
            cells.put(position, piece);
    }

다음과 같이 Piece를 상속하는 Piece 구현체들이 Nothing 인지 확인해서 분기처리 해줄 수 있다.

instanceof를 사용하지 않는다면

public abstract class Piece {
	public abstract boolean isNothing();
	
}
public final class Pawn extends Piece {
	@Override
	public boolean isNothing() {
    	return false;
    {
}
public final class Nothing extends Piece {
	@Override
	public boolean isNothing() {
    	return true;
    {
}

이런식으로 다형성을 이용해 부모의 abstract 메서드를 오버라이딩해서 메서드를 정의해 줘야 한다.

그래서 instanceof를 왜 쓰면 안돼?

다형성을 이용하면 class에 메서드가 하나 더 추가되는 꼴인데 그럼 왜 instanceof를 쓰지 말라는 건지 알아보자.

1. 캡슐화

캡슐화란 외부에서 객체의 상태나 행위를 보지 못하도록 내부에서 객체의 상태와 속성을 숨기는 것을 뜻한다. 객체지향에서는 각 객체가 자신의 책임과 역할을 독립적으로 수행하는 것이 중요하기 때문에 캡슐화가 깨지는 건 큰 문제가 될 수 있다.

instanceof를 사용하면 외부의 객체에서 해당 객체가 어떤 타입인지를 알 수 있기 때문에 캡슐화가 깨지게 된다. instanceof를 지양해야 하는 첫번째 이유이다.

2. 책임

pawn과 관련된 로직을 체스판안에 있는 기물들을 관리하는 Board에서 책임지는게 맞을까? 아님 Piece에서 추상화하여 책임지는게 맞을까?

  1. Board에서 담당
public final class Board {
	...
    
	public void checkPawn(final Piece piece) {
    	if(piece instanceof Pawn) {
        	...
        {
    {
    
	...
}
  1. Piece에서 추상화하여 다형성으로 담당
public abstract class Piece {
	public abstract void checkPawn(final Position source, final Position target, final Direction direction);
	
}
public final class Rook extends Piece {
	@Override
	public void checkPawn(final Position source, final Position target, final Direction direction) {
    	return;
    {
}
public final class Pawn extends Piece {
	@Override
	public void checkPawn(final Position source, final Position target, final Direction direction) {
   		if (source.isTwoRankDifference(target)) {
            throw new IllegalArgumentException("폰은 초기 위치일 때만 두 칸이동 가능합니다.");
        }
        
        ...
    {
}

1번 방법은 Piece에게 너 pawn이야? 그럼 이거 확인 해줘. 라고 물어보는 대신 그저 Piece들을 관리하는 객체에서 적절하지 않은 책임을 담당하고 있다. 각 타입에게 책임을 부여하면 되는 일을 하나의 메서드에서 과도하게 담당하고 있다. 이는 Single Responsibility Principle(단일 책임원칙)에 위배된다고 볼 수 있다.

마무리

instanceof가 눈앞에 놓인 상황에서는 확실히 편하고 유용해 보일 수 있다. 하지만 객체지향의 관점에서 봤을 때, 유지보수와 기능의 확장면에서 instanceof의 사용은 지양되어야 함을 알게 되었다.

객체지향적인 설계를 위해 instanceof의 사용을 가급적 하지 말자!!

좋은 웹페이지 즐겨찾기