본문 바로가기
DEV/dart & flutter

Dart 객체지향 프로그래밍, 클래스

by 올커 2023. 12. 31.

 

 


🔥 객체지향 프로그래밍(Object-Oriented Programming, OOP)

1. 클래스(Class)

Dart 언어는 객체지향 프로그래밍이므로 아래와 같이 클래스라는 개념이 사용된다.

 - 클래스(class) : 데이터가 가지는 속성과 기능을 정의

 - 인스턴스(instance) : 클래스를 이용하여 객체 선언시, 해당 객체를 인스턴스라고 함

 

 - Dart에서 클래스를 정의할 경우 아래와 같이 생성이 가능하다.

 - 인스턴스를 생성할 경우에는 함수를 실행할 때와 같이 인스턴스화하고 싶은 클래스명 뒤에 괄호()를 붙여준다.

class Fruit {		// 클래스명 지정
  String name = '사과';		// 클래스 종속변수 지정

  void sayName() {		// 클래스 메서드(종속함수) 지정
    print('이름 : ${this.name}');		// 클래스 내부 속성 지칭 → this (this.name → 클래스 내 name변수 지칭)
    print('이름 : $name');		// 클래스 내부 같은 이름 하나만 존재시 this 생략
  }
}

void main() {
  Fruit apple = Fruit();    // 변수타입을 Fruit으로 지정하고 해당 클래스의 인스턴스를 생성
  apple.sayName();		// 메서드 실행
}

 

  1) 생성자(Constructor)

 생성자는 클래스의 인스턴스를 생성하는 메서드이다.

 아래와 같이 하나의 클래스만으로 여러 개의 해당 클래스의 인스턴스를 생성할 수 있다.

class Fruit {
  final String name;		// 생성자에서 입력받는 변수는 보통 final이 붙는다.

  Fruit(String name) : this.name = name;		// 생성자 선언(클래스와 같은 이름)
  // 또는 Fruit(this.name)와 같이 매개변수를 변수에 저장하는 과정 생략 가능

  void sayName() {
    print('이름 : ${this.name}');
  }
}

void main() {
  // name에 '사과' 저장
  Fruit apple = Fruit('사과');
  apple.sayName();

  // name에 '오렌지' 저장
  Fruit orrange = Fruit('오렌지');
  orrange.sayName();
}

 

  2) 네임드 생성자(Named constructor)

 - 클래스를 생성 방법을 다양하게 명시하고 싶을 때 사용

class Fruit {
  final String name;
  final int fruitsCount;

  Fruit(String name, int fruitsCount)		// 생성자 선언
      : this.name = name,
        this.fruitsCount = fruitsCount;

  Fruit.fromMap(Map<String, dynamic> map)		// 네임드 생성자 선언
      : this.name = map['name'],
        this.fruitsCount = map['fruitsCount'];

  void sayName() {
    print('이름 : ${this.name}, 개수 : ${this.fruitsCount}개');
  }
}

void main() {
  Fruit apple = Fruit('사과', 4);		// 기본 생성자 사용시
  apple.sayName();

  Fruit orrange = Fruit.fromMap({		// 네임드 생성자 fromMap 사용시
    'name': '오렌지',
    'fruitsCount': 7,
  });
  orrange.sayName();
}

 

  3) 프라이빗 변수(Private Variable)

 - 같은 파일에서만 접근 가능한 변수를 생성할 때 사용

class Fruit {
  String _name;		// 프라이빗 변수 선언 : 변수명을 '_'로 시작

  Fruit(this._name);
}

void main() {
  Fruit apple = Fruit('사과');
  print(apple._name);
}

 

  4) 게터(getter) / 세터(setter)

 - 값을 가져오거나 지정할 때, 어떤 값을 어떤 형태로 노출하며, 어떤 변수를 변경 가능하게 할 지 지정

 - 게터 : 값을 가져올 때 사용,  세터 : 값을 지정할 때 사용

   *보통 객체지향 프로그래밍에서는 변수값을 '불변성' 특성으로 사용하여 세터는 거의 사용되지 않고 있음

class Fruit {
  String _name= '사과' ;

  String get name {		// 게터 명시(get 키워드, 매개변수 없음)
    return this._name;
  }

  set name(String name) {		// 세터 명시(set 키워드, 매개변수 하나 받음)
    this._name = name;
  }
}

