본문 바로가기
Dart/기초

[Dart] Dart 클래스 배우기

by 붕어사랑 티스토리 2021. 10. 14.
반응형

https://dart.dev/guides/language/language-tour

 

A tour of the Dart language

A tour of all the major Dart language features.

dart.dev

 

해당페이지 내용을 기반으로 합니다. 다른언어와 겹치는 내용은 제외하였습니다.

 

Dart의 클래스

  • Dart의 클래스는 최상위 클래스로 Object 클래스를 상속한다
  • Dart는 Mixin-based inheritance를 지원한다.

mixin 이 뭔말인지 몰라서 찾아봤는데 아래와 같이 정의되어있다

 

 

Mixin-ased inheritance means that although every class (except for the top class, Object?) has exactly one superclass, a class body can be reused in multiple class hierarchies.

 

간단히 말해서 c++처럼 다중상속을 지원한다고 생각하면 될 것 같다.

 

예를들어 사람이라는 인터페이스로 요리잘하는사람, 설거지잘하는사람 을 구현했다고 치자

 

만약 요리도잘하고 설거지도 잘하는 사람을 만들고 싶으면 클래스를 하나 만들고 요리잘하는사람, 설거지잘하는사람을 동시에 상속받으면 별다른 코드 생성없이 기존에 있던 코드를 재활용하여 요리도잘하고 설거지도 잘하는 사람을 구현가능하다

 

 

클래스 생성

dart의 클래스 생성은 new 키워드 생략이 가능하다

아래의 두 코드는 모두 동일하게 클래스를 생성한다.

 

var p1 = Point(2, 2);
var p1 = new Point(2, 2);

 

 

Constant Constructor

몇몇 클래스들은 constant constructor를 지원한다. compile time의 constant 클래스를 만들고 싶다면 생성자 앞에 const 키워드를 붙여주면 된다.

 

 

var p = const ImmutablePoint(2, 2);

동일한 constant를 사용하였다면 동일한 객체를 가지게 된다.

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

 

 

const변수를 생성할때 내용물은 const를 생상할 수 있다. 아래 두 코드는 같은내용이다.

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

위코드는 아래처럼 const를 생략 가능하다

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

 

 

객체의 type을 확인하는법

Object클래스의 프로퍼티인 runtimeType을 이용한다.

print('The type of a is ${a.runtimeType}');

 

 

클래스의 변수선언

nullable 타입변수는 초기값이 null로 세팅되고, non-null 타입변수는 선언과 동시에 초기화 해 주어야 한다.

 

class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}

 

 

Initialize list

constructor보다 앞서서 인스턴스의 변수를 초기화 해 줄 수 있다.

사용법은 c++과 자바처럼 : 을 이용한다.

 

class Point{
  int x;
  int y;
  Point():x=10,y=20{
  }
}


void main() {
  print(Point().x);
  //10 출력
}

 

non-nullable, non-late 변수의 초기화 시점

non-nullable, non-late 변수의 초기화 시점은(위에서 z변수) instance가 생성되는 시점이며, constructor와 initialize list보다 앞선다

 

 

 

 

Constructor

Dart에서 constructor는 두가지가 있다.

  • 다른 언어와 동일하게 클래스이름과 같은 이름을 가진 constructor
  • 클래스와 다른이름을 가진 Named constructor

아래는 기본적인 constructor 예제이다.

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

위 코드는 아래처럼 syntatic sugar로 간략하게 나타낼 수 있다.

심지어 constructor body가 수행되기전에 값을 할당한다.

class Point {
  double x = 0;
  double y = 0;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

 

Name constructor

생성자인데 클래스 이름과 다른 생성자이다. 생성방법은 아래와 같다

 

class.identifier

 

아래 예제는 origin이라는 named constructor를 생성해준 모습이다.

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

 

 

 

기본생성자, 생성자의 상속

다른 언어와 마찬가지로 생성자는 상속이 안되며 생성자를 정의하지 않으면 기본생성자가 생성된다.

 

상속이 안되는건 Named Constructor에도 적용된다.

 

 

 

 

부모클래스의 non-default 생성자 호출

기본적으로 자식클래스의 생성자는 부모생성자의 기본 생성자를 constructor body가 수행되기전에 시작한다.

허나 initializer list는 이 부모생성자보다 더 먼저 실행된다. 순서를 정리하자면 다음과 같다.

 

 

