[Must Have 코드팩토리의 플러터 프로그래밍]

02 다트 객체지향 프로그래밍

avocado8 2024. 4. 26. 02:43

 

 

2.1 객체지향 프로그래밍의 필요성

- 변수와 메서드를 특정 클래스에 종속되게 코딩할 수 있어 코드 관리가 용이함

- 인스턴스: 클래스를 이용해 선언한 객체

- 인스턴스화: 클래스에서 인스턴스(객체)를 생성하는 과정

 

2.2 객체지향 프로그래밍의 시작, 클래스

class Idol {
  String name = '아보카도'; //클래스에 종속되는 변수
  void sayName() { //클래스에 종속되는 함수(메서드)
    print('I am ${this.name}.'); //this: 클래스 내부 속성 지칭
    print('I am $name.'); //스코프 안에 같은 속성 이름이 하나뿐이라면 this 생략 가능
  }
}
void main() {
  Idol blackPink = Idol(); //Idol의 인스턴스 생성
  blackPink.sayName(); //메서드 실행
}

 

2.2.1 생성자

생성자(constructor) : 클래스의 인스턴스를 생성하는 메서드

class Idol {
  final String name;
  
  //생성자 선언
  //클래스와 같은 이름이어야 함
  //함수의 매개변수를 선언하는 것처럼 매개변수 지정
  Idol(String name) : this.name = name;
  
  void sayName() {
    print('I am ${this.name}.');
  }
}

- 생성자에서 입력받을 변수는 일반적으로 final로 선언. 인스턴스화한 다음에 변수의 값이 변경되는 것을 막기 위함

- 생성자에서 : 기호 뒤에 입력받은 매개변수가 저장될 클래스 변수 지정

인스턴스 생성🔽

void main() {
  Idol blackPink = Idol('블랙핑크');
  blackPink.sayName(); //I am 블랙핑크.
  Idol avocado = Idol('아보카도');
  avocado.sayName(); //I am 아보카도.
}

 

생성자에서 this를 사용할 경우 해당되는 변수에 자동으로 매개변수가 저장됨

class Idol {
  final String name;
  
  Idol(this.name);
  
  void sayName() {
    print('I am ${this.name}.');
  }
}

 

2.2.2 네임드 생성자

클래스를 생성하는 여러 방법을 명시하고 싶을 때 사용

class Idol {
  final String name;
  final int memsCount;
  
  //생성자
  Idol(String name, int memsCount)
    : this.name = name,
      this.memsCount = memsCount;
  //네임드 생성자
  Idol.fromMap(Map<String, dynamic> map)
    : this.name = map['name'],
      this.memsCount = map['memsCount'];
  
  void sayName() {
    print('I am ${this.name}. There are ${this.memsCount} members of our group.');
  }
}

void main() {
  Idol blackPink = Idol('블랙핑크', 4);
  blackPink.sayName(); //I am 블랙핑크. There are 4 members of our group.
  Idol avocado = Idol.fromMap({
    'name': '아보카도',
    'memsCount': 1,
  });
  avocado.sayName(); //I am 아보카도. There are 1 members of our group.
}

 

2.2.3 프라이빗 변수

같은 파일에서만 접근 가능한 변수

'_' 로 변수명을 시작하면 프라이빗 변수 선언 가능

class Idol {
  String _name;
  Idol(this._name);
}

 

2.2.4. 게터 / 세터

값을 가져올 때 / 지정할 때 사용

class Idol {
  String _name="아보카도";
  String get name { //get 키워드 사용. 매개변수X
    return this._name;
  }
  set name(String name){ //set 키워드 사용. 매개변수로 하나의 변수 받음
    this._name = name;
  }
}
void main() {
  Idol avocado = Idol();
  avocado.name = '바보카도'; //세터
  print(avocado.name); //바보카도(게터)
}

 

2.3 상속

부모 클래스의 기능을 자식 클래스가 사용할 수 있게 하는 기법. extends 키워드 사용

중복 코딩을 하지 않아도 되게 해준다.

class Idol {
  final String name;
  final int memsCount;
  Idol(this.name, this.memsCount);
  void sayName(){
    print('나는 ${this.name}');
  }
  void sayMemsCount(){
    print('${this.name}의 멤버는 ${this.memsCount}명');
  }
}

class BoyGroup extends Idol { //Idol의 자식 클래스
  //상속받은 생성자
  BoyGroup(String name, int memsCount)
    : super(name, memsCount);
  //이 클래스만의 기능
  void sayMale(){
    print('나는 남자 아이돌');
  }
}

super: 부모 클래스를 지칭

 

2.4 오버라이드

부모 클래스 또는 인터페이스에 정의된 메서드를 재정의할 때 사용. @override 키워드는 생략 가능

class GirlGroup extends Idol {
  GirlGroup(super.name, super.memsCount);
  @override
  void sayName(){
    print('나는 여자 아이돌 ${this.name}');
  }
}

