데이터 엔지니어링

패스트캠퍼스 환급챌린지 27일차: 데이터엔지니어링 초격차 강의 후기

Big Byte 2025. 4. 27. 23:00

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

 

설계도와 계약서? 자바 추상 클래스와 인터페이스로 유연함 UP! 🏗️🤝✨

안녕하세요 여러분! 또 만나서 정말 기뻐요! 🤩

와~ 어제 우리는 객체 지향의 강력한 무기인 상속캡슐화를 배웠죠! 👨‍👩‍👧‍👦🛡️ 부모 클래스의 능력을 물려받아 코드를 재사용하고(extends), 접근 제어자로 소중한 데이터를 안전하게 지키는(private, public 등) 방법을 익혔어요. 덕분에 우리 코드는 더욱 체계적이고 안전해졌습니다! 마치 잘 만들어진 게임 캐릭터들이 각자의 역할과 능력치를 갖게 된 것 같죠? 👍

 

하지만 객체 지향의 설계 능력은 여기서 끝나지 않아요! 오늘은 여기서 한 발 더 나아가, 더욱 유연하고 확장성 있는 구조를 만드는 방법을 배울 거예요. 바로 추상 클래스(Abstract Class)인터페이스(Interface)입니다! 이건 마치 건물을 짓기 전에 만드는 '미완성 설계도'나, 서로 다른 부품들이 지켜야 할 '표준 규격(계약서)'과 같아요. 😉

 

아직 구체적인 모습은 없지만, "이런 기능은 반드시 있어야 해!"라고 약속하는 거죠. 객체 지향 설계의 꽃, 추상화의 세계로 함께 떠나볼까요? 준비되셨나요? 🔥

 

1. 추상 클래스 (Abstract Class): 미완성 설계도로 뼈대 잡기! 🏛️✍️

상속을 배웠으니, 이제 이런 상상을 해봅시다. Shape(도형)이라는 클래스를 만들고, 이걸 상속받아 Circle(원), Rectangle(사각형) 등을 만들려고 해요. 모든 도형은 공통적으로 color(색상)을 가질 수 있고, draw()(그리기)라는 행동을 할 수 있겠죠? 그런데 Shape 자체를 객체로 만들어서 draw()를 호출하면 뭘 그려야 할까요? 🤔 '그냥 도형'은 그릴 수가 없죠! 원이나 사각형 같은 구체적인 도형이어야 그릴 수 있습니다.

 

이럴 때 바로 추상 클래스가 등장합니다! ✨ 추상 클래스는 이름 그대로 '추상적인' 개념을 표현하는 클래스예요. 객체를 직접 생성할 수는 없지만(인스턴스화 불가!), 자식 클래스가 반드시 갖춰야 할 **필수적인 속성이나 행동(메소드)의 틀(뼈대)**을 정의할 수 있습니다.

 

마치 자동차의 '기본 섀시'와 같아요. 섀시 자체만으로는 달릴 수 없지만, 이 섀시를 기반으로 세단, SUV, 트럭 등 다양한 자동차(자식 클래스)를 만들 수 있죠. 각 자동차는 섀시의 기본 구조를 공유하면서, 자신만의 엔진이나 디자인을 갖춥니다.

 

왜 추상 클래스를 사용할까요?

  • 구현 강제: 자식 클래스가 반드시 구현해야 할 메소드(추상 메소드)를 명시하여 통일된 구조를 강제할 수 있어요. 📜 "이 기능은 꼭 만들어!"
  • 코드 재사용성: 공통적인 속성이나 이미 구현된 메소드는 추상 클래스에 만들어두고 자식들이 물려받아 사용할 수 있어요. ♻️ (상속의 장점!)
  • 논리적 통일성: 관련된 클래스들을 추상적인 상위 개념으로 묶어 관리하기 용이해요. (예: 모든 도형은 Shape이다) 🌳

어떻게 사용하나요? abstract 키워드!

클래스와 메소드 앞에 abstract 키워드를 붙여서 "나는 추상적이야!"라고 선언해요. 추상 메소드는 {} 몸통 없이 세미콜론(;)으로 끝납니다.

      // 추상 클래스 Shape - 직접 객체 생성 불가!
public abstract class Shape {
    String color;

    // 생성자 (자식 클래스에서 super()로 호출됨)
    public Shape(String color) {
        System.out.println("Shape 생성자 호출됨");
        this.color = color;
    }

    // 일반 메소드 (구현이 이미 되어 있음)
    public String getColor() {
        return color;
    }

