데이터 엔지니어링

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

Big Byte 2025. 4. 26. 23:37

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

부모 클래스 능력 이어받기! 자바 상속과 캡슐화 마스터하기 👨‍👩‍👧‍👦🛡️


안녕하세요 여러분! 다시 만나 정말 반갑습니다! 🥳

어제 우리는 객체 지향 프로그래밍의 세계에 첫발을 내디뎠어요! 클래스라는 설계도로 객체라는 실체를 만들어내는 마법을 배웠죠. 🏛️➡️✨ 이제 코드를 단순한 명령어 나열이 아닌, 현실 세계의 개념들을 모델링하고 조립하는 방식으로 바라볼 수 있게 되셨을 거예요! 👍

하지만 객체 지향의 강력함은 여기서 멈추지 않습니다! 오늘은 어제 배운 기초 위에 더욱 견고하고 유연한 구조를 쌓아 올리는 방법을 배울 거예요. 바로 상속(Inheritance)과 접근 제어자(Access Modifier)입니다! 게임에서 기본 캐릭터가 전직하며 새로운 능력을 얻고, 중요한 아이템을 안전하게 보관하는 것과 비슷하다고 할까요? 😉 코드를 재사용하고, 데이터를 안전하게 보호하는 객체 지향의 핵심 기술 속으로 함께 떠나봅시다! 준비되셨나요? 🔥

 

 

1. 상속 (Inheritance): 능력치와 스킬 물려받기! 📜➡️✨

게임에 다양한 캐릭터를 만든다고 상상해 봅시다. `Warrior`(전사), `Mage`(마법사), `Archer`(궁수) 등등... 이 캐릭터들은 모두 공통적으로 `name`(이름), `hp`(체력), `level`(레벨) 같은 속성을 가지고 있고, `attack()`(공격하기), `move()`(이동하기) 같은 기본적인 행동을 할 수 있겠죠? 이 모든 캐릭터 클래스를 만들 때마다 이름, 체력, 공격하기 코드를 반복해서 작성해야 할까요? 🤔 생각만 해도 비효율적이죠!

이때 바로 상속이 구원투수로 등판합니다! ⚾ 상속은 이미 존재하는 클래스(부모 클래스)의 속성과 행동을 그대로 물려받아 새로운 클래스(자식 클래스)를 정의하는 기술이에요.

마치 게임의 기본 직업(부모)과 상위 직업(자식) 관계와 같아요. 전사는 기본 캐릭터의 능력(체력, 기본 공격)을 물려받으면서, 자신만의 새로운 스킬(강타)이나 속성(분노 게이지)을 추가할 수 있죠! 자식 클래스는 부모의 것을 그대로 사용하거나, 필요에 따라 기능을 더 강력하게 만들거나(메소드 오버라이딩 - 나중에!), 완전히 새로운 기능을 추가할 수 있습니다.

왜 상속을 사용할까요?

 코드 재사용성 Up!: 공통 코드는 부모 클래스에 한 번만! 자식들은 물려받기만 하면 되니 중복 코드가 사라져요. ♻️
*   유지보수 편리성 Up!: 공통 기능을 수정할 때 부모 클래스만 고치면 모든 자식에게 적용! 🔧
*   논리적인 계층 구조: 클래스 간의 'is-a' 관계(예: 전사는 캐릭터다 - Warrior is a Character)가 명확해져 코드를 이해하기 쉬워져요. 🌳

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

자바에서는 `extends` 키워드로 "나는 이 클래스를 상속받겠습니다!"라고 선언합니다.

// 부모 클래스 (Superclass) - 모든 게임 캐릭터의 기본
public class Character {
    String name;
    int hp;
    int level;

    // 생성자
    public Character(String name, int hp, int level) {
        System.out.println("기본 캐릭터 생성 중...");
        this.name = name;
        this.hp = hp;
        this.level = level;
    }

    public void attack() {
        System.out.println(this.name + "이(가) 기본 공격을 합니다! 퍽!");
    }

