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] 다양한 Contructor (생성자) 본문

Programming/Dart

[Programming/Dart] 다양한 Contructor (생성자)

scii 2020. 10. 13. 15:57

이름 있는 생성자 (Named Constructor)

이름 있는 생성자는 말 그대로 생성자에 이름을 부여한 것이다. 한 클래스 내에 많은 생성자를 생성하거나 생성자를 명확히 하기 위해서 사용할 수 있다.

class 클래스명 {
  클래스명.생성자명() {
  }
}

class Person {
  Person.init() {
  }
}

또한 이름 있는 생성자는 여러 생성자를 만들거나 생성자 내에서 값 체크 및 파싱 등 각종 작업을 할 때 유용하게 쓰인다. 다음의 예제를 살펴보자.

class Point {
  double x, y;

  // 일반적인 생성자
  Point(this.x, this.y);

  // 이름있는 생성자
  Point.origin()
      : x = 0,
        y = 0;

  // 이름있는 생성자
  Point.fromJson(Map<String, double> json)
      : x = json['x'],
        y = json['y'] {
    print('Point.fromJson: ($x, $y)');
  }
}

void main() {
  Point point1 = new Point(5, 8);
  print('point1 -> (${point1.x}, ${point1.y})');
  print('');

  Point point2 = new Point.origin();
  print('point2 -> (${point2.x}, ${point2.y})');
  print('');

  Map<String, double> json = <String, double>{};
  json['x'] = 5.5;
  json['y'] = 15.2;
  Point point3 = new Point.fromJson(json);
  print('point3 -> (${point3.x}, ${point3.y})');
}

/* 결과

point1 -> (5.0, 8.0)

point2 -> (0.0, 0.0)

Point.fromJson: (5.5, 15.2)
point3 -> (5.5, 15.2)

*/
이름 없는 생성자는 단 하나만 가질 수 있다. 또한 이름 있는 생성자를 선언하면 기본 생성자는 생략할 수 없다.

Point.origin() 은 이름 있는 생성자로서 x, y 변수를 0으로 초기화하는 역할을 수행한다. 그리고 Point.fromJson() 도 마찬가지로 이름 있는 생성자이며, 이 생성자는 json 형태의 Map 자료구조를 매개변수로 받아 변수 x, y를 초기화하는 생성자이다.

이처럼 이름 있는 생성자는 유용하게 사용된다. 또한 이름이 존재하여 생성자 오버로딩보다 코드의 가독성이 좋아진다.

참고로 ':' 키워드는 생성과 동시에 멤버 변수를 초기화하는데 쓰이는 키워드이다. 다음의 예제를 보자.

생성자 : 초기화 리스트 {
}

Person() : name = 'john', age = 18 {
}

대리 생성자 (Delegate Constructor)

생성자 호출 시 대리로 매개변수를 받아 다른 생성자를 호출한다. 쉽게 생각할 수 있는 것이이게 짧게 설명을 한다.

class Robot{
  final double height;
  Robot(this.height);

  Robot.fromPlanet(String planet) : height = (planet == 'geometry') ? 2 : 7;

  // 이 생성자가 호출되면 위의 Robot(this.height) 생성자가 호출된다. 즉, 이 생성자는
  // 대리 생성자이다.
  Robot.copy(Robot other) : this(other.height);
}

void main(){
  print(new Robot.copy(Robot(5)).height);
  print(new Robot.fromPlanet('geometry').height);
  print(new Robot.fromPlanet('primitive').height);
}

/* 결과

5.0
2.0
7.0

*/

상수 생성자 (Const Constructor)

상수 생성자는 말 그대로 생성자를 상수처럼 만들어 준다. 이 말은 해당 클래스가 상수처럼 변하지 않는 객체를 생성한다는 것이다. 상수 생성자를 만들기 위해서는 인스턴스 변수가 모두 final로 선언되어야 한다. 또한 생성자는 const 키워드가 붙어야 한다. 다음의 예제를 보자.

class Person {
  final String name;
  final int age;

  // const Person(this.name, this.age); 아래와 명령과 똑같은 의미다.
  const Person(String name, int age)
      : this.name = name,
        this.age = age;
}

