Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/Dart] 객체 지향 프로그래밍 (Object-Oriented Programming) 본문

Programming/Dart

[Programming/Dart] 객체 지향 프로그래밍 (Object-Oriented Programming)

scii 2020. 10. 6. 16:04

다트는 실제 현실 세계를 반영한 객체 지향 프로그래밍 언어이다.

클래스

컴퓨터 세계에서 객체(object)는 저장 공간에 할당되어 값을 가지거나 식별자에 의해 참조되는 공간을 말한다. 변수, 자료 구조, 함수 또는 메소드 등이 객체가 될 수 있다. 이러한 객체를 메모리에 작성한느 것을 '인스턴스(instance)화' 한다고 하며 메모리에 작성된 객체를 인스턴스라고 한다.  
인스턴스화하기 위해서는 설계도가 필요한데 설계도 역할을 하는 것이 클래스(class)이다. 클래스 안에는 속성을 표현할 수 있는데 이를 프로퍼티(property) 라고 한다.

class Person {
    String name;
    int age;
}

void main() {
    Person person = new Person();
    
    // new 키워드는 생략 가능하다.
    Person person2 = Person();
}

new 키워드는 인스턴화하는 키워드이며 생략할 수 있다.

클래스 안에 작성하는 함수를 메소드라고 부른다. 메소드는 클래스의 프로퍼티를 조작하는 등의 용도로 사용한다. 


접근 지정자

변수명 앞에 _ 기호를 붙이지 않으면 외부에서 접근 가능하고, 붙이면 접근 불가능하다.

이것은 Python과 흡사하다. 파이썬도 dart언어와 마찬가지로 _ 를 붙이면 접근 불가한 변수가 된다. 하지만 파이썬은 _ 는 protected 고 __ 는 private인데 반해, dart언어는 _ 가 private이다.
// person.dart
class Person {
    String name;
    int _age;
    
    void addOneYear() {
        _age++;
    }
}

// main.dart
import 'person.dart';
...
var person = new Person();

// 에러! 접근 불가
person._age = 10;

_age에 접근 불가능하다. 이처럼 _ 기호가 붙은 private 변수는 해당 클래스가 정의되어 있지 않은 다른 파일에서 직접 접근할 수 없다. 하지만 정의되어 있는 파일 내에서는 여전히 직접 접근할 수 있다. 이러한 접근 규칙은 메소드에도 동일하게 적용된다.


생성자

생성자는 인스턴스화하는 방법을 제공하는 일종의 메소드이다. 다트는 기본 생성자를 제공하는데, 기본 생성자는 클래스명과 같은 이름의 메소드이다.

class Person {
    String name;
    int _age;
}

...
var Person = new Person();

사용자 정의 생성자를 추가하면 기본 생성자(Person())를 사용할 수 없게 되지만, 선택 매개 변수를 사용하면 Person()도 호출할 수 있다.

class Person {
    String name;
    int _age;
    
    Person({this.name, this._age});
}

...
var person = new Person();
var person = new Person(name: '김연아', _age: 31);

setter, getter

private 변수에 접근하려면 게터(getter)와 세터(setter) 메소드가 필요하다. 각각 읽기와 쓰기 기능을 한다. 변수 앞에 _가 없으면 어디에서든 접근할 수 있는 퍼블릭 변수이므로 게터와 세터 메소드를 작성할 필요 없다. 그런데 _를 붙이면 프라이빗 변수이므로 클래스 외부에서 접근하려면 게터와 세터 메소드를 작성해야 한다.

// person.dart
class Person {
  String name;
  int _age;
  
  int get age => _age;
}

// main.dart
import 'person.dart'

...
var person = Person();
print(person.age);

getter는 private 변수값에 변경을 주어 사용할 때도 유용하다. 

class Rectangle {
  num _left, _right;
  
  Rectangle(this._left, this._right);
  
  num get left => _left
  set left(number) => _left = number;
  
  num get right => _right;
  set right(number) => _right = number;
}
프로퍼티는 게터로 가져오고 세터로 설정한다. 프로퍼티끼리 직접 계산하는 방식보다 게터와 세터를 이용해 계산하는 방식이 코딩 실수로 발생하는 오류를 줄일 수 있다.

상속

현실 세계의 상속은 재산을 물려받는 것이다. 상속을 주는 쪽이 슈퍼클래스(또는 부모 클래스), 받는 쪽이 서브클래스(또는 자식 클래스)이다. 컴퓨터 세계의 상속은 슈퍼클래스를 그대로 복사한 후 기능 추가나 변경이 첨가된다.

다음은 run() 메소드를 가진 Hero 클래스를 SuperHero 클래스가 extends 키워드를 사용하여 상속받아 새로운 기능을 추가한 예이다. 상속을 받으면 원래 있던 기능을 그대로 물려받는데, 만약 새로 정의하고 싶다면 @override 이노테이션을 사용해 오버라이드하여 재정의하면 된다. 이때 super 키워드를 사용하면 슈퍼클래스에 접근할 수 있다. 나 자신을 참조할 때는 this를 사용할 수 있다.

class Hero {
  String name = '영웅';
  
  void run() { }
}

// Hero 상속
class SuperHero extends Hero {
  // 부모의 run() 메소드를 다시 정의 (오버라이드)
  @override
  void run() {
    부모의 run()을 실행
    super.run()
    this.fly();
  }
  
  void fly() { }
}

void main() {
  SuperHero hero = SuperHero();
  hero.run();
  hero.fly();
  print(hero.name);
}

이와 같이 상속은 기존 기능을 재정의할 때 사용한다. 그리고 상속은 포함 관계를 만든다.


추상 클래스 (abstract class)

추상 클래스는 추상 메소드를 포함하는 클래스이다. 추상 메소드는 선언만 되고 정의가 없는 메소드이다.

abstract class Monster {
  void attack();
}

추상 클래스는 그대로 인스턴스화할 수 없으며 다른 클래스에서 임플리먼트(implement) 하여 기능을 완성하는 상속 재료로 사용된다. 이 때  대상 클래스에는 implements 키워드를, 메소드에는 @override 키워드를 사용한다.

class Goblin implements Monster {
  @override
  void attack() {
    print('고블린 공격!');
  }
}

class Bat implements Monster {
  @override
  void attack() {
    print('할퀴기!');
  }
}

여러 추상 클래스를 한 번에 임플리먼트할 수 있다. 추상 클래스를 구현할 때는 모든 추상 메소드를 재정의해야 한다.

abstract class Flyable {
  void fly();
}

clsas Bat implements Monster, Flyable {
  @override
  void attack() {
    print('할퀴기!');
  }
  
  @override
  void fly() {
    print('슈우우웅!');
  }
}

믹스인 (mixin)

with 키워드를 사용하면 상속하지 않고 다른 클래스의 기능을 가져오거나 오버라이드할 수 있다. 이러한 기능을 믹스인(mixin)이라고 한다.

다음 예제에서 DarkGoblin은 Monster클래스, Goblin클래스, Hero클래스의 기능을 모두 가지고 있다.

class Goblin implements Monster {
  @override
  void attack() {
    print('고블린 공격!');
  }
}

class DarkGoblin extends Goblin with Hero {
}

이렇게 작성된 다크 고블린은 Goblin이기도 하며 Hero이기도 하며 Monster이기도 하다. 이러한 것을 다형성이라고 한다.

Comments