Dart – OOP

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)는 생략된것.

그래서 인스턴스를 생성하자마자 사용할 수 있는 메서드들이 보인다.