11 minute read

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

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

모든 코드를 main() 함수에서 작성하면 코드 정리가 안되 유지 보수 및 협업에 장애물이 됨.

객체 지향 프로그래밍을 하면 변수와 메서드를 특정 클래스에 종속되게 코딩할 수 있음. 클래스를 사용해서 서로 밀접한 관계가 있는 함수와 변수를 묶어두면 코드 관리가 용이 하기 때문에 개게지향 프로그래밍은 중요함.

Map 을 이용하면 단순히 값을 저장하는 것 외에 추가적인 편의기능을 구현할 수 없음. 클래스를 사용하면 이런 문제 해결 가능. 클래스를 사용하면 필요한 값들만 입력하도록 제한하고 클래스에 특화된 함수들을 선언할 수 있음.

클래스는 일종의 설계도로서 데이터가 보유할 속성과 기능을 정의하는 자료구조.

아파트 설계도 <-> 실물 아파트

클래스 <-> 인스턴스

인스턴스화 되어야 실제 사용할 수 있는 데이터가 생성됨

  • instance : 클래스를 이용해서 객체를 선언하면 해당 객체를 클래스의 인스턴스라고 부름

2.2 클래스

class Idol {

  String name = '블랙핑크'
  // 클래스에 종속되는 함수 지정 가능
  // 메서드
  void sayName(){
    print('저는 ${this.name} 입니다.')
  }
}


// 사용시
Idol blackPink = Idol();
blackPink.sayName() // 저는 블랙핑크입니다.

2.2.1 생성자

constructor 는 클래스의 인스턴스를 생성하는 메서드입니다.

name의 값을 외부에서 입력할 수 있게 변경하기.

class Idol{
// 생성자에서 입력받는 변수들은 일반적으로 final 키워드 사용
  final String name;
  // 생성자 선언.
  // 클래스와 같은 이름이어야 함.
  // 함수의 매개변수를 선언하는 것처럼 매개변수를 지정해줍니다.
  Idol(String name) : this.name = name;
  // Idol(this.name) 으로도 표현 가능.

  void sayName(){
    print('저는 ${this.name}입니다')
  }
}

//사용하기
Idol blackPink = Idol('블랙핑크')
Idol bts = Idol('BTS')

Idol 클래스 하나로 여러 Idol 인스턴스를 생성해 중복 코딩 없이 활용할 수 있게 되었음.

2.2.2 네임드 생성자

네임드 파라미터와 비슷한 개념. 클래스를 생성하는 여러 방법을 명시하고 싶을 떄 사용. 클래스를 여러 방식으로 인스턴스화할때 유용함

Idol.fromMap(Map<String, dynamic> map):
	this.name = map['name']
	this.memberCount = map['memberCount']
	// 네임드 생성자를 {클래스명. 네임드 생성자명} 형식으로 지정하면됨

2.2.3 프라이빗 변수

일반적인 프라이빗 변수는 클래스 내부에서만 사용하는 변수

다트 언어에서는 같은 파일에서만 접근 가능한 변수

2.2.4 게터/세터

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

게터와 세터는 모두 변수처럼 사용하면 됨.

mutable 변수를 선언해도 직접 값을 가져오거나 지정할 수 있지만 게터와 세터를 사용하면 어떤 값이 노출되고 어떤 형태로 노출될지 그리고 어떤 변수를 변경 가능하게 할지 유연하게 정할 수 있음.

class Idol{
  String _name = '블랙핑크';
  String get name {
    return this._name; // name 변수는 파리이빗 변수지만 간적접으로 외부에서 접근 가능함.
  }
  //세터는 매개변수로 딱 하나의 변수 받을 수 있음.
  set name(String name){
    this._name = name;
  }
}

2.3 상속

extends 키워드를 사용해 상속 (inheritance ) 할 수 있음. 어떤 클래스의 기능을 다른 클래스가 사용할 수 있게 하는 기법.