void main() {
  Fruit apple = Fruit();

  apple.name = '오렌지'; 		//  세터
  print(apple.name);      // 게터
}


// 실행결과
// 오렌지

 


2. 상속(Inheritance)

 - 상속은 특정 클래스의 기능을 다른 클래스에서도 사용할 수 있도록 하는 기법을 말한다.

 - 상속은 extends 키워드를 사용하여 {class 자식클래스 extends 부모클래스} 순서로 지정한다.

 - 자식클래스는 부모클래스의 모든 기능을 상속받는다.

 - super는 상속한 부모 클래스를 지칭한다.

class Idol {
  final String name;
  final int membersCount;

  Idol(this.name, this.membersCount);

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

  void sayMembersCount() {
    print('${this.name} 멤버는 ${this.membersCount}명입니다.');
  }
}

class BoyGroup extends Idol {		// 클래스 상속

  // 상속받은 생성자
  BoyGroup(String name, int membersCount,) : super(name, membersCount,);

  // 상속받지 않은 기능
  void sayMale() {
    print('저는 남자 아이돌입니다.');
  }
}

void main() {

  BoyGroup bts = BoyGroup('BTS', 7); 	 // 생성자로 객체 생성

  bts.sayName();          // 부모 클래스에서 상속받은 메서드
  bts.sayMembersCount();  // 부모 클래스에서 상속받은 메서드
  bts.sayMale();          // 자식 클래스에서 생성한 메서드
}


// 실행결과
// 저는 BTS입니다.
// BST 멤버는 7명입니다.
// 저는 남자 아이돌입니다.

 


3. 오버라이드(Override)

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

 

class Idol {
  final String name;
  final int membersCount;

  Idol(this.name, this.membersCount);

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

  void sayMembersCount() {
    print('${this.name} 멤버는 ${this.membersCount}명입니다.');
  }
}

class GirlGroup extends Idol {
  GirlGroup(
      super.name,
      super.membersCount,
      );

  @override			// override 키워드를 사용하여 sayName 메서드 override
  void sayName() {
    print('저는 여자 아이돌 ${this.name}입니다.');
  }
}

void main() {
  GirlGroup redVelvet = GirlGroup('블랙핑크', 4);

  redVelvet.sayName(); 				// 자식 클래스에서 override된 메서드
  redVelvet.sayMembersCount(); 		// 부모 클래스의 메서드
}


// 실행결과
// 저는 여자 아이돌 블랙핑크입니다.
// 블랙핑크 멤버는 4명입니다.

4. 인터페이스(Interface)

 - 인터페이스는 공통으로 필요한 기능을 정의하는 역할을 한다.

 - 인터페이스를 지정하는 키워드는 따로 없으며, 클래스를 인터페이스로 사용하려면 implement 키워드를 사용한다.

 - 여러 인터페이스를 적용할 때에는 콤마(,)를 사용해 인터페이스를 나열하여 사용하면 된다.

 - 클래스와 차이점은, 상속받을 때는 부모 클래스의 모든 기능이 상속되므로 재정의할 필요가 없지만, 인터페이스는 반드시 모든 기능을 다시 정의해주어야 한다.

 

class Idol {
  final String name;
  final int membersCount;

  Idol(this.name, this.membersCount);

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

  void sayMembersCount() {
    print('${this.name} 멤버는 ${this.membersCount}명입니다.');
  }
}


class GirlGroup implements Idol {		// implements : 원하는 클래스를 인터페이스로 사용
  final String name;
  final int membersCount;

  GirlGroup(
      this.name,
      this.membersCount,
      );

  void sayName() {
    print('저는 여자 아이돌 ${this.name}입니다.');
  }

  void sayMembersCount() {
    print('${this.name} 멤버는 ${this.membersCount}명입니다.');
  }
}

void main() {
  GirlGroup redVelvet = GirlGroup('블랙핑크', 4);

  redVelvet.sayName();
  redVelvet.sayMembersCount();
}


// 실행결과
// 저는 여자 아이돌 블랙핑크입니다.
// 블랙핑크 멤버는 4명입니다.

5. 믹스인(Mixin)

 - 특정 클래스에 원하는 기능들만 골라 넣는 기능이다. 

 - mixin 믹스인이름 on 클래스명 으로 원하는 기능들로 이루어진 믹스인을 생성하고,

 - class 자식클래스명 extends 부모클래스명 with 믹스인명 과 같이 with 키워드로 믹스인을 사용할 수 있다.