  1. initializer list
  2. 부모클래스의 기본생성자
  3. 자식클래스의 기본생성자

 

 

만약 부모클래스가 unnamed, no-argument 생성자가 없다면, 자식클래스에서 직접 호출을 해주어야 한다

호출방법은 c++과 동일하게 이니셜라이저에 super 키워드로 호출한다.

 

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person 은 기본생성자가 없다
  // super.fromJson(data)를 호출해야한다
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // Prints:
  // in Person
  // in Employee
  // Instance of 'Employee'
}

 

 

 

생성자 Redirection

Dart에선 생성자 redirection이 가능하다.

 

class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

위 예제에서 alongXAxis 생성자는 메인 생성자에 리다이렉션을 이용하여 값을 전달해준다.

 

 

 

 

Constant 생성자

만약 객체를 생성하고 값을 고정하고 싶을경우 constant 생성자를 이용한다.

 

다음과 같은 조건이 있다.

 

  • 모든 변수는 final로 선언되어야 한다
  • 생성자 앞에 const 를 붙여준다
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

 

 

Factory 생성자

자바의 팩토리 메서드 패턴과 비슷하다고 생각하면된다.

사용하는 시기는 클래스를 생성할 때 그 클래스를 항상 생성하지 않을때 사용된다. 리턴형에 factory 키워드를 추가한다.

대표적인 usecase를 말해보면

 

  • 싱글톤패턴
  • 객체들을 캐싱하여 존재하는 클래스 리턴
  • 자식 클래스들을 생성하는 팩토리 메서드

 

 

아래의 예제는 공식 문서에서 나오는 logger 예제이다.

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

 

코드 설명을하자면

 

 static final Map<String, Logger> _cache =
      <String, Logger>{};

static 이고 private(Dart에서는 변수명 앞에 언더바 _ 를 붙여주면 private 이 된다.) 인 Map 클래스 _cache를 생성한다.

 

 

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

 

cache에서 name을 받은뒤에 name key값에 해당하는 클래스가 있으면 그 클래스를 리턴하고 아니면 새로운 클래스를 생성한뒤에 값을 리턴한다.

 

 

 

 

아래의 예제는 자식 클래스를 리턴해주는 factory 예제이다.

class Pizza {
  int price;
  String size;
  
  Pizza(this.price, this.size);
  // named factory 
  factory Pizza.pizzaFactory(int num) {
    switch(num){
      case 1:
        return CheezePizza(100,'L');
        break;
      case 2:
        return PepperoniPizza(200,'S');
      default:
        return Pizza(100,'L');
    }
  }
  printPizzaInfo(){
    switch(this.runtimeType){
      case CheezePizza:
        print("CheezePiza");
        break;
      case PepperoniPizza:
        print("PepperoniPizza");
        break;
      default:
        print("Pizza");
        break;
    }
    print(price);
    print(size);
  }
}

class CheezePizza extends Pizza{
  CheezePizza(int price,String size):super(price, size);
}

class PepperoniPizza extends Pizza{
  PepperoniPizza(int price,String size):super(price, size);
}

void main() {
  Pizza.pizzaFactory(1).printPizzaInfo();
  Pizza.pizzaFactory(2).printPizzaInfo();
  
}

결과

CheezePiza
100
L
PepperoniPizza
200
S

 

 

 

getter와 setter

Dart에서는 getter와 setter를 정의할 때 get, set 키워드를 이용한다.

 

또한 getter와 setter를 통해 새로운 프로퍼티를 만들어 줄 수도 있다.

 

아래 예제는 getter와 setter를 이용해 right와 bottom이라는 변수를 만들어주는 예제이다.

 

class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

 

 

클래스의 상속과 부모클래스의 접근

extends 키워드와 super 키워드를 사용합니다.

 

 

 

Abstract 클래스

