Dev Study/Java

[Java] 다형성과 추상 클래스: if문의 늪에서 탈출하기

parkhh98 2026. 1. 21. 16:04

반복되는 instanceof 검사를 없애고, 확장 가능한 코드를 만드는 법

Phase 1: 리모컨이 너무 많아 (Bad Code)

최신 스마트 홈 시스템을 개발한다고 가정해보자. TV, 에어컨, 공기청정기 등 수많은 가전제품(Device)을 한 번에 제어해야 한다. 하지만 다형성을 모르는 개발자는 제품마다 제각각인 메서드 이름을 사용한다.

// 규격이 없는 혼란스러운 상태
class TV {
    void turnOnTV() { System.out.println("TV 켜짐"); }
}

class AirCon {
    void startCooling() { System.out.println("에어컨 시원해짐"); }
}

이렇게 되면, 안방에 있는 모든 기계를 켜려고 할 때 지옥의 조건문 파티가 열린다.

// Bad Code: 제품이 늘어날 때마다 코드를 수정해야 한다 (OCP 위반)
Object[] devices = { new TV(), new AirCon() };

for (Object device : devices) {
    if (device instanceof TV) {
        // 일일이 검사하고(instanceof), 강제로 변신시켜서(Casting) 실행
        ((TV)device).turnOnTV(); 
    } else if (device instanceof AirCon) {
        ((AirCon)device).startCooling();
    }
}

문제점: 만약 '로봇청소기'를 새로 샀다면? 메인 코드를 또 열어서 `else if`를 추가해야 한다. 유지보수가 불가능한 구조다.

Phase 2: 만능 리모컨 만들기 (Solution)

해결책은 간단하다. 제조사들에게 "전원 켜는 기능은 무조건 `powerOn()`으로 통일해!"라고 강제하는 것이다. 이때 사용하는 것이 바로 추상 클래스(Abstract Class)다.

// "모든 기계는 powerOn 기능을 가져야 한다"는 법(Rule)을 제정
abstract class SmartDevice {
    // 내용은 자식이 채워라 (미완성 설계도)
    abstract void powerOn();
}

class TV extends SmartDevice {
    @Override
    void powerOn() { System.out.println("TV 켜짐"); } // 부모의 법을 따름
}

class AirCon extends SmartDevice {
    @Override
    void powerOn() { System.out.println("에어컨 시원해짐"); }
}

이제 메인 코드는 더 이상 제품이 TV인지 에어컨인지 궁금해할 필요가 없다. 그냥 "기계니까 켜져라!" 한 마디면 된다.

// Good Code: 조건문, 캐스팅 모두 사라짐
SmartDevice[] devices = { new TV(), new AirCon() };

for (SmartDevice device : devices) {
    // 다형성(Polymorphism): 실제 객체가 누구냐에 따라 알아서 다른 동작이 실행됨
    device.powerOn(); 
}

효과: 나중에 '로봇청소기'가 추가되어도, **메인 코드는 단 한 줄도 수정할 필요가 없다.**

Phase 3: 믿음의 법칙 (Deep Dive)

다형성을 사용할 때 꼭 알아야 할 중요한 규칙이 있다. "변수는 껍데기를 믿고, 메서드는 알맹이를 믿는다."

SmartDevice p = new TV(); // 껍데기는 SmartDevice, 알맹이는 TV

1. 변수(Field)는 껍데기를 따라간다 (Static Binding)

`p.brand`를 호출하면, 자바는 참조 변수의 타입(`SmartDevice`)에 정의된 변수를 가져온다. 아무리 실제 객체가 `TV`라도, 껍데기가 `SmartDevice`라면 부모의 값을 가져온다. (이래서 필드 오버라이딩은 피하는 게 좋다.)

2. 메서드(Method)는 알맹이를 따라간다 (Dynamic Binding)

`p.powerOn()`을 호출하면, 자바는 실제 생성된 객체(`new TV`)가 오버라이딩한 최신 메서드를 실행한다. 이것이 우리가 조건문 없이도 `TV`의 기능을 실행할 수 있는 이유다.

Conclusion

다형성은 단순히 코드를 줄이는 기술이 아니다. "변하는 것(구체적인 제품)""변하지 않는 것(켜는 행위)"을 분리하여, 미래의 변경에 유연하게 대처할 수 있게 해주는 객체지향의 핵심 철학이다.

 

📚 함께 보면 좋은 'Antigravity & VS Code & Java' 로드맵

다형성과 추상화에 대해 알아보았습니다, Java에서 소위 말하는 제네릭이란 뭘까요?

Next Step 제네릭에 대해 알아보기
[Java] 제네릭의 역설: 유연할수록 아무것도 담을 수가 없다