{% seo %}Class 와 객체 (2) | myHobbyBest
Class 와 객체 (2)
포스트
취소

Class 와 객체 (2)

Implicit interfaces (함축적 인터페이스)

모든 클래스는 클래스와 클래스가 구현하는 모든 인터페이스의 모든 인스턴스 멤버를 포함하는 인터페이스를 함축적으로 정의한다. B 클래스의 구현을 상속받지 않고 클래스 B의 API를 지원하는 클래스 A를 생성하려면 클래스 A가 B 인터페이스를 구현해야 한다.

클래스는 구현절 안에서 선언한 다음 인터페이스에 필요한 API를 제공하여 하나 이상의 인터페이스를 구현한다. 예를 들어:

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
// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final String _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  String get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

실행 결과 출력

1
2
Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?

다음은 클래스가 여러 인터페이스를 구현하도록 지정하는 예이다.

1
class Point implements Comparable, Location {...}

클라스의 확장(Extending a class)

서브클래스를 생성하려면 extends 를 사용하며 super 는 상위(부모)클래스를 참조한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

확장의 다른 사용법에 대해서는 제네릭의 매개변수화된 유형에 대한 설명을 참조한다.

  • 멤버의 재 정의 (Overriding members)

서브클래스는 인스턴스 메서드(연산자 포함), getter 및 setter를 재정의할 수 있다. @override 주석을 사용하여 의도적으로 멤버를 재정의하고 있음을 나타낼 수 있다.

1
2
3
4
5
6
7
8
9
10
class Television {
  // ···
  set contrast(int value) {...}
}

class SmartTelevision extends Television {
  @override
  set contrast(num value) {...}
  // ···
}

재정의 메서드 선언은 아래와 같이 여러 가지 면에서 재정의된 메서드와 일치해야 한다.

  • 반환 유형은 재정의 대상 메서드의 반환 유형과 동일한 유형(또는 하위 유형)이어야 한다.
  • 인수 유형은 재정의 대상 메서드의 인수 유형과 동일한 유형(또는 상위 유형)이어야 한다. 앞의 예에서 SmartTelevisionconstrast 세터(setter)는 인수 유형을 int에서 상위 유형인 num으로 변경하고 있다.
  • 재정의 대상 메서드가 n 위치 매개변수를 허용하는 경우 재정의 메서드는 n 위치 매개변수도 허용해야 한다.
  • 제네릭 메서드는 제네릭이 아닌 메서드를 재정의할 수 없고 제네릭이 아닌 메서드는 제네릭 메서드를 재정의할 수 없다.

때로는 메소드 매개변수 또는 인스턴스 변수의 type을 축소하고 싶을 수 있다. 이것은 정상적인 규칙을 위반하고 런타임 시에 type 오류를 일으킬 수 있다는 점에서 다운캐스트(?)와 유사하다. 그래도 코드가 type 오류가 발생하지 않는다는 것을 보장할 수 있다면 유형을 좁힐 수 있다. 이 경우 매개 변수 선언에서 covariant 키워드를 사용할 수 있다. 자세한 내용은 Dart language specification 을 참조한다.

  • 주의 : == 를 재정의하면 객체의 해시코드 게터(hashCode getter) 도 재정의해야 한다. == 및 hashCode 재정의의 예는 Implementing map keys 을 참조한다.

  • noSuchMethod()

코드가 존재하지 않는 메서드나 인스턴스 변수를 사용하려고 시도할 때마다 이를 알아내서 대응하려면 noSuchMethod()를 재정의할 수 있다.

1
2
3
4
5
6
7
8
9
class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: '
        '${invocation.memberName}');
  }
}

다음 중 하나에 해당하지 않는 한 구현되지 않는 메서드를 호출할 수 없다.

  • 수신자에 static type 인 dynamic이 있다.
  • 수신자는 구현되지 않는 메서드(추상은 OK)를 정의하는 정적 유형을 가지며 수신자의 dynamic type은 Object.클라스 내부에 있는 것과는 다른 noSuchMethod()가 구현된다.(?)