abstract 키워드를 이용하여 interface를 구현 할 수 있다.

Abstract 메소드

인스터스의 메소드, getter, setter들은 모드 abstract로 될 수 있다. 선언방법은 함수구현부 대신 세미콜론 ; 을 사용한다.

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

 

 

 

Implicit interface

Dart에서는 모든 클래스가 interface입니다. 고로 interface 키워드가 포함되어 있지 않습니다.

 

상속받은 클래스는 상속받은 부모의 데이터까지 포함하여 interface가 됩니다.

 

interface로 사용하려면 implements 키워드를 이용합니다.

 

// 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()));
}

 

 

 

Overriding annotation

자바와 마찬가지로 함수를 override 할때 @override 어노테이션을 쓸 수 있습니다.

class Television {
  // ···
  set contrast(int value) {...}
}

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

 

 

 

 

 

Extension Method

Dart에 핵심기능중 하나. 공식문서 예제가 사람 참 헤깔리게 적어놔서 이해하는데 한참 걸렸음

 

간단히 말해서 어떤 클래스에 추가로 코드를 주입하는 기능입니다.

 

class A{
}

extension on A{
  extensionPrint(){
    print("It is Extension Method!");
  }
}
void main() {
  A().extensionPrint();
}
It is Extension Method!

사용법은 다음과 같습니다

 

 

extension [extension 네임] on [확장할 클래스]{

    ...

}

 

위 예제에서는 extension 네임이 생략되었습니다. 허나 extension간 충돌이 발생할 수 있으니 extension name을 적어주도록 합니다.

 

 

 

 

 

Extension method간 의 충돌 해결

아래의 코드는 Dart에서 동작하지 않습니다.

'7'.parseInt(); //error!

허나 string api를 import 하면 동작 합니다.

 

import 'string_apis.dart';
print('7'.parseInt()); // Use an extension method.

string_apis.dart에 String 의 extension으로 parseInt가 담겨있기 때문이지요.

 

 

 

 

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
  // ···
}

 

 

 

여기서 API 충돌예시를 들겠습니다.

 

string_apis_2 라는 라이브러리가 있고 이 라이브리러 안에 똑같이 parseInt() 가 extension으로 추가되었다고 가정합니다.

 

그럼 아래 코드는 API 충돌이 날 수 밖에 없습니다. 똑같은 parseInt()가 extension으로 추가되었는데 어떤걸 쓸지 모르기 때문에요.

 

import 'string_apis.dart';
import 'string_apis_2.dart'

print('42'.parseInt()); // 충돌

 

 

해결방법은 다음과 같습니다.

 

 

1. hide 키워드 사용

 

hide 키워드를 이용하여 충돌나는 extension들중 하나를 가립니다.

import 'string_apis.dart';
import 'string_apis_2.dart' hide NumberParsing2;

print('42'.parseInt()); // string_apis.dart 의 extension이 사용된다.

 

2. extension 이름을 명시적으로 지정

 

아래 코드에서 첫번째 라이브러리의 익스텐션 이름은 NumberParsing이고 두번째는 NumberParsing2라고 합시다.

이를 명시적으로 지정해줘 extension을 사용하면 충돌을 막을 수 있습니다.

 

import 'string_apis.dart';
import 'string_apis_2.dart';

print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());

// print('42'.parseInt()); // API 충돌!

 

3. prefix사용

만약 extension의 이름까지 같다면 prefix를 사용하여 충돌을 막을 수 있습니다.

파이썬처럼 import에 as를 사용해 줍시다.

import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

// print('42'.parseInt()); // API 충돌
print(NumberParsing('42').parseInt());
print(rad.NumberParsing('42').parseInt());

// API3이 가지고있는 extension. prefix로 import 했지만 implicitly하게 사용 가능하다!
print('42'.parseNum());

마지막줄을 보시면 rad 없이 extension을 호출하고 있습니다. prefix로 import 하여도 묵시적으로 extension이 사용가능함을 보여줍니다.

 

 

 

 

 

 

show 키워드

앞서 hide라는 키워드로 특정 라이브러리를 import 할 때 일부 기능을 가릴수 있었습니다.

