Dart 객체 지향 프로그래밍

context·2023년 3월 26일
0

Dart

목록 보기
2/2

클래스 선언과 인스턴스 선언은 타 언어의 객체 지향 프로그래밍과 동일한 것 같다.

class

class People{
	String name = "yjh" // member
    
    void intro (){ // method
    	print("im ${this.name}")
// class 내 member 지칭 시 this 키워드 사용.
// 만약 해당 intro method 내 name 이라는 이름의 변수 존재 시, this를 꼭 써줘야 한다.
    	print("im ${name}");
    }
}

void main(){
	People me = People() // instance
    me.intro() // "im yjh"
}

인스턴스 생성 시, 변수 타입을 클래스명으로 지정해, () 를 붙여, 인스턴스화하여, 선언한다.

constructor

클래스의 인스턴스를 생성하는 메소드
클래스와 같은 이름을 사용하며, () 내 원하는 매개변수를 입력한다.

class People{
	final String name; // 생성자에서 입력받는 변수들은 일반적으로 final
    
    // constructor
    People(String name) : this.name = name;
    
    // method
    void intro(){
    	print("im ${this.name}")
    }
}

void main(){
	People me = People("yjh");
    me.intro(); // "im yjh"
    
    People you = People("jbl");
    you.intro() // "im jbl"
}

이 외 다르게 생성자를 사용할 수 있다.

class People{
	final String name; 
    final int age;
    
    // constructor
	People(this.name) // this 사용 시, 해당 변수에 자동으로 매개변수 저장.
    
    // 여러 개의 constructor
    People(String name, int age) : this.name = name , this.age = age;
    
    // named constructor : {클래스명, 네임드 생성자명} 형식
    People.fromMap(Map<String, dynamic> map) : 
	    this.name = map['name'], this.age = map['age']
    
    // method
    void intro(){
    	print("im ${this.name}")
    }
}

void main() {
	People me = People("yjh", 27) // 여러 개의 constructor 사용
    people me = People.fromMap({
    	'name' : "yjh", 'age': 27, // named constructor
    })
}

named constructor는 클래스를 여러 방식으로 인스턴스화 할 때 유용하다.

private

타 언어에서는 class 내부 변수를 칭하지만, 다트에서는 같은 파일에서만 접근 가능한 변수를 말함.
String _name = "" 형태로 , 변수명에 _기호로 시작한다.

extends

class People{
	final String name;
    final int age;
    
    void hello() { print("hi")}
}

class Student extends People
	// extends Constructor
    Student(
    	String name, int age
    ) : super(name, age) // super(부모 클래스)

override

부모 클래스 또는 인터페이스에 정의된 메소드 재정의 시 사용
키워드 생략은 가능하나 협엽 및 유지보수 위해선 @override 명시.

interface

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

  • 인터페이스를 지정하는 키워드는 없으며, 적용 개수 제한이 없다.
class student implements People {}

상속과의 차이는 기능 상속 시, 재정의가 필요가 없다. 하지만, 인터페이스는 모든 기능을 재정의해 주어야 한다.

mix-in

특정 클래스에 원하는 기능들만 골라쓰는 기능.

mixin Work on People{
	void study(){}
} // Student 클래스 내에 Study라는 method 존재.

class Student extends People with Work{
	// mixIn 적용 시에는 with 키워드 사용.
}

abstract

인스턴스화가 필요없는 공통 부모 클래스 만들 때 사용. 자식 클래스들에 필수적으로 또는 공통적으로 정의해야하는 메소드가 존재할 때 사용한다.
Service Logic와 동일하다고 생각하면 될 것 같다.

abstract class People {}

class Student implement People

Generic

클래스나 함수 정의를 선언 시가 아닌 인스턴스화 또는 실행할 때로 미룬다.
특정 변수의 타입을 하나의 타입으로 제한하고 싶지 않을 때 사용한다.
예시로 setInt() / setString() 함수를 따로 만들지 않도록, (다형성)

<T> 변수 타입 / <E> element 타입 / <K> key / <V> value

Static