    // ✨ 추상 메소드 ✨ - 몸통({})이 없고 세미콜론(;)으로 끝남!
    // 자식 클래스에서 반드시 구현(오버라이딩)해야 함!
    public abstract void draw();

    // 추상 클래스도 일반 메소드를 가질 수 있어요!
    public void commonAction() {
        System.out.println("모든 도형이 할 수 있는 공통 작업");
    }
}

// Shape를 상속받는 Circle 클래스
public class Circle extends Shape { // ✨ extends 사용!
    int radius; // 원만의 속성: 반지름

    public Circle(String color, int radius) {
        super(color); // ✨ 부모(Shape) 생성자 호출!
        System.out.println("Circle 생성자 호출됨");
        this.radius = radius;
    }

    // ✨ 부모의 추상 메소드 'draw()'를 반드시 구현해야 함! ✨
    @Override
    public void draw() {
        System.out.println(super.color + " 색상의 원을 그립니다 (반지름: " + this.radius + ")");
    }
}

// Shape를 상속받는 Rectangle 클래스
public class Rectangle extends Shape {
    int width, height; // 사각형만의 속성

    public Rectangle(String color, int width, int height) {
        super(color);
        System.out.println("Rectangle 생성자 호출됨");
        this.width = width;
        this.height = height;
    }

    // ✨ 여기도 'draw()' 구현 필수! ✨
    @Override
    public void draw() {
        System.out.println(super.color + " 색상의 사각형을 그립니다 (너비: " + this.width + ", 높이: " + this.height + ")");
    }
}

// 사용 예시
public class DrawingBoard {
    public static void main(String[] args) {
        // Shape shape = new Shape("빨강"); // 🚨 컴파일 에러! 추상 클래스는 직접 객체 생성 불가!

        Circle redCircle = new Circle("빨강", 5);
        Rectangle blueRectangle = new Rectangle("파랑", 10, 7);

        System.out.println("\n--- 원 그리기 ---");
        System.out.println("색상: " + redCircle.getColor()); // 부모에게 물려받은 메소드
        redCircle.commonAction();             // 부모에게 물려받은 메소드
        redCircle.draw();                     // 오버라이드된 메소드 (Circle에 구현됨)

        System.out.println("\n--- 사각형 그리기 ---");
        System.out.println("색상: " + blueRectangle.getColor());
        blueRectangle.commonAction();
        blueRectangle.draw();                 // 오버라이드된 메소드 (Rectangle에 구현됨)
    }
}
    

Shape 클래스는 abstract라서 직접 객체를 만들 수 없지만, CircleRectangleShapeextends하고 abstract 메소드인 draw()를 각자 구현함으로써 객체를 만들 수 있게 되었어요.

 

추상 클래스는 이렇게 "뼈대는 내가 잡아줄 테니, 살은 네가 붙여!"라고 말하는 것과 같답니다. 😎

 

2. 인터페이스 (Interface): 기능 명세서(계약서) 만들기! 📜🤝🔌

 

이번엔 다른 상황을 생각해 볼까요? 게임 캐릭터 중에는 날 수 있는 캐릭터(Bird, Dragon)도 있고, 헤엄칠 수 있는 캐릭터(Fish, Mermaid)도 있어요. 또 어떤 아이템은 충전이 가능(Smartphone, ElectricCar)할 수도 있죠. 이런 '날 수 있는 능력', '헤엄칠 수 있는 능력', '충전 가능한 능력' 등은 특정 클래스 계층 구조에 묶이기보다는 여러 종류의 객체가 가질 수 있는 부가적인 기능(Capability)에 가까워요.

 

이때 바로 인터페이스가 활약합니다! 🦸‍♀️ 인터페이스는 클래스가 어떤 기능을 제공해야 하는지에 대한 명세(specification) 또는 계약(contract)이에요. 오직 추상 메소드(Java 8부터는 default 메소드, static 메소드도 가능)와 상수(값을 바꿀 수 없는 변수)만을 가질 수 있습니다.

 

인터페이스는 마치 'USB 포트 규격'이나 '리모컨 버튼'과 같아요. USB 포트 규격은 "이런 모양과 전기 신호를 사용해야 해!"라고 약속하고, 어떤 회사든 이 규격만 맞추면 장치를 연결할 수 있죠. 리모컨의 '전원' 버튼은 어떤 TV든 켜고 끄는 기능을 수행해야 한다는 약속입니다. 실제 TV 내부 회로(구현)는 제조사마다 다르겠지만요!

 