show 키워드는 정 반대입니다. 특정 라이브러리에서 내가 원하는 부분만 import 할 수 있습니다.

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

 

 

 

 

 

 

Mixin 클래스란

클래스코드를 다중상속을 통하여 재활용 할 수 있도록 만든 기능이다.

 

Mixin 생성 및 사용법

  • 재사용될 클래스 코드는 생성자를 정의하면 안된다
  • mixin으로 사용될 클래스의 생성을 원하지 않을경우 mixin 키워드를 사용한다
  • mixin 클래스는 with 키워드로 상속해준다.
class Person{
  int age;
  String name;
  Person(this.age, this.name);
}

class Singer{
  String? song;
}

mixin Dancer{
  String? dance;
}
class Idol extends Person with Singer, Dancer {
  
  Idol(int age,String name,[String? song, String? dance]):super(age, name){
    
    this.song = song??"no song";
    this.dance = dance??"no dance";
    
  }
  playSingAndDance(){
    print(this.song);
    print(this.dance);
  }
}


void main() {
  Idol idol = Idol(20, "K-Idol", "k-pop","k-dance");
  idol.playSingAndDance();
  
  Singer();// 생성가능하나 생성자를 정의하면 mixin으로 사용 불가
  //Dancer();// mixin은 생성 불가능!
}

 

on 키워드

 

만약 mixin 클래스를 특정 클래스에만 적용하고 싶다면 on 키워드를 사용한다.

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

 

 

Static Variable

java에서는 static variable은 선언과 동시에 초기화가 되지만 Dart에서는 static variabled이 사용될 때 초기화 된다.

 

 

Static Method

static 메소드는 컴파일시점에서 constan가 된다. 고로 static 메소드를 constant constructor에 넘겨줄수도 있다.

 

 

 

 

 

제네릭 Generics

 

Dart에서도 제네릭을 지원한다. 사용방법은 아래코드를 보면 이해할 수 있다.

 

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

 

 

 

제네릭을 이용한 type 강제화

List로 예를 들어보자. Dart의 List는 파이썬의 List처럼 여러가지 자료형을 담을 수 있다.

void main() {
  var mylist = [];
  int x =10;
  mylist.addAll(['Seth', 'Kathy', 'Lars']);
  mylist.add(x);
  for(var i in mylist){
    print(i.runtimeType);
  }
}
String
String
String
int

 

 

허나 아래처럼 제네릭을 이용하면 리스트에 특정 자료형만 담도록 강제화 시켜줄 수 있다.

var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
//names.add(42); // Error

 

 

 

 

특정 자료형만 generic으로 사용하기

 

extends 키워드를 이용하면 특정 자료형만 generic키워드에 들어 갈 수 있다.

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}
var someBaseClassFoo = Foo<SomeBaseClass>();  //OK
var extenderFoo = Foo<Extender>();  //
//var foo = Foo<Object>(); //error

 

위 예제에서 제네릭으로 SomeBaseClass와 이를 상속받아 다형성을 구현할 수 있는 클래스만 쓸수 있도록 강제화 시켜 줄 수 있다

 

 

 

Generic 함수

Dart에서는 Generic 함수도 지원한다. 함수의 리턴형, 인풋, argument에 사용할 수 있다.

T first<T>(List<T> ts) {
  T tmp = ts[0];
  return tmp;
}

 

 

Callable class, 클래스를 함수처럼 쓰기

클래스를 함수처럼 쓰고싶다면 call() 함수를 implement 하면 된다.

 

 

class WannabeFunction {
  String call(String a, String b, String c) => '$a $b $c!';
}

var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');

void main() => print(out);

 

반응형

'Dart > 기초' 카테고리의 다른 글

[Dart] var과 dynamic  (0) 2021.10.15
[Dart] mixin 클래스  (0) 2021.10.15
[Dart] Dart 배우기  (0) 2021.10.13
[Dart] 리스트이어 붙이기. spread operator ...  (0) 2021.07.26
Dart의 선택적 매개변수  (0) 2021.07.20

댓글