    public void move() {
        System.out.println(this.name + "이(가) 이동합니다.");
    }
}

// 자식 클래스 (Subclass) - Character를 상속받는 Warrior
public class Warrior extends Character { // ✨ extends Character ✨
    int strength; // 전사만의 속성: 힘

    // Warrior 생성자
    public Warrior(String name, int hp, int level, int strength) {
        super(name, hp, level); // ✨ 부모 클래스(Character)의 생성자 호출! 필수! ✨
        System.out.println("강력한 전사 생성 완료!");
        this.strength = strength;
        // name, hp, level은 Character로부터 상속받았어요!
    }

    // Warrior만의 행동 추가
    public void bash() {
        System.out.println(this.name + "이(가) " + this.strength + "의 힘으로 강타! 💥");
    }

    // 부모의 attack 메소드를 재정의(Override) 할 수도 있어요!
    @Override
    public void attack() {
        System.out.println(this.name + "이(가) 검으로 강력하게 공격합니다! 챙강!");
    }
}

// 자식 클래스 (Subclass) - Character를 상속받는 Mage
public class Mage extends Character {
    int mana; // 마법사만의 속성: 마나

    public Mage(String name, int hp, int level, int mana) {
        super(name, hp, level); // 부모 생성자 호출
        System.out.println("지혜로운 마법사 생성 완료!");
        this.mana = mana;
    }

    public void castSpell() {
        System.out.println(this.name + "이(가) 마나 " + this.mana + "를 사용하여 화염 마법 시전! 🔥");
    }

    // 마법사는 기본 공격이 다를 수 있겠죠?
    @Override
    public void attack() {
        System.out.println(this.name + "이(가) 지팡이로 약하게 공격합니다. 뿅!");
    }
}

// 사용 예시
public class Game {
    public static void main(String[] args) {
        Warrior thor = new Warrior("토르", 200, 10, 50);
        Mage gandalf = new Mage("간달프", 100, 12, 150);

        System.out.println("\n--- 전사 정보 ---");
        System.out.println("이름: " + thor.name); // 상속받은 속성
        System.out.println("체력: " + thor.hp);    // 상속받은 속성
        thor.move();      // 상속받은 메소드
        thor.attack();    // 오버라이드된 메소드
        thor.bash();      // Warrior 고유 메소드

        System.out.println("\n--- 마법사 정보 ---");
        System.out.println("이름: " + gandalf.name); // 상속받은 속성
        System.out.println("레벨: " + gandalf.level); // 상속받은 속성
        gandalf.move();     // 상속받은 메소드
        gandalf.attack();   // 오버라이드된 메소드
        gandalf.castSpell(); // Mage 고유 메소드
    }
}

WarriorMageCharacterextends 해서 name, hp, level, move() 등을 공짜로 얻었어요! 그러면서 각자 필요한 strengthmana, bash()castSpell() 같은 고유한 특징을 추가했죠. 심지어 attack() 메소드는 각 직업에 맞게 다르게 구현(오버라이딩)했습니다. 정말 멋지지 않나요? 😎

(Tip: super()는 자식 클래스 생성자 첫 줄에서 부모 클래스의 생성자를 호출하는 코드예요. 부모 클래스의 멤버를 초기화하기 위해 꼭 필요하답니다!)

 

 

2. 접근 제어자 (Access Modifier): 내 정보는 소중하니까! 🛡️🔒

클래스와 멤버를 만들다 보면 "어? 이 hp는 외부에서 마음대로 수정하면 안 되는데?", "이 특수 스킬은 특정 조건에서만 사용 가능해야 하는데..." 같은 고민이 생길 수 있어요. 🤔 맞아요! 모든 것을 활짝 열어두면, 예상치 못한 버그가 발생하기 쉽습니다. 예를 들어, 누군가 실수로 thor.hp = -9999; 이렇게 코드를 짜면 큰일 나겠죠!

 