class Idol {
  final String name;
  final int membersCount;

  Idol(this.name, this.membersCount);

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

  void sayMembersCount() {
    print('${this.name} 멤버는 ${this.membersCount}명입니다.');
  }
}

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

class BoyGroup extends Idol with IdolSingMixin{		// mixin 적용시 with 키워드 사용
  BoyGroup(
      super.name,
      super.membersCount,
      );

  void sayMale() {
    print('저는 남자 아이돌입니다.');
  }
}

void main(){
  BoyGroup bts = BoyGroup('BTS', 7);

  bts.sing();		  // 믹스인에 정의된 sing() 함수 사용 가능
}

// 실행결과
// BTS가 노래를 부릅니다.

6. 추상(abstract class)

 - 추상은 부모 클래스를 인스턴스화할 필요는 없으나, 자식 클래스들에 공통적으로 정의되어야 하는 메서드가 존재할 때 사용한다.

 - 추상 클래스는 abstract 키워드를 사용하여 지정한다.

 

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

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

  void sayName();          // 추상 메서드 선언
  void sayMembersCount();  // 추상 메서드 선언
}

class GirlGroup implements Idol {		// implements 키워드를 사용해 추상 클래스를 구현하는 클래스
  final String name;
  final int membersCount;

  GirlGroup(
      this.name,
      this.membersCount,
      );

  void sayName() {
    print('저는 여자 아이돌 ${this.name}입니다.');
  }

  void sayMembersCount() {
    print('${this.name} 멤버는 ${this.membersCount}명입니다.');
  }
}

void main() {
  GirlGroup redVelvet = GirlGroup('블랙핑크', 4);

  redVelvet.sayName();
  redVelvet.sayMembersCount();
}

// 실행결과
// 저는 여자 아이돌 블랙핑크입니다.
// 블랙핑크 멤버는 4명입니다.

 

 


7. 제네릭(Generic)

 - 특정 변수의 타입을 하나로 제한하고 싶지 않을 때 자주 사용됨

   (ex. setInt(), setString()처럼 따로 만들지 않고 제네릭을 사용해 set() 함수 하나로 여러 자료형을 입력받게 처리)

 - 클래스나 함수의 정의를 선언할 때가 아닌 실행할 때로 미루는 기능

class Cache<T> {		// 인스턴스화할 때 입력받을 타입을 T로 지정
  final T data;  		// data의 타입을 T 타입으로 지정

  Cache({
    required this.data,
  });
}

void main() {
  final cache = Cache<List<int>>(		  // T의 타입을 List<int>로 입력
    data: [1,2,3],
  );

  // 제네릭에 입력된 값을통해 data 변수의 타입이 자동으로 유추
  print(cache.data.reduce((value, element) => value + element));
}

// 실행결과
// 6

 

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

   · T : 변수 타입 (ex. T value;)

   · E : 리스트 내부 요소들의 타입 (ex. List<E>)

   · K : 키를 표현할 때 사용 (ex. Map<K, V>)

   · V : 값을 표현할 때 사용 (ex. Map<K, V>)


8. 스태틱(Static)

 - 변수와 메서드 등 속성을 '클래스가 생성한 인스턴스'가 아닌 '클래스 자체'에 귀속되도록 할 경우 사용

class Counter{
  static int i= 0;  		// static 변수 선언

  Counter(){
    i++;
    print(i++);
  }
}

void main() {
  Counter count1 = Counter();
  Counter count2 = Counter();
  Counter count3 = Counter();
}

// 실행결과
// 1
// 2
// 3

 


9. 캐스케이드 연산자(Cascade Operator)

 - 캐스케이드 연산자는 .. 기호를 사용하여 인스턴스에서 해당 인스턴스의 속성이나 멤버 함수를 연속해서 사용하게 한다.

class Idol {
  final String name;
  final int membersCount;

  Idol(this.name, this.membersCount);

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

  void sayMembersCount() {
    print('${this.name} 멤버는 ${this.membersCount}명입니다.');
  }
}

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

// 실행결과
// 저는 블랙핑크입니다.
// 블랙핑크 멤버는 4명입니다.

 

 

 

반응형

'DEV > dart & flutter' 카테고리의 다른 글

다트 비동기 프로그래밍 Future, async/await, Stream  (1) 2024.01.01
Dart 기본 문법 정리  (0) 2023.12.27

댓글