Dart | Dart #2 OOP : 객체지향 프로그래밍

앙두·2023년 7월 5일
0

for My Programming

목록 보기
11/20

Dart : 객체지향 프로그래밍

인프런 무료 강의
https://www.inflearn.com/course/lecture?courseSlug=dart-%EC%96%B8%EC%96%B4-%EC%9E%85%EB%AC%B8&unitId=107600
DartPad
https://dartpad.dev/

왜 객체지향 프로그래밍일까?

객체지향 프로그래밍 (OOP : Object Oriented Programming)

void main(){
  Test test = Test();
}

class Test {} // (1)
class Test extends Object {} // (2)

간단히 말하자면,
(1)(2) 는 똑같다.
extends Object 가 생략된 것. (부모 클래스인 Objectextends 된 사실)


☝🏻 그래서 Object가 가지고 있는 기본적인 속성 4가지를
기본으로 탑재하고 있다고 생각하면 된다.


부모 Class

  • 부모 ClassIdol 생성
  • Immutable 프로그래밍 (밖에서 값을 바꿀 수 없게 함) - final / const 사용
class Idol {
  final String name;
  final List<String> members;

  // constrouctor (생성자)
  const Idol(String name, List<String> members)
     : this.name = name,
       this.members = members;

  void sayHello() => print('우리는 ${this.name}');
  void introduce() => print('안녕하세요 저희는 ${this.name}입니당!');
}

class를 상속받아 instance를 만들어보자.

void main() {
  // const constructor 로 immutable 하게! 효율을 높여준대 (이유는 Flutter 에서)
  // const 로 선언하면, 같은 주소값을 참조해서 값이 엄격일치됨! 같은 Instance가 됨
  Idol winner = const Idol('위너', ['진우', '민호', '승훈', '승윤']);
 
  winner.sayHello(); // 우리는 위너
  winner.introduce(); // 안녕하세요 저희는 위너입니당!
  
  Idol blackpink = Idol.fromList([['제니','리사','로제','지수'], '블랙핑크']);
  
  blackpink.sayHello();
  blackpink.introduce();
}
  • instance를 생성할 때, new Idol() 이런식으로 생성자 함수를 쓰는데, Dart 에서는 'new' 생략 가능!
  • 부모 class에서 immutable(final/const)로 선언했다면, instance에서도 const로 선언해야 함
  • const로 선언하면 같은 주소값을 참조해서 값이 엄격일치된다.

constructor 는 이러한 방식으로 만들 수도 있다. 👇🏻

// constrouctor (생성자)
  const Idol(String name, List<String> members)
     : this.name = name,
       this.members = members;
       
// 👇🏻 위 방법보다, 더 간결한 방법
// 이렇게 써도 됨 (이미 타입은 위에 지정하기 때문)
  const Idol(this.name, this.members);
  
// 이런 방법도 있음
// 여기엔 const 를 안붙였으므로, 생성할 때 const 붙이면 에러남
  Idol.fromList(List values):
  this.members = values[0], this.name = values[1];
void main() {
  Idol winner = const Idol('위너', ['진우', '민호', '승훈', '승윤']);
 
  winner.sayHello(); // 우리는 위너
  winner.introduce(); // 안녕하세요 저희는 위너입니당!
  
  Idol blackpink = Idol.fromList([['제니','리사','로제','지수'], '블랙핑크']);
  
  blackpink.sayHello(); // 우리는 블랙핑크
  blackpink.introduce(); // 안녕하세요 저희는 블랙핑크입니당!
}

private

  • private 속성을 부여하고 싶다면, 앞에 underscore(_) 붙여주자.
    => 현재 파일에서만 사용할 수 있고, 다른 외부에서 import 해도 사용할 수 없음

getter / setter

  • getter : 데이터 가져올 때
  • setter : 데이터 설정할 때
// 부모 class 선언
class _Idol {
  String name;
  List<String> members;

  _Idol(this.name, this.members);