자세한 내용은 비공식 noSuchMethod forwarding specification 을 참조하자.

확장 메서드 (Extension methods)

확장 메서드는 기존 라이브러리에 기능을 추가하는 방법이다. 모르는 사이에 확장 메서드을 사용할 수도 있다. 예를 들어 IDE에서 코드완성(code completion) 기능을 사용하면 일반 방법들과 함께 확장 메서드가 제안된다.

다음은 string_apis.dart에 정의된 parseInt()라는 이름의 확장 메서드를 사용하는 예이다.

1
2
3
4
import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

확장 메서드 사용 및 구현에 대한 자세한 내용은 extension methods page 를 참조하자.

Enumerated types (열거형)

종종 enumerations 또는 enums 라고도 불리는 열거형(Enumerated types)은 고정된 수의 상수 값들을 나타내는 데 사용되는 특별한 종류의 클래스다.

( 주의 : 모든 enum은 자동으로 Enum 클래스를 확장한다. 이들은 또한 봉인(sealed)되어 있어서 서브클래싱, 구현, 혼합되거나 명시적 인스턴스를 할 수 없다.

추상 클래스(abstract class)와 믹스인(mixin)은 Enum을 명시적으로 구현하거나 확장할 수 있지만 enum 선언에 의해 구현되거나 혼합되지 않는 한 어떤 객체도 해당 클래스 나 믹스인의 유형을 실제로 구현할 수 없다.)

  • 간단한 enums 선언 (Declaring simple enums)

간단한 enums type을 선언하려면 enum 키워드를 사용하고 열거하려는 값을 나열다.

1
enum Color { red, green, blue }
  • TIP : 복사-붙여넣기 오류를 방지하기 위해 열거 유형을 선언할 때 마지막 항목 뒤에 쉼표(,)를 사용할 수도 있다.

  • 향상된 enums 선언 (Declaring enhanced enums)

Dart에서는 열거형 선언이 필드, 메서드 및 알려진 상수 인스턴스의 확정된 수로 제한되는 상수 생성자로 클래스를 선언하는 것이 허용된다.

향상된 열거형(enhanced enums)을 선언하려면 일반 클래스와 유사한 구문을 따르지만 몇 가지 추가 요구 사항이 있다.

  • 믹스인(mixin)에 의해 추가된 변수를 포함하여 인스턴스 변수는 최종 변수여야 한다.
  • 모든 생성 생성자는 상수여야 한다.
  • 팩토리 생성자는 확정되고 알려진 열거형 인스턴스 중 하나만 반환할 수 있다.
  • Enum은 자동으로 확장되므로 다른 클래스는 확장할 수 없다.
  • index, hashCode, 동일성 연산자 (==) 에 대한 재정의를 할수 없다.
  • values 라는 멤버는 자동으로 생성된 정적값 getter와 충돌하므로 열거형에서 선언할 수 없다.

  • 열거형의 모든 인스턴스는 선언 시작 부분에 선언되어야 하며 적어도 하나의 인스턴스가 선언되어야 한다.

다음은 여러 인스턴스, 인스턴스 변수들, 하나의 getter 그리고 구현된 인터페이스로 향상된 열거형을 선언하는 예이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Vehicle implements Comparable<Vehicle> {
  car(tires: 4, passengers: 5, carbonPerKilometer: 400),
  bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
  bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);

  const Vehicle({
    required this.tires,
    required this.passengers,
    required this.carbonPerKilometer,
  });

  final int tires;
  final int passengers;
  final int carbonPerKilometer;

  int get carbonFootprint => (carbonPerKilometer / passengers).round();

  @override
  int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}

