본문 바로가기
flutter 공부 기록소

Fluuter 공부 기록 5

by riddleuscode 2023. 6. 17.

이어서 이번에는 인터페이스를 살펴보겠습니다.

 

Dart에는 인터페이스를 지정하는 키워드가 없다고 합니다.

이게 무슨 말이냐 하면,

class Perth extends Ghost{
  String _specialty;
  final String _name="퍼스";

  Perth(super.name,super.lv, super.hp, super.atk,this._specialty);
  //Perth(String _name, int _lv, int _hp,int _atk,this._specialty):super(_name,_lv,_hp,_atk);

  //@override
  void attack(Ghost) {
    Ghost.hp -= this.atk;
    print("퍼스는 ${Ghost.name} 에게 $atk만큼의 피해를 주었다. 남은 체력(${Ghost.hp})");

  }
}

클래스는 이런식으로 class라고 지정을 해주는데,

자바처럼 interface Ghost와 같이 지정해주는 말이 없답니다.

대신, 다른 클래스를 인터페이스로 사용할 수가 있습니다. 이때 익숙한 implements를 사용합니다.

class Perth implements Ghost{
  String _specialty;

  Perth(this._name, this._lv,this._hp, this._atk,this._specialty);

  //@override
  void attack(Ghost) {
    Ghost.hp -= this.atk;
    print("퍼스는 ${Ghost.name} 에게 $atk만큼의 피해를 주었다. 남은 체력(${Ghost.hp})");

  }

  @override
  int _atk;

  @override
  int _hp;

  @override
  int _lv;

  @override
  String _name;

  int get lv => _lv;

  set lv(int value) {
    _lv = value;
  }

  @override
  int get hp => _hp;
  
  @override
  set hp(int value) {
    _hp = value;
  }

  @override
  String get name => _name;
  @override
  set name(String value) {
    _name = value;
  }

  @override
  int get atk => _atk;
  
  @override
  set atk(int value) {
    _atk = value;
  }
  
  
  
}

보시면 아시겠지만, 일반 매소드는 물론이고, getter 및 setter까지 재정의 해주었습니다.

일반 상속은 필수가 아니지만, implements를 해주게 되면 모든 매서드를 다시 정의해주어야합니다.

그러므로 단순히 일부 매소드만 재정의할 경우에는 인터페이스를 쓰지말고 그냥 상속을 받는게 훨씬 편하겠죠?

반대로 모든 매소드를 재정의 해야하는 경우에는 인터페이스를 사용하면 되는데, 언제 그럴진 모르겠습니다.

그리고 차이점이 하나 더 있는데, 인터페이스는 "," 를 사용하여 한번에 여러개를 적용시킬 수 있다고 합니다.

반면 상속은 한번에 하나만 가능하죠.

 

이번엔 믹스인을 살펴보겠습니다.

믹스인은 특정 클래스를 지정해서 필드나 매서드를 지정할 수 있습니다. (지정은 on 키워드로 합니다)

ghost의 매서드인 attack을 지우고, 믹스인을 만들어 그곳에 다시 정의해보겠습니다.

mixin attackF on Ghost{
  int turn=0;
  void attack(Ghost){
    Ghost.hp -= this.atk;
    print("$name은(는)  ${Ghost.name} 에게 $atk만큼의 피해를 주었다. 남은 체력(${Ghost.hp}), 현재 턴:${++turn}");
  }
}
class Perth extends Ghost with attackF{
  Perth(super.name, super.lv, super.hp, super.atk);

}


void main() {
  Ghost g= Ghost.fromMap({
    "name":"유우","lv": 10,"hp" : 1000, "atk": 200
  });
  Perth g2= Perth("퍼스",20,2000,100);
  for(int i=0;i<5;i++){
    g2.attack(g);
  }

  runApp(const MyApp());
}

 

결과:

