본문 바로가기
Programming/Flutter

[Flutter] Dart 언어 기본 문법 #06 - Mixin & 추상 클래스

by NAIMJAE 2025. 1. 7.

#06 Dart 언어 기본 문법 - Mixin & 추상 클래스


Ⅰ OOP 객체 관계

01. OOP 객체 관계란?

객체 관계

  • 객체 지향 프로그래밍에서 객체 관계란 프로그램을 구성하는 여러 객체들이 서로 상호작용하고 연결되는 방식을 의미
  • 객체들이 서로 독립적으로 존재하는 것이 아니라, 다른 객체와 관계를 맺고 협력하여 기능을 수행 함
  • 객체 간의 관계를 잘 설계하면 코드의 재사용성, 유지 보수성, 확장성을 높일 수 있음
  • 객체의 관계는 [연관 관계, 상속 관계, 의존 관계]로 분류

02. 연관 관계 (Association)

연관 관계

  • 연관 관계란 객체들이 서로 연결되어 있는 관계
  • 컴포지션 관계와 집합 관계로 분류

 

컴포지션 관계 (Composition)

  • [부분-전체] 관계 중에서 생명 주기가 밀접하게 연관된 강한 소유 관계를 의미
  • 전체 객체가 소멸될 때 부분 객체도 함께 소멸, 부분 객체는 전체 객체에 종속
// 부분 객체
class Engine {
    final String type;
    Engine(this.type);

    void startEngine() {
        print('${type} 엔진이 시동됩니다.');
    }
}

// 전체 객체
class Car {
    final Engine engine;
    Car(String engineType) : engine = Engine(engineType);

    void startCart() {
        engine.startEngine();
        print('차가 출발합니다.');
    }
}

void main() {
    // 부분 객체의 생명주기를 전체 객체에서 관리
    Car car = Car('v8');
}

 

집합 관계 (Aggregation)

  • [부분-전체] 관계 중에서 전체와 부분의 생명 주기가 독립적이며, 약한 소유 관계를 의미
  • 전체 객체가 소멸될 때 부분 객체는 독립적으로 존재할 수 있음
// 부분 객체
class Employee {
  final String name;
  Employee(this.name);

  void displayEmployeeInfo() {
    print('직원의 이름 : ${name}');
  }
}

// 전체 객체
class Department {
    final String deptName;
    final List<Employee> employees;
    Department(this.deptName) : employees = [];

    void addEmployee(Employee emp) {
        employees.add(emp);
    }

    void displayDepartmentInfo() {
        print('부서 이름 : ${deptName}');
        for (var emp in employees) {
            emp.displayEmployeeInfo();
        }
    }
}

void main() {
    // 부분 객체는 전체 객체와 독립적으로 존재 가능
    Department dept1 = Department('개발 부서');
    Department dept2 = Department('디자인 부서');
    Employee emp1 = Employee('홍길동');
    Employee emp2 = Employee('김철수');
    Employee emp3 = Employee('김유신');

    dept1.addEmployee(emp1);
    dept1.addEmployee(emp2);
    dept2.addEmployee(emp3);

    dept1.displayDepartmentInfo();
    dept2.displayDepartmentInfo();
}

Ⅱ Mixin

01. Mixin이란?

Mixin이란?

  • Mixin은 Dart에서 클래스 간의 코드를 재사용하기 위한 매커니즘, 일족의 코드 조각
  • Mixin은 다중 상속의 문제를 해결할 수 있으며, 컴퍼지션을 사용하지 않고 여러 클래스의 기능을 공유할 수 있음

 

Mixin의 필요성

  • Java에서 하나의 클래스는 오직 하나의 클래스만 상속할 수 있는 단일 상속만 허용
  • 대신 interface를 통해 다중 구현이 가능하지만, 메서드의 구체적인 구현이 제한됨
  • Mixin을 활용하면 다중 상속 문제를 해결하고, 다른 클래스의 기능을 재사용 가능

 

with 키워드

  • Mixin을 클래스에 주입할 때 사용하는 키워드
  • 다른 클래스의 코드를 상속 없이 재사용하기 위해 사용
  • 만약 Mixin 주입 과정에서 코드 충돌이 발생할 경우 마지막에 주입된 Mixin의 메서드를 적용
mixin Engine {
    int power = 5000;
}

mixin Wheel {
    String wheelName = '4륜 구동 바퀴';
}

class BMW with Engine, Wheel {}