향상된 열거형 선언에 대한 자세한 내용은 클래스 섹션을 참조하자. (주: 향상된 열거형에는 2.17 이상의 Dart 언어 버전이 필요하다.)

  • 이넘의 사용법 ( Using enums )

다른 정적 변수와 마찬가지로 열거된 값에 액세스한다.

1
2
3
4
final favoriteColor = Color.blue;
if (favoriteColor == Color.blue) {
  print('Your favorite color is blue!');
}
  • 열거형의 각 값에는 열거형 선언에서 값의 0부터 시작하는 위치를 반환하는 인덱스 getter가 있다. 예를 들어 첫 번째 값의 인덱스는 0이고 두 번째 값의 인덱스는 1 이다.
1
2
3
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
  • 열거된 모든 값의 list를 얻으려면 이넘(enums)의 valuse 상수를 사용한다.
1
2
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

enums을 switch 문에서 사용할 수 있는데, 모든 이넘 값을 처리하지 않으면 경고(WARNING)가 표시 된다.

1
2
3
4
5
6
7
8
9
10
11
12
var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

Color.blue에서 ‘blue’와 같은 열거형 값의 이름에 액세스해야 하는 경우 .name 속성을 사용한다.

1
print(Color.blue.name); // 'blue'

클래스에 기능 추가 : mixins

믹스인(mixins)은 여러 클래스 계층 구조에서 클래스 코드를 재사용하는 방법이다. 믹스인을 사용하려면 with 키워드와 하나 이상의 믹스인 이름을 사용한다. 다음 예제는 믹스인을 사용하는 두 클래스를 보여준다.

1
2
3
4
5
6
7
8
9
10
class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

믹스인을 구현하려면 Object를 확장하고 생성자를 선언하지 않는 클래스를 생성한다. mixin이 일반 클래스로 사용되는 것을 원치 않으면 class 대신 mixin 키워드를 사용한다. 예를 들어:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

떄로는 믹스인을 사용할 수 있는 유형(type)을 제한하려는 경우가 있다. 예를 들어 믹스인은 믹스인이 정의하지 않은 메서드를 호출할 수 있는지 여부에 따라 달라질 수 있다. 다음 예제에서 볼 수 있듯이 on 키워드를 사용하여 필요한 슈퍼클래스를 지정함으로써 믹스인의 사용을 제한할 수 있다.

1
2
3
4
5
6
7
8
9
class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

앞의 코드에서 Musician 클래스를 확장하거나 구현하는 클래스만이 Mixin MusicalPerformer를 사용할 수 있다. SingerDancer는 Musician을 확장하기 때문에 MusicalPerformer에서 믹스인할 수 있다.

클라스 변수와 메서드 (Class variables and methods)

정적(static) 키워드를 사용하여 클래스 전체 변수 및 메서드를 구현한다.

  • Static variables

정적 변수(클래스 변수)는 클래스 전체 상태 및 상수에 유용하다.

1
2
3
4
5
6
7
8
class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

정적 변수는 사용될 때까지 초기화되지 않는다. (주: 이 페이지는 상수 이름에 대해 lowerCamelCase를 선호하는 스타일 가이드 권장 사항을 따른다.)

  • Static methods

static 메서드(클래스 메서드)는 인스턴스에서 작동하지 않으므로 이에 대한 액세스 권한이 없다. 그러나 정적 변수에 액세스할 수 있다. 다음 예제와 같이 클래스에서 직접 정적 메서드를 호출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

(주: 일반적이거나 널리 사용되는 유틸리티 및 기능에 대해서는 정적 메서드 대신 최상위 함수를 사용하는 것이 좋다.)

정적 메서드를 compile-time 상수로 사용할 수 있다. 예를 들어 정적 메서드를 상수 생성자에 대한 매개 변수로 전달할 수 있다.

참고 자료

  • https://dart.dev/guides/language/language-tour
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

Class 와 객체지향 프로그래밍 ( OOP )

제네릭(Generics)

Comments powered by Disqus.