class 정의 / 생성자
void main() { Idol blackPink = Idol('블랙핑크', ['지수', '제니', '리사', '로제']); Idol bts = Idol.fromList([ ['RM','진','슈가','제이홉','지민','뷔','정국'], 'BTS' ]); blackPink.sayHello(); bts.sayHello(); } class Idol { String name; // this.name List<String> members; // this.members //constructor (positional params) Idol(String name, List<String> members) : this.name = name, this.members = members;// constructor 축약 형식으로 가능 >> Idol(this.name,this.members); // 생성자를 다른 형식으로 사용해도 됨 Idol.fromList(List value) : this.members = value[0], this.name = value[1]; void sayHello() { print('안녕하세요 ${this.name} 입니다.'); } }
class 를 인스턴스로 선언하고 나서, 추후 값을 변경할 수 없도록 만드는게 좋다. final 로 선언하지 못하는 상황을 제외하고 대부분의 상황에서는 버그를 줄이기 위해 final로 변수를 선언하는게 좋으므로.. 습관을 들이자
void main() { Idol blackPink = Idol('블랙핑크', ['지수', '제니', '리사', '로제']); Idol bts = Idol.fromList([ ['RM','진','슈가','제이홉','지민','뷔','정국'], 'BTS' ]); blackPink.name = 'dylan'; // error blackPink.sayHello(); bts.sayHello(); } class Idol { final String name; // this.name final List<String> members; // this.members //constructor (positional params) Idol(String name, List<String> members) : this.name = name, this.members = members;// constructor 축약 형식으로 가능 >> Idol(this.name,this.members); // 또 다른 constructor 형식 Idol.fromList(List value) : this.members = value[0], this.name = value[1]; void sayHello() { print('안녕하세요 ${this.name} 입니다.'); } }
혹은 const 를 이용하여 값을 변경하지 못하게 하는 방법도 있다.
void main() { Idol blackPink = const Idol('블랙핑크', ['지수', '제니', '리사', '로제']); Idol blackPink2 = const Idol('블랙핑크', ['지수', '제니', '리사', '로제']); print(blackPink == blackPink2); //true } class Idol { final String name; // this.name final List<String> members; // this.members //constructor (positional params) const Idol(this.name, this.members); }
const 로 인스턴스를 만들면 두개의 인스턴스는 같은 것으로 취급한다. (const 가 아니면, 내부 데이터가 동일하더라도 다른 것으로 취급)
getter / setter
void main() { Idol blackPink = Idol('블랙핑크', ['지수', '제니', '리사', '로제']); print(blackPink.firstMember);// 지수 blackPink.changeMember = ['dylan','IT']; // setter print(blackPink.firstMember); //getter (dylan) } class Idol { final String name; // this.name final List<String> members; // this.members //constructor (positional params) Idol(this.name, this.members); //getter String get firstMember { // String 을 리턴해주는 getter 정의 return this.members[0]; } //setter : 무조건 하나의 파라미터만 받을수있음 set changeMember(List<String> members) { this.members = members; // error (members 가 final 로 지정되어있기때문) } }
굳이 getter 을 사용하는 이유는… class 내에 메서드를 만드는것과 기능적인 차이는 없으나, 뉘앙스의 차이정도로 보면 되겠다.
setter는 현대 프로그래밍에선 잘 쓰지 않는다. 왜냐하면 일반적으로 final (값이 변경되지않도록 / 무결성을 유지) 로 정의하는데 setter 를 사용하여 값을 변경하면, 원래 의도와는 어긋나기 때문이다.
private
private 는 해당 코드의 파일 밖에서 사용할 수 없게 만들기 위해 존재함. 외부 파일에서 해당 파일을 import 하더라도, private 으로 선언된 class를 사용할 수가없다. 변수나 함수도 당연히 private 으로 선언가능
void main() { _Idol blackPink = _Idol('블랙핑크', ['지수', '제니', '리사', '로제']); } class _Idol { final String name; // this.name final List<String> members; // this.members //constructor (positional params) _Idol(this.name, this.members); }
inheritance (상속)
void main() { Idol apink = Idol(name: '에이핑크', membersCount: 5); BoyGroup bts = BoyGroup('BTS', 7); bts.sayName(); bts.sayMembersCount(); bts.sayMale(); GirlGroup redVelvet = GirlGroup('Red Velvet', 5); redVelvet.sayName(); redVelvet.sayMembersCount(); redVelvet.sayFemale(); // Type Comparison print(apink is Idol); //true print(apink is BoyGroup); //false print(apink is GirlGroup); //false // Type Comparison 2 print(bts is Idol); //true print(bts is BoyGroup); //true print(bts is GirlGroup); //false } class Idol { String name; int membersCount; //named parameters' constructor Idol({required this.name, required this.membersCount}); void sayName() { print('저는 ${this.name} 입니다.'); } void sayMembersCount() { print('${this.name} 은 ${this.membersCount} 명의 멤버가 있습니다.'); } } // 상속 // 부모 클래스의 모든 속성을 자식 클래스가 부여받음 class BoyGroup extends Idol { BoyGroup( String name, // BoyGroup 의 변수 int membersCount, // BoyGroup 의 변수 ) : super( name: name, // 받은 파라미터를 부모 클래스인 Idol 생성자로 전달 membersCount: membersCount, // 받은 파라미터를 부모 클래스인 Idol 생성자로 전달 ); void sayMale() { print('we are boy group'); } } class GirlGroup extends Idol { GirlGroup( String name, // GirlGroup 의 변수 int membersCount, // GirlGroup 의 변수 ) : super( name: name, // 받은 파라미터를 부모 클래스인 Idol 생성자로 전달 membersCount: membersCount, // 받은 파라미터를 부모 클래스인 Idol 생성자로 전달 ); void sayFemale() { print('we are girl group'); } }
method override
void main() { TimesTwo tt = TimesTwo(2); print(tt.calculate()); TimesFour tf = TimesFour(2); print(tf.calculate()); } // method override class TimesTwo { final int number; TimesTwo(this.number); int calculate() { return this.number * 2; } } class TimesFour extends TimesTwo { TimesFour( int number, ) : super(number); @override int calculate() { return super.number * 4; // this.number 도 동일함 (어차피 number로 받아온 값을 부모 클래스 number에 넣어주므로) return super.calculate() * 2; // 이것도 가능 } }
static
void main() { Employee dylan = Employee('dylan'); Employee chorong = Employee('chorong'); dylan.name = 'dylan2'; // 인스턴스에 귀속됨 dylan.printNameAndBuilding(); // 인스턴스에 귀속됨 chorong.printNameAndBuilding(); // 인스턴스에 귀속됨 Employee.building = 'AP tower'; // class에 귀속, instance를 만들지 않아도 static으로 선언된 building 호출 가능 dylan.printNameAndBuilding(); // 다른 instance 임에도 building이 AP tower로 찍힘 chorong.printNameAndBuilding(); Employee.printBuilding(); // class에 귀속 } class Employee { // static은 instance 에 귀속되지 않고 class에 귀속된다. // 인스턴스를 생성해도 각 인스턴스별 특정 변수에 동일한 값을 귀속시키고 싶다면, static을 사용하자 static String? building; String name; Employee(this.name); void printNameAndBuilding() { print('제 이름은 $name 입니다. $building 건물 에서 근무하고 있습니다.'); } static void printBuilding(){ print('저는 $building 건물에서 근무하고 있습니다.'); } }
interface
void main(){ BoyGroup bts = BoyGroup('BTS'); GirlGroup redVelvet = GirlGroup('레드벨벳'); bts.sayName(); redVelvet.sayName(); IdolInterface test = IdolInterface('test'); //error (abstract 이므로) print(bts is IdolInterface); //true } // interface (dart는 class를 사용하여 구현함) // 인터페이스를 만들면 협업시 특정 클래스와 동일한 포맷을 따르도록 강제할수있음 // 인터페이스로써 만들어진 class는 instance화 하라고 만든게 아니기때문에.. // 누군가 실수로 instance 로 만들지 않게 하기위해, /* * interface (dart는 class를 사용하여 구현함) * 인터페이스를 만들면 협업시 특정 클래스와 동일한 포맷을 따르도록 강제할 수 있다. * 인터페이스로써 만들어진 class는 instance를 생성하기 위한 용도가 아님 * 누군가 실수로 instance 를 만들지 않게 하기위해, abstract를 붙여, 해당 클래스를 이용해 instance 를 생성하는것을 막는다. * */ abstract class IdolInterface{ String name; IdolInterface(this.name); void sayName(); } class BoyGroup implements IdolInterface{ String name; BoyGroup(this.name); void sayName(){ print('제 이름은 $name 입니다'); } } class GirlGroup implements IdolInterface{ String name; GirlGroup(this.name); void sayName(){ print('제 이름은 $name 입니다'); } }
generic
void main(){ List<String> names = []; // 이것도 generic 임. List에 String 타입이 들어갈거라고 외부에서 정의한것 Lecture<String,String> lecture1 = Lecture('123','lecture1'); // String값을 넣겠다고 정의 Lecture<int,String> lecture2 = Lecture(123,'lecture2'); lecture1.printIdType(); // String lecture2.printIdType(); // int } /* * generic - 타입을 외부에서 정의 받을 때 사용 * */ class Lecture<T,X> { final T id; final X name; Lecture(this.id,this.name); void printIdType(){ print(id.runtimeType); } }
모든 클래스들이 부모 클래스 (Object)를 상속받고 있음 (extends Object)는 생략된것.
그래서 인스턴스를 생성하자마자 사용할 수 있는 메서드들이 보인다.