void main() {
  Person person1 = const Person('john', 24);
  Person person2 = const Person('john', 24);
  Person person3 = new Person('john', 24);
  Person person4 = new Person('john', 24);

  print(identical(person1, person2));
  print(identical(person2, person3));
  print(identical(person3, person4));
}

/* 결과 

true
false
flase

*/

identical() 메소드는 매개변수로 들어온 객체가 같은 인스턴스인지 비교해주는 메소드다. person1과 person2는 상수 생성자를 참조하고 있다. 따라서 동일한 인스턴스를 참조하고 있기 때문에 true가 된다. 그래서 Flutter 프레임워크를 사용하다보면 "const EdgeInset.all(5.0)" 이러한 코드를 볼 수 있다. 이것이 바로 상수 생성자이다. 메모리의 효율을 높여준다.
person3과 person4는 각각 새로운 인스턴스를 생성했기 때문에 동일한 인스턴스를 가지지 않는다. 어떠한 방식으로 인스턴스를 참조하고 있는지 아래의 그림을 보면 알 수 있다.

상수 생성자 인스턴스 접근 모습


팩토리 생성자 (Factory Constructor)

팩토리 생성자는 팩토리 패턴을 쉽게 쓰기 위해 만들어졌다. 팩토리 패턴을 사용하면 해당 클래스의 인스턴스를 매번 생성하지 않아도 된다. 보통 자식 클래스의 인스턴스를 반환 받는다.

enum BookType { Fiction, Essay }

abstract class Book {
  String getType() {
    return 'Book';
  }

  String getTitle();

  int getPage();

  // 팩토리 생성자
  factory Book(BookType type) {
    switch (type) {
      case BookType.Fiction:
        return new FictionBook();
      case BookType.Essay:
        return new EssayBook();
      default:
        return null;
    }
  }
}

class FictionBook implements Book {
  @override
  int getPage() {
    return 355;
  }

  @override
  String getTitle() {
    return 'About Time';
  }

  @override
  String getType() {
    return 'Fiction';
  }
}

class EssayBook implements Book {
  @override
  int getPage() {
    return 180;
  }

  @override
  String getTitle() {
    return 'World Coder';
  }

  @override
  String getType() {
    return 'Essay';
  }
}

void main() {
  Book fictionBook = new Book(BookType.Fiction);
  Book essayBook = new Book(BookType.Essay);

  print('제목: ${fictionBook.getTitle()}, '
      '페이지: ${fictionBook.getPage()}, '
      '장르: ${fictionBook.getType()}');
  print('제목: ${essayBook.getTitle()}, '
      '페이지: ${essayBook.getPage()}, '
      '장르: ${essayBook.getType()}');
}

/* 결과

제목: About Time, 페이지: 355, 장르: Fiction
제목: World Coder, 페이지: 180, 장르: Essay

*/

factory 키워드를 쓰면 Singleton 패턴이나 Factory 패턴으로 객체를 작성할 때 유용하다. 다음은 Factory 패턴의 또 다른 예제 코드이다.

class PositivePoint extends Point {
  double x, y;

  PositivePoint(this.x, this.y) : super.init();

  @override
  void printPoints() {
    print('x: $x, y: $y');
  }
}

class NegativePoint extends Point {
  double x, y;

  NegativePoint(this.x, this.y) : super.init();

  @override
  void printPoints() {
    print('x: $x, y: $y');
  }
}

abstract class Point {
  Point.init();

  void printPoints();

  factory Point(double x, double y) {
    if ((x < 0) || (y < 0)) {
      return NegativePoint(x, y);
    }
    return PositivePoint(x, y);
  }
}

void main() {
  Point p1 = Point(3, 5);   // PositivePoint instance
  Point p2 = Point(-1, 2);  // NegativePoint instance

  p1.printPoints();
  p2.printPoints();
  print('p1 -> $p1, p2 -> $p2');
}

/* 결과

x: 3.0, y: 5.0
x: -1.0, y: 2.0
p1 -> Instance of 'PositivePoint', p2 -> Instance of 'NegativePoint'

*/
Comments