void main() {
    // BMW 객체 인스턴스화
    BMW bmw1 = BMW();
    
    // BMW 클래스에는 power과 wheelName 속성이 정의되어 있지 않지만,
    // mixin을 통해 상속받은 속성을 참조 가능
    print('bmw1 power : ${bmw1.power}');
    print('bmw1 wheelName : ${bmw1.wheelName}');

    Wheel wheel1 = Wheel(); // 에러 - Mixin은 인스턴스화 불가능
}

02. Mixin 인스턴스화

Mixin 인스턴스화

  • 기본적으로 Mixin은 클래스가 아닌 코드 조각에 가까워, 독립적으로 인스턴스화를 할 수 없음
  • 컴파일 과정에서 주입되는 class로 병합되어 하나의 인스턴스가 됨
  • Mixin은 생성자를 정의할 수 없으며, 독립적인 객체로 존재할 수 없음

 

Mixin class

  • Mixin class는 Mixin을 독립적으로 인스턴스화할 수 있게 함
  • Mixin class는 Mixin의 성질과 class의 성질을 모두 가지고 있음
  Mixin Class mixin class
인스턴스화 불가능 가능 가능
다중 상속 가능 불가능 가능
생성자 정의 불가능 가능 불가능
mixin class Engine {
    int power = 3000;
}

mixin class Wheel {
    String wheelName = '4륜 구동 바퀴';
}

class BMW with Engine, Wheel {}

void main() {
    BMW bmw1 = BMW();
    print('bmw1 power : ${bmw1.power}');
    print('bmw1 wheelName : ${bmw1.wheelName}');

    // Mixin Class는 독립적으로 인스턴스화 가능
    Engine engine = Engine();
    Wheel wheel = Wheel();
    print('engine : ${engine.power}');
    print('wheel : ${wheel.wheelName}');
}

Ⅲ 추상 클래스

01. 추상 클래스란?

추상 클래스란?

  • 추상 클래스는 완전한 구현들을 제공하지 않는 클래스
  • 상속을 통해 다른 클래스가 이를 확장하여, 구체적인 동작을 구현하도록 강제할 수 있는 클래스
  • 추상 클래스는 조금 더 일반화적인 개념을 표현하며, 공통된 속성과 메서드를 정의하는데 많이 활용
  • 추상 클래스는 메서드 구현부가 없으며, 인스턴스화 시킬 수 없음

02. 추상 클래스로의 코드 발전 과정

1) 동물 소리가 나는 일반 클래스 생성

class Dog {
    void performAction() {
        print('멍멍 배고파');
    }
}

class Cat {
    void performAction() {
        print('야옹 배고파');
    }
}

void main() {
    Dog dog = Dog();
    Cat cat = Cat();
    
    dog.performAction();
    cat.performAction();
}

 

2) 물고기 클래스 추가

class Dog {
    void performAction() {
        print('멍멍 배고파');
    }
}

class Cat {
    void performAction() {
        print('야옹 배고파');
    }
}

class Fish {
    void hungry() {
        print('뻐끔 배고파');
    }
}

void main() {
    Dog dog = Dog();
    Cat cat = Cat();
    
    dog.performAction();
    cat.performAction();

    Fish fish = Fish();
    fish.hungry();
    
    // 기능은 정상적으로 작동하지만 공통된 행위를 하는 메서드의 이름이 다름
    fish.performAction(); // 만약 동적인 코드였다면 오류 발생
}

 

3) Animal 추상 클래스를 구현

abstract class Animal {
    void performAction();
}

class Dog implements Animal {
    @override
    void performAction() {
        print('멍멍 배고파');
    }
}

class Cat implements Animal {
    @override
    void performAction() {
        print('야옹 배고파');
    }
}

class Fish implements Animal {
    @override
    void performAction() {
        print('뻐끔 배고파');
    }
}

void main() {
    Dog dog = Dog();
    Cat cat = Cat();
    
    dog.performAction();
    cat.performAction();

    Fish fish = Fish();
    fish.performAction();
    
    // 공통된 행위를 하는 메서드를 추상 클래스에서 정의했기 때문에
    // 이를 구현하는 클래스들은 공통된 행위를 구현하기 위해 같은 이름의 메서드를 사용
}

 

4) 동적 바인딩

void start(Animal name) {
    // 어떤 동물이 동적으로 전달되더라도 performAction()이 호출되게 설계
    name.performAction();
}

void main() {
    Dog dog = Dog();
    Cat cat = Cat();
    
    dog.performAction();
    cat.performAction();

    Fish fish = Fish();
    fish.performAction();

    // 동적 바인딩
    start(Dog());
    start(Cat());
    start(Fish());
}