void main() {
  GirlGroup blackPink = GirlGroup('블랙핑크', 4);
  blackPink.sayName(); //자식 클래스에서 오버라이드된 메서드 사용
  blackPink.sayMemsCount(); //부모 클래스의 메서드 사용
}

 

2.5 인터페이스

공통으로 필요한 기능을 정의만 해두는 역할. 반드시 재정의할 필요가 있는 기능을 정의하는 용도

다트에는 인터페이스를 지정하는 키워드가 따로 없으며 클래스를 implements 키워드로 인터페이스로 사용

상속은 단 하나의 클래스만 할 수 있지만 인터페이스는 적용 개수에 제한이 없어 콤마로 연결해줄 수 있음

class GirlGroup implements Idol {
  final String name;
  final int memsCount;
  GirlGroup(this.name, this.memsCount);
  void sayName(){
    print('나는 여자 아이돌 ${this.name} ');
  }
  void sayMemsCount(){
    print('멤버는 ${this.memsCount}명');
  }
}

상속과는 다르게 인터페이스는 반드시 모든 기능을 다시 정의해줘야 함

사용법은 클래스와 동일

 

2.6 믹스인

특정 클래스에 원하는 기능들만 골라 넣을 수 있는 기능. 특정 클래스를 지정해서 속성들을 정의할 수 있으며 지정한 클래스를 상속하는 클래스에도 사용할 수 있다. 인터페이스처럼 한 개의 클래스에 여러 개의 믹스인을 적용할 수도 있다.

with 키워드로 적용

mixin IdolSingMixin on Idol{
  void sing(){
    print('${this.name}이 노래를 부릅니다.');
  }
}

class BoyGroup extends Idol with IdolSingMixin {
  BoyGroup(super.name, super.memsCount);
  void sayMale(){
    print('나는 남자 아이돌');
  }
}

void main() {
  BoyGroup bts = BoyGroup('BTS', 7);
  bts.sing(); //믹스인에 적용된 함수 사용 가능
}

 

2.7 추상

상속이나 인터페이스로 사용하는 데 필요한 속성만 정의하고 인스턴스화할 수 없도록 하는 기능

추상 클래스는 추상 메서드를 선언하 수 있으며 추상 메서드는 함수의 반환타입, 이름, 매개변수만 정의하고 함수 바디의 선언은 자식 클래스에서 필수로 정의하도록 강제함.

인스턴스화가 필요없는 공통 부모 클래스를 만들 때 사용

abstract class Idol {
  final String name;
  final int memsCount;
  Idol(this.name, this.memsCount);
  //추상 메서드 선언
  void sayName();
  void sayMemsCount();
}

 

추상클래스는 implements 키워드로 구현

class GirlGroup implements Idol {
  final String name;
  final int memsCount;
  GirlGroup(this.name, this.memsCount);
  void sayName(){
    print('나는 여자아이돌 ${this.name}');
  }
  void sayMemsCount(){
    print('멤버는 ${this.memsCount}명');
  }
}

생성자를 비롯한 모든 메서드를 정의해야 한다.

사용 방법은 클래스와 동일함

 

2.8 제네릭

클래스나 함수의 정의를 선언할 때가 아니라 인스턴스화하거나 실행할 때로 미루는 기능. 특정 변수의 타입을 하나의 타입으로 제한하고 싶지 않을 때 자주 사용한다.

class Cache<T> { //T: 인스턴스화할 때 입력받을 타입
  final T data;
  Cache({
    required this.data,
  });
}

void main() {
  final cache = Cache<List<int>>(
    data: [1,2,3],
  );
  //제네릭에 입력된 값을 통해 data 변수의 타입이 자동으로 유추
  print(cache.data.reduce((value, element) => value + element)); //6
}

 

*흔히 사용되는 제네릭 문자들

T : 변수 타입 표현

E : 리스트 내부 요소들의 타입 표현 ex) List<E>

K : 키를 표현할 때 흔히 사용

V : 값을 표현할 때 흔히 사용 ex) Map<K, V>

 

2.9 스태틱

속성이 클래스 인스턴스가 아닌 클래스 자체에 귀속

class Counter {
  static int i = 0;
  Counter(){
    print(i++);
  }
}

void main() {
  Counter count1 = Counter(); //0
  Counter count2 = Counter(); //1
}

i를 스태틱 변수(정적 변수)로 지정했기 때문에 i는 Counter 클래스에 귀속됨

생성자에 this.i가 아닌 i로 명시

클래스에 직접 귀속되므로 생성자에서 static 값을 지정할 수 없음.

즉 static은 인스턴스끼리 공유해야 하는 정보에 지정한다.

 

2.10 캐스케이드 연산자

인스턴스에서 해당 인스턴스의 속성이나 멤버 함수를 연속해서 사용하는 기능. .. 기호 사용

void main() {
  Idol blackpink = Idol('블랙핑크', 4)
    ..sayName()
    ..sayMemsCount();
}

캐스케이드 연산자 (..)를 사용해 선언한 변수의 메서드를 연속해서 실행.