퍼스은(는)  유우 에게 100만큼의 피해를 주었다. 남은 체력(900), 현재 턴:1
퍼스은(는)  유우 에게 100만큼의 피해를 주었다. 남은 체력(800), 현재 턴:2
퍼스은(는)  유우 에게 100만큼의 피해를 주었다. 남은 체력(700), 현재 턴:3
퍼스은(는)  유우 에게 100만큼의 피해를 주었다. 남은 체력(600), 현재 턴:4
퍼스은(는)  유우 에게 100만큼의 피해를 주었다. 남은 체력(500), 현재 턴:5

 

분명 attackF는 atk라는 속성이 없는데 잘 작동합니다.

그 이유는 on Ghost로 지정을 해주었기 때문입니다.

이럴 경우, 이 믹스인을 다시 Ghost에 붙이는 것은 불가능 합니다.

대신, 상속 받는 클래스에다 붙일 수 있고, 인터페이스와 마찬가지로 여러개를 붙일 수 있습니다.

 

그러면, Ghost에도 붙일 수 있게 개조를 좀 해보겠습니다.

mixin attackF{
  static int turn=0;

  void attack(Ghost, Ghost2){
    Ghost.hp -= Ghost2.atk;
    print("${Ghost2.name}은(는)  ${Ghost.name} 에게 ${Ghost2.atk}만큼의 피해를 주었다. 남은 체력(${Ghost.hp}), 현재 턴:${++turn}");
  }
}

class Ghost with attackF{
  String _name;
  int _lv;
  int _hp;
  int _atk;


  int get lv => _lv;
  set lv(int value) {
    _lv = value;
  }

  int get hp => _hp;

  set hp(int value) {
    _hp = value;
  }

  String get name => _name;

  set name(String value) {
    _name = value;
  }

  int get atk => _atk;

  set atk(int value) {
    _atk = value;
  }


  Ghost(this._name, this._lv,this._hp, this._atk);

  Ghost.fromMap(Map<String,dynamic>map):
        this._name= map["name"],
        this._lv= map["lv"],
        this._hp= map["hp"],
        this._atk= map["atk"];

}
class Perth extends Ghost{
  Perth(super.name, super.lv, super.hp, super.atk);

}


void main() {
  Ghost g= Ghost.fromMap({
    "name":"유우","lv": 10,"hp" : 1000, "atk": 200
  });
  Perth g2= Perth("퍼스",20,2000,100);
  for(int i=0;i<2;i++){
    g.attack(g2,g);
    g2.attack(g,g2);
  }

  runApp(const MyApp());
}

결과:

유우은(는)  퍼스 에게 200만큼의 피해를 주었다. 남은 체력(1800), 현재 턴:1
퍼스은(는)  유우 에게 100만큼의 피해를 주었다. 남은 체력(900), 현재 턴:2
유우은(는)  퍼스 에게 200만큼의 피해를 주었다. 남은 체력(1600), 현재 턴:3
퍼스은(는)  유우 에게 100만큼의 피해를 주었다. 남은 체력(800), 현재 턴:4

 

이번에는 양측이 서로 공격을 하게 만들었고, 따라서 턴이 따로 놀면 안되겠죠?

그래서 static을 넣어서 공격마다 턴이 증가하도록 만들었습니다.

그리고 인자로 Ghost를 둘 받았기 때문에 on으로 ghost를 지정해주지 않아도 모든 속성을 다룰 수 있습니다.

이제 Ghost에 with를 사용해서 attackF를 가져올 수 있고,

Perth는 상속을 받았으므로 따로 with를 사용할 필요가 없습니다.

 

추상(abstract)는 사용해보진 않고 정리만 하고 넘어가겠습니다.

추상 클래스는 필요한 속성들을 정의할 수 있고, 매소드는 {구현부} 를 제외하고 

반환 타입, 매서드 이름, 매개변수만 정의할 수 있습니다.

그리고 이 추상 클래스는 객체를 만들어 사용할 순 없고, 구현을 해준 후 그 구현된 클래스를 사용할 수 있습니다.

 

인터페이스가 모든 매서드를 재정의해야하는 것과 비슷하게,