그래서 등장한 것이 바로 접근 제어자(Access Modifier) 입니다! 접근 제어자는 클래스, 멤버 변수, 메소드 등에 붙어서 외부에서 얼마나 접근할 수 있는지를 통제하는 문지기 역할을 해요. 이것이 바로 객체 지향의 중요한 특징 중 하나인 캡슐화(Encapsulation)를 지키는 핵심 도구입니다! 💊

 

캡슐화는 중요한 데이터(속성)와 그 데이터를 다루는 기능(메소드)을 클래스라는 캡슐 안에 넣고, 외부에는 꼭 필요한 부분만 공개(public)하고 내부의 복잡하거나 민감한 부분(private)은 숨기는 것을 말해요. 약 캡슐 안의 성분은 보호하고, 우리는 캡슐만 먹으면 되는 것처럼요!

 

자바에는 주로 4가지 접근 제어자가 있습니다:

  1. public: 월드 프리패스! 🌍🔓
    • 어디서든(같은 클래스, 같은 패키지, 다른 패키지, 자식 클래스...) 누구나 접근 가능! 보통 클래스 자체나, 외부에서 반드시 사용해야 하는 핵심 메소드에 사용해요.
  2. protected: 상속자 우대! 👨‍👩‍👧‍👦🔑
    • 같은 패키지 내부 + 다른 패키지라도 상속받은 자식 클래스에서는 접근 가능! 상속 관계에서 부모의 특정 기능을 자식에게만 물려주고 싶을 때 유용해요.
  3. (default) (package-private): 우리 동네 전용! 🏘️🚪
    • 아무것도 안 쓰면 기본값! (키워드가 없어요)
    • 같은 패키지 안에서만 접근 가능. 다른 패키지에서는 (상속받아도) 접근 불가! 패키지 내부에서만 공유하고 싶은 기능에 사용해요.
  4. private: 나만 볼 거야! 🤫🔒
    • 가장 강력한 철벽! 오직 그 멤버가 선언된 클래스 내부에서만 접근 가능! 외부에서는 절대 직접 건드릴 수 없어요. 클래스의 중요한 데이터(멤버 변수)는 대부분 private으로 선언해서 보호하고, 이 데이터에 접근하거나 수정해야 할 때는 public으로 만들어진 메소드(Getter/Setter 또는 다른 기능 메소드)를 통하도록 하는 것이 캡슐화의 정석!

캡슐화 적용 예시 (Character 클래스 수정):

public class Character {
    private String name; // 🔒 이름은 외부에서 함부로 바꾸지 못하게!
    private int hp;      // 🔒 체력도 마찬가지!
    private int level;   // 🔒 레벨도!

    // 생성자에서 초기화는 가능하도록!
    public Character(String name, int initialHp, int level) {
        this.name = name;
        // 체력은 0 이상이어야 한다는 규칙 적용!
        if (initialHp > 0) {
            this.hp = initialHp;
        } else {
            this.hp = 1; // 최소 체력 1 보장
        }
        this.level = level;
    }

    // --- Getter 메소드들 (외부에서 값을 읽을 수 있게) ---
    // 🔓 public 메소드를 통해 이름 조회 기능 제공
    public String getName() {
        return this.name;
    }

    // 🔓 public 메소드를 통해 현재 체력 조회 기능 제공
    public int getHp() {
        return this.hp;
    }

    // 🔓 public 메소드를 통해 레벨 조회 기능 제공
    public int getLevel() {
        return level;
    }

    // --- 기능 메소드들 (값을 안전하게 변경하거나 사용) ---
    // 🔓 데미지를 입는 기능 (Setter 역할이지만, 더 안전하게!)
    public void takeDamage(int damage) {
        if (damage > 0) {
            this.hp -= damage;
            if (this.hp < 0) {
                this.hp = 0; // 체력이 음수가 되지 않도록 방지
            }
            System.out.println(this.name + "이(가) " + damage + "의 데미지를 입었습니다! 현재 HP: " + this.hp);
        } else {
            System.out.println("유효하지 않은 데미지 값입니다.");
        }
    }

