Flutter for Beginners

Dart 객체지향 프로그래밍 - 상속(Inheritance)

Andrew's Akashic Records 2025. 2. 24. 13:07
728x90

Dart

 

 

상속(Inheritance)

상속은 부모 클래스의 속성과 메서드를 자식 클래스에서 사용할 수 있도록 하는 기능입니다.

1. 기본 상속

class Animal {
  String name;

  Animal(this.name);

  void makeSound() {
    print("$name이(가) 소리를 냅니다.");
  }
}

class Dog extends Animal {
  Dog(String name) : super(name);

  void bark() {
    print("$name이(가) 짖습니다. 🐶");
  }
}

void main() {
  Dog dog = Dog("멍멍이");
  dog.makeSound(); // 멍멍이가 소리를 냅니다.
  dog.bark();      // 멍멍이가 짖습니다. 🐶
}
  • super(name);을 사용하면 부모 클래스의 생성자를 호출할 수 있습니다.

2. 메서드 오버라이딩 (Method Overriding)

자식 클래스에서 부모 클래스의 메서드를 재정의(Overriding) 할 수 있습니다.

class Animal {
  void makeSound() {
    print("동물이 소리를 냅니다.");
  }
}

class Dog extends Animal {
  @override
  void makeSound() {
    print("멍멍! 🐶");
  }
}

void main() {
  Dog dog = Dog();
  dog.makeSound(); // 멍멍! 🐶
}
  • @override 키워드를 사용하여 부모의 메서드를 재정의할 수 있습니다.

3. 캡슐화(Encapsulation)

Dart에서는 변수와 메서드의 접근 범위를 제한하여 데이터를 보호할 수 있습니다.

3.1 접근 제한자 (_ 언더스코어 사용)

Dart에서는 private(비공개) 변수를 만들 때 변수명 앞에 _를 붙입니다.

class BankAccount {
  String owner;
  double _balance; // private 변수

  BankAccount(this.owner, this._balance);

  void deposit(double amount) {
    _balance += amount;
    print("$amount원이 입금되었습니다.");
  }

  void showBalance() {
    print("잔액: $_balance원");
  }
}

void main() {
  BankAccount account = BankAccount("Alice", 1000);
  account.deposit(500);
  account.showBalance(); // 잔액: 1500원

  // account._balance = 5000; // ❌ 접근 불가 (private 변수)
}
  • _balance 변수는 클래스 내부에서만 접근 가능하고, 외부에서는 직접 수정할 수 없습니다.

4. 추상 클래스와 인터페이스

Dart의 extend와 implement (추상 클래스 vs 인터페이스)

Dart에서 객체지향 프로그래밍(OOP) 을 구현할 때 extend와 implement를 사용하여 클래스를 확장할 수 있습니다.
이 두 개념은 상속(Inheritance)  인터페이스(Interface) 구현에서 중요한 역할을 합니다.

4.1. extend (클래스 상속)

extend를 사용하면 부모 클래스의 속성과 메서드를 상속받을 수 있습니다.

  • 상속된 클래스는 부모 클래스의 모든 메서드와 변수를 그대로 사용할 수 있음.
  • 필요하면 @override를 사용하여 부모 메서드를 재정의할 수 있음.
  • 단일 상속만 가능 (Dart는 다중 상속을 지원하지 않음).
// 부모 클래스 (추상 클래스)
abstract class Animal {
  void makeSound() {
    print("동물이 소리를 냅니다.");
  }
}

// 자식 클래스 (Animal을 상속)
class Dog extends Animal {
  @override
  void makeSound() {
    print("멍멍! 🐶");
  }
}

void main() {
  Dog dog = Dog();
  dog.makeSound(); // 멍멍! 🐶
}
  • Dog 클래스는 Animal 클래스를 extend하여 모든 속성과 메서드를 상속.
  • makeSound() 메서드를 @override 하여 부모 메서드를 재정의.

4.2. implement (인터페이스 구현)