  void sayHello() => print('우리는 ${name}');
  void introduce() => print('안녕하세요 저희는 ${name}입니당!');

  // getter
  String get firstMember {
    return members[0];
  }

  // setter
  set firstMember(String name) {
    members[0] = name;
  }
}
void main() {
  _Idol winner = _Idol('위너', ['진우', '민호', '승훈', '승윤']);

  winner.firstMember = '앙두';
  print(winner.firstMember); // 앙두
  print(winner.members); // [앙두, 민호, 승훈, 승윤]
}
  • setter 로 데이터를 설정하여, getter 로 설정한 데이터를 가져온다.
  • 데이터가 바뀌었다.
  • 사실 getter / setter 잘 이해안됐다..

Inheritance (상속)

  • 상속을 받으면 부모 class 의 모든 속성을
  • 자식 class 가 모두 부여받는다.
class Idol {
  String name;
  int membersCnt;

  // Constructor
  // named parameter
  Idol({
    required this.name,
    required this.membersCnt,
  });

  void sayName() => print('우리는 ${this.name}!');
  void sayCnt() => print('우리는 ${this.membersCnt}명의 멤버가 있어!');
}

// 클래스 BoyGroup은 Idol의 속성들을 모두 상속받아 사용할 수 있다.
class BoyGroup extends Idol {
  // 부모 클래스를 지칭하는 건 'super'
  // 부모의 constructor 와 일치시켜줘야 한다
  // positional parameter
  BoyGroup(String name, int membersCnt)
      : super(name: name, membersCnt: membersCnt);

  void sayMale() => print('(남자 아이돌)');
}

// 클래스 GirlGroup Idol 의 속성들을 모두 상속받아 사용할 수 있다.
class GirlGroup extends Idol {
  // 부모 클래스를 지칭하는 건 'super'
  // 부모의 constructor 와 일치시켜줘야 한다
  // positional parameter
  GirlGroup(String name, int membersCnt)
      : super(name: name, membersCnt: membersCnt);

  void sayFemale() => print('(여자 아이돌)');
}
void main() {
  Idol winner = Idol(name: '위너', membersCnt: 5);

  print('========winner=======');
  winner.sayName(); // 우리는 위너!
  winner.sayCnt(); // 우리는 5명의 멤버가 있어!

  BoyGroup bigbang = BoyGroup('빅뱅', 4);

  print('========bigbang=======');
  bigbang.sayName(); // 우리는 빅뱅!
  bigbang.sayCnt(); // 우리는 4명의 멤버가 있어!
  bigbang.sayMale(); // (남자 아이돌)

  GirlGroup sistar = GirlGroup('씨스타', 4);

  print('========sistar=======');
  sistar.sayName(); // 우리는 씨스타!
  sistar.sayCnt(); // 우리는 4명의 멤버가 있어!
  sistar.sayFemale(); // (여자 아이돌)
  
  print('=======Type Comparison=======');
  // Idol을 상속받은 자식 클래스는
  // 자기자신이 될 수도 있고, 부모 클래스가 될 수도 있다.
  print(bigbang is Idol); // true
  print(bigbang is BoyGroup); // true

  print(sistar is Idol); // true
  print(sistar is GirlGroup); // true
}

method / override

  • method : function (class 내부에 있는 함수)
  • override : 덮어쓰다 (우선시하다)
// 부모 class 선언
class TimesTwo {
  final int number;
  
  TimesTwo(this.number);
  
  // method
  int calculate(){
    return number * 2;
  }
}

// extends 로 부모 class 를 상속
class TimesFour extends TimesTwo {
  // 부모를 지칭하는 super 를 사용함으로써, 부모의 number 가 됨
  TimesFour(int number): super(number);
  
  
  // @override 를 안적어도 작동은 되지만, 직관적이기 위해 적어주자!
  int calculate(){
    return number * 4;
  } 
  