클래스 멤버가 인스턴스의 귀속되는 것이 아닌, 클래스 자체에 귀속
(생성자를 통한 값 할당이 불가.)

Cascade

인스턴스에서, 해당 속성, 메소드를 연속해서 사용.

People a = People("yjh", 27)
	..sayHello() // 메소드까지 연속해서 사용

비동기 통신

Future

해당 클래스는 미래의 받아올 값 을 뜻한다. (서버 요청 같은 작업)

void main(){
	addNum(1,2);
}

void addNum(int num1, int num2){
	print('$num1 + $num2' 시작);
}

Future.delayed(Duration(seconds: 3), (){
	print(${num1 + num2});
})

print('끝');

//print는 "1 + 2 시작" , '끝' , '3' 순서로 이루어질 것임.

비동기 연산으로, resource를 낭비하지 않고, 다음 코드를 실행함.

async / await

위 코드는 문맥상 맞지 않는 순서로 실행되고 있음.

void main(){
	addNum(1,2);
}

Future<void> addNum(int num1, int num2) async{ // 비동기 함수 정의
	print('$num1 + $num2' 시작);
}

//비동기 함수를 논리적 순서로 실행
await Future.delayed(Duration(seconds: 3), (){ // 대기되고자 하는 비동기 함수
	print(${num1 + num2});
})

print('끝');

//print는 "1 + 2 시작" , '3' , '끝' 순서로 이루어질 것임.

동기 프로그래밍처럼 실행되는 것으로 보이겠지만, 비동기 프로그래밍을 유지함.

Stream

Future는 단 한 번의 return을 하는 비동기 프로그래밍, 지속적인 값 반환을 위해서는 Stream을 사용한다.

  • Stream은 한 번 listen 되면, Stream에 주입되는 모든 값들을 지속적으로 받는다.

데이터가 한 번 이상 반환되는가?

  • Yes
    : for, await를 사용해서 표현하는게 효율적인가?
    O : await for
    X : Stream listen()
  • No
    : 여러 개의 병렬로 실행할 수 있는 비동기 함수가 존재하는가?
    O : await Future.wait()
    wait()은 하나의 Future로 구성된 List를 매개변수로 입력받음.
    해당 함수에 입력된 비동기 함수들은 모두 동시 실행되며, 응답값을 요청을 보낸 순서대로 저장해둔다.
    X : await

기본적인 Stream 사용법은 이러하다.

import 'dart:async';

void main(){
	final controller = StreamController();
    final stream = controller.stream // 스트림 가져오기.
    
    //StreamController를 Listen(값이 주입될 때마다, 콜백 실행)
    final streamListener = stream.listen((val) {
    	print(val);
    })
}

controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
controller.sink.add(4);

실행결과
1
2
3
4

하나의 스트림으로 여러 번 Listen()을 실행하고 싶다면, 브로드캐스트 스트림

import 'dart:async';

void main(){
	final controller = StreamController();
    
	final stream = controller.stream.asBroadcastStream();
    
    final streamListener1 = stream.listen(val){
    	print('listen1');
        print(val)
    }
    
    final streamListener = stream.listen(val){
    	print('listen2')
        print(val)
    }
controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
controller.sink.add(4);

실행결과
listen1
1
listen2
1
listen1
2
listen2
2
...
}

함수로 Stream return

import 'dart:async';

Stream<String> calc(int num) async*{
	for(int i=0; i<5; i++){
//StreamController의 add()처럼 yield 키워드를 이용해서 return
    	yield 'i = $i';
        await Future.delayed(Duration(second: 2));
    }
}

void playStream(){
	calc(1).listen(val){
   		print(val); 
   }
}

void main(){ playStream(); }

실행결과
i=0
i=1
i=2
...

코드팩토리의 플러터 프로그래밍 책을 훑으면서, Dart의 기본문법을 정리해봤다.
기존 사용하던 js, ts와 유사한 개념이 많아, 읽으면서, 필요한 내용만 기록했다.
또한, 모호하고 애매했던 지식들을 채우는데 도움이 된 것 같다.

0개의 댓글