    // 레벨업 기능 (내부 로직에 따라 안전하게 레벨 변경)
    public void levelUp() {
        this.level++;
        // 레벨업 시 체력 증가 등의 추가 로직 가능
        this.hp += 10; // 예시: 레벨업 시 체력 10 증가
        System.out.println(this.name + " 레벨 업! 🎉 현재 레벨: " + this.level + ", HP: " + this.hp);
    }

    // 🔓 기본 공격 (기능 제공)
    public void attack() {
        System.out.println(this.name + "이(가) 기본 공격!");
    }

    public void move() {
        System.out.println(this.name + "이(가) 이동합니다.");
    }
}

// 다른 클래스에서의 사용
public class Game {
    public static void main(String[] args) {
        Character basicChar = new Character("모험가", 50, 1);

        // basicChar.hp = 10000; // 🚨 컴파일 에러! private 멤버는 외부에서 직접 접근 불가!
        // System.out.println(basicChar.name); // 🚨 컴파일 에러!

        // 안전하게 public 메소드를 통해 접근!
        System.out.println("캐릭터 이름: " + basicChar.getName());
        System.out.println("현재 체력: " + basicChar.getHp());

        basicChar.takeDamage(20); // 데미지 입히기
        basicChar.levelUp();      // 레벨업 시키기
        basicChar.takeDamage(40); // 또 데미지!

        System.out.println("최종 체력: " + basicChar.getHp());
        System.out.println("최종 레벨: " + basicChar.getLevel());
    }
}

이제 Charactername, hp, levelprivate으로 보호되어 외부에서 직접 수정할 수 없어요! 대신 public으로 제공된 getName(), getHp(), takeDamage(), levelUp() 같은 메소드를 통해서만 안전하게 상호작용할 수 있죠. 이렇게 하면 실수로 데이터를 망가뜨릴 위험을 줄이고, 클래스를 사용하는 쪽에서는 내부 구현을 몰라도 제공된 기능만으로 쉽게 사용할 수 있게 됩니다. 이것이 바로 캡슐화의 매력! ✨

 

3. 정리하며 📝

휴~ 오늘 우리는 객체 지향 프로그래밍의 날개를 달아주는 상속접근 제어자(캡슐화)라는 강력한 무기를 장착했습니다! 🚀🛡️ 어제 배운 클래스와 객체에 이 두 가지 개념을 더하면, 여러분은 훨씬 더 재사용 가능하고, 안전하며, 잘 구조화된 자바 코드를 작성하는 개발자로 거듭나실 수 있을 거예요!

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

  • 상속 (Inheritance): extends를 이용해 부모 클래스의 자산을 물려받아 코드를 재사용하고 확장하는 마법! (코드 중복 ↓, 효율성 ↑) 📜➡️✨
  • 접근 제어자 (Access Modifier): public, protected, default, private으로 멤버 접근 권한을 설정하여 코드의 안전성을 높이는 문지기! 🛡️🔒
  • 캡슐화 (Encapsulation): 데이터와 기능을 묶고 내부를 보호하며, 필요한 인터페이스만 외부에 제공하여 객체의 독립성안정성을 높이는 원칙! 💊튼튼!

상속으로 코드의 재활용성을 높이고, 접근 제어자와 캡슐화로 소중한 데이터를 지키세요! 이 개념들은 앞으로 배울 다형성, 인터페이스 같은 더 흥미로운 객체 지향 기법들의 든든한 기반이 될 거예요. 꼭 직접 다양한 예제 (동물->강아지/고양이, 도형->원/사각형 등)를 만들어보며 손에 익히시길 강력 추천합니다! 💪💻

 

벌써 토요일 밤이네요! 다들 토요일, 즐겁게 보내셨나요? 😊 한 주 동안 새로운 개념 배우시느라 정말 수고 많으셨습니다! 주말에는 코딩 생각 잠시 내려놓고, 좋아하는 영화를 보거나, 맛있는 음식을 먹거나, 푹 잠을 자면서 에너지를 충전하는 시간을 꼭 가지세요!