왜 인터페이스를 사용할까요?

  • 구현 강제 (계약): 인터페이스를 구현(implement)하는 클래스는 인터페이스에 정의된 모든 추상 메소드를 반드시 구현해야 해요. "이 기능들을 제공하겠다고 약속해!" 🤝
  • 다중 구현 가능: 클래스는 단 하나의 클래스만 상속(extends)할 수 있지만, 인터페이스는 여러 개를 구현(implements)할 수 있어요! 🎉 (예: Drone 클래스는 Flyable 인터페이스와 Chargeable 인터페이스를 동시에 구현 가능) -> 자바에서 다중 상속의 효과를 내는 방법!
  • 느슨한 결합 (Loose Coupling): 클래스들이 구체적인 클래스가 아닌 인터페이스에 의존하게 만들면, 나중에 구현 클래스가 바뀌더라도 코드 수정을 최소화할 수 있어요. 유연성 Up! 🔗➡️🕊️
  • 표준화: 여러 클래스가 동일한 인터페이스를 구현함으로써 일관된 사용 방식을 제공할 수 있어요. (예: 모든 '날 수 있는 것'은 fly() 메소드를 가짐)

어떻게 사용하나요? interfaceimplements 키워드!

인터페이스는 interface 키워드로 정의하고, 클래스는 implements 키워드로 "나는 이 인터페이스의 기능을 구현하겠습니다!"라고 선언합니다.

      // 날 수 있는 기능을 정의하는 인터페이스
public interface Flyable {
    // 인터페이스의 모든 필드는 자동으로 public static final (상수)
    int MAX_ALTITUDE = 10000; // 최대 고도 (상수)

    // 인터페이스의 모든 추상 메소드는 자동으로 public abstract
    void fly(); // ✨ 추상 메소드 (몸통 없음)
}

// 헤엄칠 수 있는 기능을 정의하는 인터페이스
public interface Swimmable {
    void swim(); // ✨ 추상 메소드
}

// 새 클래스 - Flyable 인터페이스를 구현
public class Bird implements Flyable { // ✨ implements Flyable ✨
    String type;

    public Bird(String type) { this.type = type; }

    // ✨ Flyable 인터페이스의 fly() 메소드를 반드시 구현! ✨
    @Override
    public void fly() {
        System.out.println(this.type + "가 하늘을 납니다. 훨훨~ (최대 고도: " + Flyable.MAX_ALTITUDE + ")");
    }
}

// 비행기 클래스 - Flyable 인터페이스를 구현
public class Airplane implements Flyable {
    String model;

    public Airplane(String model) { this.model = model; }

    // ✨ Airplane도 fly() 구현 필수! ✨
    @Override
    public void fly() {
        System.out.println(this.model + " 비행기가 엔진 소리를 내며 하늘을 납니다. 슈우웅~");
    }
}

// 오리 클래스 - Flyable과 Swimmable 두 인터페이스를 모두 구현!
public class Duck implements Flyable, Swimmable { // ✨ 콤마(,)로 여러 인터페이스 구현 가능! ✨
    String name;

    public Duck(String name) { this.name = name; }

    // ✨ fly() 구현 필수! ✨
    @Override
    public void fly() {
        System.out.println(this.name + " 오리가 짧게 날갯짓하며 날아갑니다. 푸드덕!");
    }

    // ✨ swim() 구현 필수! ✨
    @Override
    public void swim() {
        System.out.println(this.name + " 오리가 물 위를 헤엄칩니다. 둥둥~");
    }
}

// 사용 예시
public class NatureSimulation {
    public static void main(String[] args) {
        Bird eagle = new Bird("독수리");
        Airplane boeing747 = new Airplane("보잉747");
        Duck donald = new Duck("도날드");

        System.out.println("\n--- 날 수 있는 것들 ---");
        // Flyable 타입의 변수에는 Flyable을 구현한 모든 객체를 담을 수 있음! (다형성!)
        Flyable flyer1 = eagle;
        Flyable flyer2 = boeing747;
        Flyable flyer3 = donald;

        flyer1.fly();
        flyer2.fly();
        flyer3.fly();
        // flyer1.swim(); // 🚨 에러! Flyable 타입 변수로는 swim() 호출 불가!

        System.out.println("\n--- 헤엄칠 수 있는 오리 ---");
        // Swimmable 타입 변수에도 Duck 객체를 담을 수 있음
        Swimmable swimmer = donald;
        swimmer.swim();
        // swimmer.fly(); // 🚨 에러! Swimmable 타입 변수로는 fly() 호출 불가!

        System.out.println("\n--- 오리의 모든 능력 ---");
        // Duck 타입 변수로는 모든 메소드 호출 가능
        donald.fly();
        donald.swim();
    }
}

