subindev 님의 블로그

[Flutter] #7 Dart 기본 문법 추상 클래스 본문

앱개발/Flutter

[Flutter] #7 Dart 기본 문법 추상 클래스

subindev 2025. 1. 7. 18:03

Dart 프로그래밍 기초

 

1. 추상 클래스과 인터페이스


추상클래스란 ?

추상 클래스(Abstract Class)란 객체지향 프로그래밍에서 공통적인 특징을 정의하지만, 스스로는 인스턴스를 생성할 수 없는 클래스를 말합니다. 추상 클래스는 주로 다른 클래스들이 상속받아 사용할 수 있도록 기본 구조를 정의하기 위해 사용됩니다.

 

특징

  1. 추상 메서드(Abstract Method) 포함
    • 추상 클래스는 하나 이상의 추상 메서드를 포함할 수 있습니다.
    • 추상 메서드는 선언만 되어 있고, 구현(구체적인 내용)이 없는 메서드입니다. 
    • abstract class Shape { abstract void draw(); // 추상 메서드 }
  2. 인스턴스 생성 불가
    • 추상 클래스 자체로는 객체를 생성할 수 없습니다.
      Shape shape = new Shape(); // 오류 발생
      
  3. 상속을 통해 구현
    • 추상 클래스를 상속받은 클래스가 추상 메서드를 구현해야 합니다.
      class Circle extends Shape {
          void draw() {
              System.out.println("Drawing a circle");
          }
      }
      
  4. 일반 메서드와 필드 포함 가능
    • 추상 클래스는 추상 메서드 외에도 일반 메서드와 멤버 변수를 포함할 수 있습니다.
      abstract class Shape {
          String color;
      
          void setColor(String color) {
              this.color = color;
          }
      
          abstract void draw();
      }
      

사용 이유

  1. 코드 재사용성 증가
    • 공통된 기능(필드와 메서드)을 추상 클래스에 정의하고, 이를 상속받아 중복 코드를 줄입니다.
  2. 설계 강제
    • 추상 메서드는 하위 클래스가 반드시 구현해야 하므로 일관된 인터페이스를 강제할 수 있습니다.
  3. 유연한 확장성 제공
    • 추상 클래스는 다양한 하위 클래스가 공통적인 구조를 가지면서도 각기 다른 동작을 구현하도록 유도합니다.

예시

abstract class Animal {
    // 추상 메서드
    abstract void sound();

    // 일반 메서드
    void sleep() {
        System.out.println("Sleeping...");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Bark!");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Meow!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.sound(); // 출력: Bark!
        dog.sleep(); // 출력: Sleeping...

        Animal cat = new Cat();
        cat.sound(); // 출력: Meow!
    }
}

 

추상클래스 vs 인터페이스

다중 상속 다중 상속 불가능 (한 클래스만 상속 가능) 다중 구현 가능
메서드 구현 일반 메서드와 추상 메서드 모두 가질 수 있음 기본적으로 추상 메서드만 가짐 (Java 8 이후 default 메서드 가능)
속성 인스턴스 변수 가질 수 있음 상수(변경 불가능한 값)만 가질 수 있음

 

 

인터페이스란?

  • 인터페이스는 클래스나 프로그램이 서로 상호작용할 수 있도록 정의된 일종의 "규약"입니다.
  • 인터페이스는 오직 추상 메서드(구현 없는 메서드)와 상수만을 포함합니다.
  • 인터페이스를 구현한 클래스는 인터페이스에 정의된 모든 메서드를 반드시 구현해야 합니다.

특징

  1. 다중 구현 가능
    • 하나의 클래스는 여러 개의 인터페이스를 동시에 구현할 수 있습니다. java에서 다중상속을 인터페이스를 이용하여 구현합니다.
  2. 추상 메서드만 포함
    • Java 8부터는 default와 static 메서드를 포함할 수 있지만, 기본적으로 메서드는 구현부가 없습니다.
  3. 상수만 포함
    • 인터페이스 내의 변수는 자동으로 public static final로 선언됩니다. (즉, 상수만 포함)
  4. 객체 생성 불가
    • 인터페이스 자체로는 객체를 생성할 수 없습니다.

인터페이스 예제

1. 기본 사용 예제

// 인터페이스 정의
interface Animal {
    void sound(); // 추상 메서드
    void sleep();
}

// 인터페이스 구현
class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Bark!");
    }

    @Override
    public void sleep() {
        System.out.println("Dog is sleeping...");
    }
}

