Dart 튜토리얼 7편: 클래스 설계 기본(Classes·Constructors·Methods·Extend)
요약
핵심 요지
문서가 설명하는 범위
class와 인스턴스 변수, 클래스 멤버의 기본 규칙constructor로 필드를 초기화하는 방법과 형태method/getter/setter및operator9의 사용법extends로 상속하고super와override로 확장하는 방식
읽는 시간: 17분 | 난이도: 초급
참고 자료
- Classes - classes, instance variables, class members
- Constructors - constructors, initializing fields, named constructors
- Methods - instance methods, getters/setters, operators, abstract methods
- Extend a class - extends, overriding, super, noSuchMethod
문제 상황
클래스는 데이터를 담는 그릇이면서, 동시에 그 데이터로 무엇을 할지 정의하는 설계 단위입니다.
입문자 입장에서는 “어디까지를 필드10로 두고, 어디부터를 메서드로 만들지”가 가장 헷갈립니다.
인스턴스11를 만들 때 생성자를 어디에 두고 어떤 값을 받을지도 함께 고민해야 합니다.
이 혼란을 줄이려면, 역할을 단계별로 나눠 생각하는 것이 좋습니다.
먼저 class로 형태를 만들고, constructor로 초기화 규칙을 고정합니다.
그 다음 method/getter/setter로 외부에서 쓰기 좋은 API를 만듭니다.
마지막으로 extends와 super로 공통 동작을 재사용합니다.
해결 방법
클래스 설계를 4단계로 정리합니다.
각 단계는 “코드를 어떻게 쓰는지”뿐 아니라 “왜 분리하는지”도 함께 설명합니다.
단계 1: class로 데이터와 책임을 묶기
Why
NOTE관련된 값이 흩어져 있으면, 전달할 때 순서를 틀리거나 누락하기 쉽습니다.
그래서 “의미가 있는 데이터 묶음”을 하나의 타입으로 고정하는 것이 필요합니다.// (x, y)를 따로 전달하면 순서를 바꿔 넣는 실수가 생길 수 있다.void move(double x, double y) {}
What
NOTE
class는 데이터(필드)와 동작을 묶어, 한 덩어리로 다루게 하는 타입입니다.
How
TIP
class는 관련된 데이터와 동작을 묶는 단위입니다.
기본 예시는 좌표를 나타내는 타입처럼, 의미가 있는 데이터 묶음을 클래스 하나로 만드는 흐름입니다.
필드는인스턴스가 가진 상태를 표현하고, 생성자는 그 상태를 만들어 줍니다.class Point {final double x;final double y;Point(this.x, this.y);}
Watch out
WARNING
final필드는 생성 시점에 반드시 값이 정해져야 합니다.
따라서 생성자에서 값을 받거나, 초기값/초기화 목록으로 값을 넣어야 합니다.class Point {final double x;final double y;// Point(); // 컴파일 오류: final 필드가 초기화되지 않았다.Point(this.x, this.y);}
결론: 의미 있는 데이터 묶음이 한 타입으로 고정되어, 전달과 재사용이 쉬워집니다.
단계 2: constructor로 초기화 규칙을 고정하기
Why
NOTE인스턴스를 만든 뒤에 필드를 하나씩 채우기 시작하면, “중간 상태”가 생깁니다.
그래서 생성 단계에서 초기화 규칙을 한 번에 고정하는 편이 안전합니다.class User {String name;int age;User(this.name, this.age);}
What
NOTE
constructor는 인스턴스를 만들면서 필드를 초기화하는 규칙입니다.
How
TIP
constructor는 인스턴스를 만들 때 “필드를 어떤 규칙으로 채울지”를 정합니다.
기본 생성자, 이름 있는 생성자, 그리고 초기화 목록을 사용한 패턴을 살펴보면 흐름이 잡힙니다.
입문자 관점에서는 “생성자에서 필드를 다 채운다”는 원칙만 먼저 잡아도 사고가 줄어듭니다.class Person {final String name;final int age;Person(this.name, this.age);Person.teen(this.name) : age = 10;}
Watch out
WARNING필드를 “나중에 채우겠다”는 방식은 실수를 만들기 쉽습니다.
특히final필드는 생성자에서 초기화가 끝나야 하므로, 생성자/초기화 목록으로 규칙을 고정하는 편이 안전합니다.
결론: 인스턴스가 만들어진 직후의 상태가 예측 가능해지고, 필드 초기화가 흩어지지 않습니다.
단계 3: method/getter/setter로 외부 API를 설계하기
Why
NOTE외부에 그대로 필드를 노출하면, 어디서 어떤 값이 들어오는지 통제하기 어렵습니다.
그래서 “읽기/쓰기/동작”을 각각의 멤버로 분리해 의도를 드러내는 편이 좋습니다.class Temperature {double celsius;Temperature(this.celsius);}
What
NOTE
getter/setter는 필드처럼 보이지만, 내부 규칙(계산/검증/변환)을 넣을 수 있는 멤버입니다.
How
TIP동작은 세 가지로 나눠 생각하면 정리됩니다.
method는 “호출해서 실행하는 동작”이고,getter는 “값처럼 읽히는 계산”입니다.
setter는 “대입을 받는 동작”이라서, 내부 규칙(검증/변환)을 한 곳에 모을 수 있습니다.class Rectangle {double left;double top;double width;double height;Rectangle(this.left, this.top, this.width, this.height);double get right => left + width;set right(double value) => left = value - width;}같은 클래스에서
method를 추가해 “행동”도 표현할 수 있습니다.class Rectangle {double left;double top;double width;double height;Rectangle(this.left, this.top, this.width, this.height);double get right => left + width;set right(double value) => left = value - width;void moveBy(double dx, double dy) {left = left + dx;top = top + dy;}}
Watch out
WARNING
setter에서 자기 자신을 다시 대입하면 무한 재귀가 될 수 있습니다.
즉,right = value;같은 대입을set right(...)안에서 다시 호출하지 않도록 주의해야 합니다.class Bad {double _right = 0;double get right => _right;// set right(double value) => right = value; // 위험: setter가 자기 자신을 다시 호출한다.set right(double value) => _right = value;}
결론: 외부에서는 값처럼, 내부에서는 규칙처럼 동작을 정리해 API12를 읽기 쉽게 만듭니다.
단계 4: extends로 공통 동작을 재사용하기
Why
NOTE공통 동작이 여러 곳에 복사되면, 수정할 때 누락이 생기기 쉽습니다.
그래서 “공통 부분은 한 곳에 모으고”, 다른 부분만 덮어쓰는 방식이 필요합니다.class Animal {void speak() {}}
What
NOTE
extends로 상속 관계를 만들고, 필요한 동작만override로 바꿉니다.
How
TIP
extends는 기존 클래스를 기반으로 새 클래스를 만드는 문법입니다.
상속은 “공통 동작을 재사용하고, 필요한 부분만 확장”하는 방식으로 이해하면 됩니다.
이때 부모 생성자 호출과super의 역할이 함께 등장합니다.class Animal {final String name;Animal(this.name);void speak() {print('$name makes a noise.');}}class Dog extends Animal {Dog(super.name);@overridevoid speak() {print('$name barks.');}}
Watch out
WARNING
override는 동작을 바꾸는 것이므로, 기본 동작까지 유지해야 한다면super호출을 포함해야 합니다.class Dog extends Animal {Dog(super.name);@overridevoid speak() {super.speak();print('$name barks.');}}
결론: 공통 로직은 부모에 두고, 차이점만 override로 바꿔 유지보수를 단순하게 만듭니다.
Footnotes
-
class(클래스): 데이터와 동작을 한 타입으로 묶는 정의 단위다. ↩
-
constructor(생성자): 인스턴스를 만들 때 필드를 초기화하는 특별한 멤버다. ↩
-
method(메서드): 객체에 속한 동작을 정의하는 함수다. ↩
-
getter(게터): 필드처럼 읽히지만 실제로는 값을 반환하는 메서드다. ↩
-
setter(세터): 필드처럼 대입되지만 실제로는 값을 받아 상태를 바꾸는 메서드다. ↩
-
extends(익스텐즈): 다른 클래스를 상속받아 새 클래스를 만드는 키워드다. ↩
-
super(수퍼): 상위 클래스의 생성자나 멤버를 가리키는 키워드다. ↩
-
override(오버라이드): 상위 클래스의 멤버를 하위 클래스에서 재정의하는 표시다. ↩
-
operator(연산자):
+같은 연산 기호로 동작을 정의하는 문법 요소다. ↩ -
field(필드): 클래스가 가지는 데이터 항목으로, 인스턴스의 상태를 나타낸다. ↩
-
instance(인스턴스): class로부터 만들어진 실제 객체다. ↩
-
API(API): 외부에서 사용할 수 있도록 공개한 사용 방법의 모음이다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!