추상 클래스를 구현하는 클래스는 선언만 해둔 매서드 들을 전부 정의해주어야 합니다.

예시를 들면, Ghost를 추상클래스로 만들고, Perth, Yuwoo같이 실제 사용될 령으로 구현한 후 사용하면 될 것 같습니다.

아, 인터페이스를 구현하는 것 처럼 추상 클래스를 구현하기 위해서는 동일하게 implements를 사용해야 합니다.

[class 구현할 클래스명 implements 추상클래스명] 이런 형식입니다.

 

이번에는 제네릭을 보겠습니다. 책에서는 이렇게 설명하고 있습니다.

"클래스나 함수의 정의를 선언할 때가 아니라 인스턴스화하거나 실행할 때로 미룹니다."

자바에서 사용해 봐서 뭔지는 알지만, 뭔가 설명해보려니 말이 안나오는 개념인데,

꽤 괜찮은 설명인 것 같습니다. 

그러면 왜 미루느냐? 하면 우선 내용물을 받아보고 결정해보겠다는 겁니다.

우선 사용해보겠습니다.

class Note<T>{
  T content;
  Note({required this.content});

  @override
  String toString() {
    return 'Note{content: $content}';
  }

}

void main() {
  Note stringListNote= Note<List<String>>(
    content:["문자열리스트"]
  );
  Note intNote= Note<int>(
      content:5
  );
  print(stringListNote.content.runtimeType);
  print(intNote.content.runtimeType);

  runApp(const MyApp());
}

결과:

List<String>
int

 

처음에는 내용의 타입을 정해주지 않았습니다.

노트의 내용이 List가 될수도 있고, bool이 될 수도 있고, Map이 될 수도 있겠죠?

그리고 main에서 노트를 생성하고 값을 넣어주었습니다.

넣어주면 자동으로 타입이 정해질까요?

 

그래서 한번 출력해보려고 필요한 매서드를 검색해보니, runtimeType을 사용하면 타입을 반환하는군요!

결과와 같이 타입이 자동으로 정해졌습니다.

 

바구니를 만들때 만드는 사람은 그 바구니에 뭐가 담길지 알 수가 없습니다.

그리고 누군가 바구니를 사갔는데,

바구니에 사탕을 담았다면 그 바구니는 이제 사탕바구니가 되는거고,

바구니에 음식을 담았다면 그건 이제 장바구니가 되는 그런 상황이랑 비슷한게 아닐까요?

 

값을 넣어 주었을때 용도가 정해지는 그런 기능이 제네릭이라고 생각합니다.

 

오늘 마지막으로 볼게 하나 더 있는데 캐스케이드 연산자입니다.

class Note<T>{
  T content;
  Note({required this.content});

  @override
  String toString() {
    return 'Note{content: $content}';
  }

}

void main() {
  Note stringListNote= Note<List<String>>(
    content:["문자열리스트"]
  )..content.add("이미 리스트가 되어 add 사용가능")
  ..content.add("오늘은 여기까지~");

  print(stringListNote.content);

  runApp(const MyApp());
}

결과: [문자열리스트, 이미 리스트가 되어 add 사용가능, 오늘은 여기까지~]

객체 뒤에 ..을 붙여 사용하고, 연속으로 쓸 수 있습니다.

그 객체의 매서드를 사용할 수도 있고, 값(속성)을 변경시킬 수도 있습니다.

 

분명 새로운 내용인데 뭔가 더 쓸 말이 없네요.. 오늘은 여기까지 하겠습니다.

다음 글에서는 비동기 프로그래밍에 대한 내용을 살펴보겠습니다.

감사합니다~

 

 

 

 

'flutter 공부 기록소' 카테고리의 다른 글

Flutter 공부 기록 7  (0) 2023.06.19
Flutter 공부 기록 6  (0) 2023.06.18
Flutter 공부 기록 4  (0) 2023.06.16
Flutter 공부 기록 3  (0) 2023.06.15
Flutter 공부 기록2  (0) 2023.06.14