implement를 사용하면 특정 클래스의 인터페이스를 구현할 수 있습니다.

  • implements를 사용하면 모든 메서드를 직접 구현해야 함.
  • 다중 인터페이스 구현 가능 (여러 개의 클래스 인터페이스를 구현 가능).
  • 기존 클래스의 구현을 가져올 수 없고, 새롭게 모든 메서드를 정의해야 함.

 

// 인터페이스 역할을 하는 추상 클래스
abstract class Animal {
  void makeSound(); // 반드시 구현해야 함
}

// 인터페이스를 구현하는 클래스
class Cat implements Animal {
  @override
  void makeSound() {
    print("야옹! 🐱");
  }
}

void main() {
  Cat cat = Cat();
  cat.makeSound(); // 야옹! 🐱
}
  • implements를 사용하면 부모 클래스의 메서드를 반드시 구현해야 함.
  • Cat 클래스는 Animal의 makeSound() 메서드를 구현해야 오류가 발생하지 않음.

4.3. extend vs implement 차이점 정리

특징 extend (상속) implement (인터페이스)

특징 extend implement
기능 부모 클래스의 모든 속성과 메서드를 상속받음 부모 클래스의 인터페이스(메서드 정의)만 가져옴
메서드 구현 재정의(override) 가능, 안 해도 됨 모든 메서드를 반드시 구현해야 함
다중 사용 가능 여부 단일 상속만 가능 여러 개의 인터페이스 구현 가능
부모 클래스의 구현 사용 가능 (super 키워드 지원) 부모 클래스의 구현을 사용할 수 없음
사용 예제 class Dog extends Animal {} class Dog implements Animal {}

4.4. extend와 implement를 함께 사용하기

extend로 상속을 받고, implements로 다른 인터페이스를 추가로 구현할 수도 있습니다.

 

// 부모 클래스
abstract class Animal {
  void eat() {
    print("동물이 먹습니다.");
  }
}

// 인터페이스 역할을 하는 클래스
abstract class Flyable {
  void fly();
}

// Bird 클래스는 Animal을 상속(`extend`)하고 Flyable을 구현(`implements`)함
class Bird extends Animal implements Flyable {
  @override
  void fly() {
    print("새가 날아갑니다. 🕊️");
  }
}

void main() {
  Bird bird = Bird();
  bird.eat(); // 동물이 먹습니다. (Animal에서 상속)
  bird.fly(); // 새가 날아갑니다. (Flyable에서 구현)
}
  • Bird 클래스는 Animal을 상속받아(extend) eat()을 그대로 사용.
  • Flyable 인터페이스를 구현(implements)하여 fly()를 직접 정의.

5. 믹스인 (Mixin)

Mixin을 사용하면 코드를 재사용할 수 있으며, 다중 상속처럼 활용할 수 있습니다.

with 키워드를 사용하여 믹스인을 추가할 수 있습니다.

mixin Swimmable {
  void swim() {
    print("수영할 수 있습니다! 🏊");
  }
}

class Fish with Swimmable {}

void main() {
  Fish fish = Fish();
  fish.swim(); // 수영할 수 있습니다! 🏊
}
  • mixin을 사용하면 기능을 추가하는 방식으로 코드 재사용 가능.
  • extends, implements와 함께 사용하여 유연한 객체지향 프로그래밍 가능.

6. Dart 3.0 interface 키워드 추가

Dart 3.0부터 interface 키워드가 도입되어, 특정 클래스를 인터페이스로만 사용할 수 있도록 제한할 수 있습니다.

interface 클래스 예제

interface class Animal {
  void makeSound();
}

class Dog implements Animal {
  @override
  void makeSound() {
    print("멍멍! 🐶");
  }
}

void main() {
  Dog dog = Dog();
  dog.makeSound(); // 멍멍! 🐶
}
  • interface class는 반드시 implements로만 사용 가능.
  • 기본 구현이 있더라도 implements한 클래스는 모든 메서드를 직접 구현해야 함.
728x90