Bird, Airplane, Duck은 각자 다른 클래스지만, Flyable이라는 계약을 따르기로 했기 때문에 모두 fly() 메소드를 가지고 있어요. 덕분에 Flyable 타입 변수 하나로 이들을 모두 다룰 수 있죠 (이것이 바로 다형성(Polymorphism)의 매력! - 곧 배울 거예요!).

 

심지어 DuckFlyableSwimmable 두 개의 인터페이스를 동시에 구현해서 여러 능력을 가질 수 있게 되었습니다. 정말 유연하죠? 😉

3. 추상 클래스 vs 인터페이스: 언제 뭘 쓸까? 🤔

둘 다 추상화를 위한 도구지만, 약간의 차이가 있어요.

구분 추상 클래스 (Abstract Class) 인터페이스 (Interface)
목적 관련 클래스들의 공통 뼈대/기반 정의 (is-a 관계) 클래스가 가져야 할 기능/행동 명세 정의 (can-do 관계)
상속/구현 extends (단일 상속만 가능) implements (여러 개 구현 가능)
멤버 추상 메소드, 일반 메소드, 멤버 변수(상태) 가능 추상 메소드, (Java 8+) default/static 메소드, 상수만 가능 (상태 X)
생성자 가질 수 있음 (직접 생성은 X, 자식에게 호출됨) 가질 수 없음
키워드 abstract class, abstract method interface, implements
사용 시점 (언제?) 관련성이 높은 클래스들 간에 공통적인 상태(변수)행동(메소드)을 공유하고 싶을 때 서로 관련 없는 클래스라도 특정 기능(능력)을 공통적으로 가져야 할 때, 다중 구현이 필요할 때, API 규격을 정의할 때

간단히 말해, "얘네들은 근본적으로 같은 종류(is-a)인데 세부 구현만 달라" 싶으면 추상 클래스를 고려하고, "얘네들이 이런 능력(can-do)을 갖춰야 해"라는 계약이 필요하면 인터페이스를 우선 생각해보세요! 물론 실제 설계에서는 둘을 함께 사용하며 더욱 강력한 구조를 만들기도 합니다. 💪

 

4. 정리하며 📝

휴~ 오늘은 객체 지향 설계의 깊이를 더하는 추상 클래스와 인터페이스에 대해 알아봤습니다! 🚀🏗️ 처음에는 조금 헷갈릴 수 있지만, 이 개념들을 잘 이해하고 활용하면 정말 유연하고 확장성 높은 코드를 작성할 수 있게 될 거예요. 마치 레고 블록처럼, 필요한 기능을 인터페이스로 정의하고, 기본 구조를 추상 클래스로 잡아서 다양한 조합을 만들어내는 거죠!

오늘의 핵심 내용을 다시 한번 마음에 새겨볼까요?

  • 추상 클래스 (Abstract Class): abstract 키워드, 미완성 설계도, 객체 생성 불가, 상속(extends)을 통해 뼈대 제공 및 일부 구현 강제! 🏛️✍️
  • 인터페이스 (Interface): interface 키워드, 기능 명세서(계약서), 객체 생성 불가, 구현(implements)을 통해 특정 능력(메소드) 구현 강제, 다중 구현 가능! 📜🤝🔌
  • 활용: is-a 관계공통 상태/행동에는 추상 클래스, can-do 관계기능 계약/다중 구현에는 인터페이스를 고려하자! 🤔💡

추상 클래스와 인터페이스는 앞으로 배울 다형성과 같은 고급 객체 지향 개념의 핵심적인 바탕이 됩니다. 오늘 배운 내용들을 바탕으로 "어떤 동물을 추상 클래스로 만들고, 날 수 있는/헤엄칠 수 있는 능력을 인터페이스로 정의해볼까?" 와 같이 직접 고민하고 코드를 작성해보는 연습을 꼭 해보시길 바랍니다! 직접 해보는 것만큼 좋은 학습은 없으니까요! 💪💻

 

이야~ 정신없이 달려오니 벌써 일요일 밤이네요! 🌃 이번 한 주도 객체 지향의 새로운 세계를 탐험하며 추상 클래스, 인터페이스까지 정복하시느라 정말 정말 수고 많으셨습니다! 👏 주말 동안 푹 쉬면서 에너지 충전 잘 하셨나요? 😊

 

이제 내일부터 다시 새로운 한 주가 시작되네요! 이번 주에 배운 이 탄탄한 개념들을 발판 삼아, 다음 주에는 더욱 흥미진진한 내용으로 함께 나아가요! 💪 우리 모두 다음 주도 힘차게 시작해봅시다! 모두들 화이팅! 

 

https://abit.ly/lisbva