class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("Meow!");
    }

    @Override
    public void sleep() {
        System.out.println("Cat is sleeping...");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.sound(); // 출력: Bark!
        dog.sleep(); // 출력: Dog is sleeping...

        Animal cat = new Cat();
        cat.sound(); // 출력: Meow!
        cat.sleep(); // 출력: Cat is sleeping...
    }
}

2. 다중 인터페이스 구현

// 인터페이스 정의
interface Printable {
    void print();
}

interface Scannable {
    void scan();
}

// 클래스가 두 인터페이스를 구현
class PrinterScanner implements Printable, Scannable {
    @Override
    public void print() {
        System.out.println("Printing a document...");
    }

    @Override
    public void scan() {
        System.out.println("Scanning a document...");
    }
}

public class Main {
    public static void main(String[] args) {
        PrinterScanner device = new PrinterScanner();
        device.print(); // 출력: Printing a document...
        device.scan();  // 출력: Scanning a document...
    }
}

3. Java 8 이후 default 메서드

  • default 메서드는 인터페이스 내에서 기본 구현을 제공할 수 있습니다.
  • 클래스는 default 메서드를 재정의하지 않아도 됩니다.
interface Vehicle {
    void start();

    // Java 8부터 추가된 default 메서드
    default void stop() {
        System.out.println("The vehicle is stopping.");
    }
}

class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("The car is starting.");
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle car = new Car();
        car.start(); // 출력: The car is starting.
        car.stop();  // 출력: The vehicle is stopping.
    }
}

 

 

추상 클래스 vs 인터페이스 비교

구분 추상 클래스 인터페이스

목적 클래스 간의 공통 기능 정의 및 상속 구조 설계 클래스가 따라야 할 규약(계약)을 정의
다중 상속/구현 다중 상속 불가 다중 구현 가능
메서드 일반 메서드와 추상 메서드 모두 포함 가능 기본적으로 추상 메서드만 포함 (default 메서드 예외)
속성 인스턴스 변수 포함 가능 상수만 포함 가능 (public static final 자동 적용)
사용 특정 공통 동작의 기본 구현 제공 클래스 간 규약을 강제

 

언제 사용해야 할까?

  • 추상 클래스: 공통 동작과 필드(상태)를 공유해야 하며, 기본 구현이 필요한 경우.
  • 인터페이스: 서로 다른 클래스 간의 일관된 규약을 강제하고, 다중 구현이 필요한 경우.

 

 


Dart에서의 추상클래스

abstract class Animal {
  void performAction();
}

// 추상클래스를 구현할 때 implements 를 사용한다.
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 start(Animal a) {
  // performAction() 메서드가 동작하게 만들고 싶다.
  // 단. 강아지든, 고양이든, 물고기든 동적으로 들어 오더라도
  // performAction() 호출 되게 설계해야 해!
  a.performAction();
}

void start1(Cat c) {
  c.performAction();
}

void start2(Dog c) {
  c.performAction();
}

void start3(Fish c) {
  c.performAction();
}

void main() {
  start(Dog());
  start(Cat());
  start(Fish());
}

 

 

 


Quiz

추상 클래스와 동적 바인딩을 활용한 다형성 구현하기

Shape라는 추상 클래스를 정의하고, 이를 상속받은 Circle과 Rectangle 클래스가 있습니다. calculateArea 함수를 통해 동적 바인딩을 활용하여 다양한 도형의 면적을 계산할 수 있도록 구현하세요

// 추상 클래스
abstract class Shape {
  // 메서드의 바디(구현부) 없다면 추상 메서드 이다.
  double getArea();
}

class Circle implements Shape {
  final double radius;
  // 생성자는 가능한 축약형으로 만들자 - 우리의 규칙
  Circle(this.radius);

  // 면접을 구하는 행위를 해야 한다.
  @override
  double getArea() {
    return 3.14 * radius * radius;
  }
}

class Rectangle implements Shape {
  final double width;
  final double height;

  Rectangle(this.width, this.height);

  // 면접을 구하는 행위를 해야 한다.
  @override
  double getArea() {
    return width * height;
  }
}

// 동적 바인을 활용한 함수를 설계해보자.
// 전역 함수
void calculateArea(Shape s) {
  // 전달된 도형의 면접을 출력하시오.
  print(s.getArea());
}

void main() {
  Shape myCircle = Circle(5.0);
  Shape myRectangle = Rectangle(4.0, 6.0);

  calculateArea(myCircle);
  calculateArea(myRectangle);
}