class BoyGroup extends Idol{ // 자식 클래스는 부모 클래스의 모든 기능을 상속함.
  BoyGroup(
  	String name,
  	int membersCount,
  	) : super // super는 부모클래스를 지칭함. Idol 클래스에 기본 생성자가 있으므로 생성자 실행.
  	(name,
  	memberCount)
  	// 상속받지 않은 기능 추가
  	void sayMale(){
		print('저는 남자 아이돌입니다')};
}

2.4 오버라이드

부모클래스 또는 인터페이스에 정의된 메서드를 재정의할때 사용됨

class GirlGroup extends Idol{
  GirlGroup(
  super.name
  super.memberCount)

  //override 키워드를 이용해 오버라이드함. 메서드 재정의
  // 한클래스에 이름이 같은 메서드가 존재할 수 없기 때문에 override 키워드를 생략해도 메서드가 덮어씌워짐. 하지만 키워드를 쓰는게 더 명시적
  @override
  void sayName(){
    print('저는 여자 아이돌 ${this.name}입니다.')
  }
}

2.5 인터페이스

상속은 공유되는 기능을 이어받는 개념이지만, 인터페이스는 공통으로 필요한 기능을 정의만 해두는 역할

class GirlGroup implements Idol{
  //implements 키워드를 사용하면 원하는 클래스를 인터페이스로 사용할 수 있음.
  final String name;
  final int memberCount;
  ...
  //Idol 클래스가 정의한 기능을 다시 정의함.
  상속은 부모 클래스가 모든 기능을 상속하므로 재정의할 필요가 없음
  인터페이스는 모든 기능을 다시 정의 해야줘야 함.
  애초에 반드시 재정의할 필요가 있는 기능을 정의하는 용도가 인터페이스.

2.6 믹스인

특정 클래스에 원하는 기능들만 골라 넣을 수 있는 기능. 특정 클래스를 지정해서 속성들을 정의할 수 있으며, 지정한 클래스를 상속하는 클래스에서도 사용 할 수 있음. 그리고 인터페이스처럼 한 개의 클래스에 여러개의 믹스인을 적용할 수 있음. 여러 믹스인을 적용하고 싶으면 기호로 열거하면 됨.

mixin IdolSingMixin on Idol{
  void sing(){
    print('${this.name}이 노래를 부릅니다')
  	}
  }
  //믹스인을 적용할 때는 with 키워드 사용
  class BoyGroup extends Idol with IdolSingMixin{
    BoyGroup(
    super.name;
    super.membersCount,)
  }
  void sayMale(){
    print('저는 남자 아이돌입니다')
  }
}

void main(){
  BoyGroup bts = BoyGroup('BTS', 7)
  bts.sing()
}
  • 믹스인과 상속의 차이점?
    • 공통점 : 상속과 믹스인은 모두 클래스에서 코드를 재사용 하는 방법
      • 상속 : 부모-자식 관계를 형성하며, 하나의 부모클래스에서만 상속을 받을 수 있음. Dart 에서는 다중 상속이 불가능. 자식 클래스는 부모 클래스의 모든 속성과 메서드를 상속받고, 필요에 따라 재정의 가능(override)
      • 믹스인 : 클래스에 메서드를 혼합하는 방법으로, 여러 믹스인에서 메서드를 가져와 한 클래스에 추가할 수 있음. 즉 다중 상속의 효과를 낼 수 있음. 믹스인은 with 키워드를 사용해서 클래스에 추가

2.7 추상

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

Idol 클래스를 인터페이스로 사용하고 Idol 클래스를 따로 인스턴스화 할 일이 없다면 Idol 클래스를 추상 클래스로 선언해서 Idol 클래스의 인스턴스 화를 방지하고 메서드 정의를 자식 클래스에 위임할 수 있음. 또한 추상 클래스는 추상 메서드를 선언할 수 있으며 추상 메서드는 함수의 반환 타입, 이름, 매개변수만 정의하고 함수 바디의 선언을 자식 클래스에서 필수로 정의하도록 강제함.