  // 위와 동일. override를 사용. 부모 메서드를 덮어쓴 것.
  int calculate(){
    return super.calculate() * 2;
  }
}
void main(){
  TimesTwo two = TimesTwo(2);
  print(two.calculate()); // 4
  
  TimesFour four = TimesFour(2);
  print(four.calculate()); // 8
}

static

  • staticinstance에 귀속되지 않고 class에 귀속된다.
class Employee {
  // class 내의 고정값이 되어, 인스턴스에서 귀속되지 않고, class로 다이렉트로 값이 먹힘
  static String? building;
  
  // final 로 선언하면 변경 불가
  final String name;

  Employee(this.name);

  void printNameAndBuilding() =>
      print('제 이름은 $name입니다. $building 빌딩에서 근무하고 있습니다.');

  static void printBuilding() => print('저는 $building 건물에 있습니다.');
}

위에서 building을 따로 값을 지정해주지 않았음.

void main() {
  Employee hj = Employee('현주');
  
  // 값을 설정해놓으면, 어떤 인스턴스던, 이 값이 들어감
  Employee.building = '위워크';
  
  hj.printNameAndBuilding(); // 제 이름은 현주입니다. 위워크 빌딩에서 근무하고 있습니다.
}
  • building 값을 할당해주면, 생성된 instance 마다 저 값이 자동으로 들어감.

Interface

  • class 가 속성들을 물려주는 것이라면,
  • interface 는 특정 구조를 강제하는 것 (목적이 instance화가 아님)
  • abstract 를 붙여줌으로 구별
  • interface 를 상속받을 때는 implements 를 사용
// interface
// abstract(추상) 를 붙여주면 'instance로 쓰지말고, 설계도로만 봐라' 라는 의미
abstract class IdolInterface {
  String name;

  IdolInterface(this.name);

  // 어떤 형태인지만 구조를 정해놓는 것이기 때문에, 함수가 있다면 body를 제외해도 됨
  void sayName();
}

// extends 는 class 를 상속받을 때
// implements 는 interface 를 상속받을 때
// interface 와 설계틀을 똑같이 맞춰줘야 한다.
class GirlGroup implements IdolInterface {
  String name;

  GirlGroup(this.name);

  void sayName() {
    print('우리는 여자아이돌 그룹, $name 이야.');
  }
}

// interface 와 설계틀을 똑같이 맞춰줘야 한다.
class BoyGroup implements IdolInterface {
  String name;

  BoyGroup(this.name);

  void sayName() {
    print('우리는 남자아이돌 그룹, $name 이야.');
  }
}
  • interface 구조와 똑같이 맞춰줘야 에러가 나지 않는다.
void main() {
  GirlGroup sistar = GirlGroup('씨스타');
  BoyGroup bigbang = BoyGroup('빅뱅');

  sistar.sayName(); // 우리는 여자아이돌 그룹, 씨스타 이야.
  bigbang.sayName(); // 우리는 남자아이돌 그룹, 빅뱅 이야.
}

generic

  • Type 을 외부에서 받을 때 사용
  • <> 안에 아무 대문자나 넣어두 됨
class Lecture<T> {
  // 제너릭 T 자리에 들어오는 type에 따라 id type이 정해짐
  // => class 내 변수의 type을 외부에서 받아 사용
  final T id;
  final String name;

  Lecture(this.id, this.name);

  void printIdType() {
    print(id.runtimeType);
  }
}

// 제너릭 타입을 2, 3개 무한히 넣어줄 수 있음
// 대신 다 외부에서 만들 때, 설정해주면 됨
class Lecture2<T, A> {
  final T id;
  final A name;

  Lecture2(this.id, this.name);

  void printIdType() {
    // Type 검사
    print(id.runtimeType);
    print(name.runtimeType);
  }
}
void main() {
  Lecture<String> math = Lecture('01', 'math');
  Lecture<int> science = Lecture(07, 'science');

  math.printIdType(); // String
  science.printIdType(); // int

  Lecture2<int, String> social = Lecture2(22, 'social');

  social.printIdType(); // int, string
}
profile
쓸모있는 기술자

0개의 댓글