abstract class Idol{ // abstract 키워드를 사용해 추상 클래스 지정
  final String name;
  final int memberCount;

  Idol(this.name, this.membersCount); // 생성자 선언

 void sayName(); // 추상 메서드 선언. 선언까지만 함!
 void sayMembersCount(); // 추상 메서드 선언. 선언까지만 함!

}

선언만 되어있는 메서드들은 상속받는 모든 클래스에서 구현되어야 함.

class GirlGroup implements Idol{ // implements키워드를 이용해 추상 클래스를 구현하는 클래스
  final String name;
  GirlGroup(
  this.name)
  void sayName(){
    print("저는 여자아이돌 ${this.name}입니다")
  }
}

//사용방법
void main(){
  GirlGroup blackPink = GirlGroup('블랙핑크')
}

추상 메서드는 부모 클래스를 인스턴스화 할일이 없고 자식 클래스들에 필수적 또는 공통적으로 정의돼야 하는 메서드가 존재할때 사용됨.

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

여러 클래스가 가져야 하는 메서드나 속성을 표준화하고, 해당 메서드나 속성의 구현을 각 클래스에 위임할 수 있다.

이는 코드의 재사용성을 높이고, 유지관리를 용이하게 한다.

2.8 제네릭

제네릭은 클래스나 함수의 정의를 선언할때가 아니라 인스턴스화 하거나 실행할때로 미룸. 특정 변수의 타입을 하나의 타입으로 제한하고 싶지 않을 때 사용.

Map, List, Set 등에서 사용한 <> 사이에 입력되는 값이 제네릭 문자.

List 이라 하면 String 값들로 구성된 리스트를 생성하겠다는 뜻인데, List는 제네릭이므로 인스턴스화 하기전 어떤 타입으로 List가 생성될지 알지 못함.

class Cache<T>{
  //인스턴스화 할떄 입력받을 타입을 T로 지정
  //data의 타입을 추후 입력될 T타입으로 지정
  final T data;
  Cache({
    retuired this.data,
  })
}
void main(){
  final cache = Cache<List<int>>(
  data:[1,2,3])

  // 제네릭에 입력된 값을 통해 data 변수의 타입이 자동으로 유추됨.
  print(cache.data.reduce((v,e)=> v+e))
}
  • 흔히 사용되는 제네릭 문자들
    • T : 변수 타입
    • E : 리스트 내부 요소 들의 타입 표현
    • K : 키를 표현할때 사용 ex> Map<K, V>
    • V : 값을 표현할 때 사용

2.9 스태틱

지금까지 작성한 변수와 메서드 등 모든 속성은 각 ‘클래스의 인스턴스’에 귀속 되었습니다. 하지만 static 키워드를 사용하면 클래스 자체에 귀속됨.

class Counter{

  static 변수 선언
  static int i=0;

 Counter(){
   i++ //this.i가 아님!
   print(i++)
 }
}

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

static 변수는 클래스에 직접 귀속되기 때문에 생성자에서 static 값을 지정하지 못함. 결과적으로 static 키워드는 인스턴스끼리 공유해야 하는 정보에 지정하면 되겠습니다.

2.10 캐스케이드 연산자

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

.. 기호를 사용

void main(){
  Idol blackpink = Idol('블랙핑크', 4)
  ..sayName()// .. operator 를 사용하면 선언한 변수의 메서드를 연속으로 실행 가능
  ..sayMembersCount()
}

플러터는 모든 위젯을 객체지향 프로그래밍을 통해 구현함!

객체지향 프로그래밍을 통해서 중복 코드를 줄이고, 정리가 잘된 효율적인 코드를 작성하는 것은 개발자의 필수 덕목임.

Updated:

Leave a comment