OOP(객체지향 프로그래밍) (with Dart)

김희산·2023년 7월 14일
0

TIL

목록 보기
23/23

서론

객체지향 프로그래밍... 개발공부를 시작했을 때 개념강의를 듣고 멘붕(?)이 왔던 용어였습니다. 도대체 무슨 말을 하고 있는 건지... 그래서 이게 뭘 한다는 건지 그때는 전혀 이해하지 못했습니다. 그 때 본 글이 있었는데 프로그래밍에 대해서 계속 공부를 하고 코드를 작성하다보면 저절로 모든 게 이해가 다 될거라는 글이었습니다.

사실, 아직도 100퍼센트 이해했다고는 볼 수 없지만, 어느 정도 뭘 의도하고 프로그래밍을 OOP를 준수하면서 하는지에 대해서 감은 온 것 같습니다.

저번 포스트에 이어서 코드팩토리님의 flutter강의 초반 부분인 OOP에 대한 강의가 있었는데 많은 도움이 되어서 강의를 듣고 정리해보았습니다.
OOP 개념에 대한 글을 따로 정리하진 않았고 코드로 작성한 것들만 보여드리겠습니다.

Dart를 시작하시는 분들 께 많은 도움이 되셨으면 좋겠습니다.
(OOP의 4가지 특징 그것에 대해 형식을 정해서 정리한 건 아닙니다.)

// **Object Oriented Programming**

// 기능들을 한 곳에 모아 놓을 수 있는게 class

// class 통해서 프로그래밍을 하는 것을 OOP 라고 한다.

// 설계사를 만드는 게 클래스의 정의 → 실제로 무언가를 만들어 내는 것 → 그 결과물이 인스턴스

void main() {
// Dart언어에서는 new Idol() 이렇게 해도되고 그냥 Idol() 해도 됨
// Idol blackPink = const Idol('블랙핑크', ['지수', '제니', '리사', '로제']);
// Idol blackPink2 = const Idol('블랙핑크', ['지수', '제니', '리사', '로제']);
// const로 인스턴스 생성시 같은 인스턴스로 취급
// print('const로 선언시 두 객체는 같다. ${blackPink == blackPink2}이다.'); // true

// Idol blackPink3 = Idol('블랙핑크', ['지수', '제니', '리사', '로제']);
// Idol blackPink4 = Idol('블랙핑크', ['지수', '제니', '리사', '로제']);
// print('그냥 선언시에는 같지 않다.  ${blackPink3 == blackPink4}이다.'); // false

// Idol bts = Idol('BTS', ['RM', '진', '슈가', "제이홉", "지민", "뷔", "정국"]);
  _Idol blackPink = _Idol('블랙핑크', ['지수', '제니', '리사', '로제']);
  _Idol bts = _Idol.fromList([
    ['RM', '진', '슈가', "제이홉", "지민", "뷔", "정국"],
    'BTS',
  ]);
// print(bts.name);
// print(bts.members);
// bts.sayHello();
// bts.introduce();

  print(blackPink.firstMember); // 지수
  print(bts.firstMember); // RM

// setter 호출
// blackPink.firstMember = '코드팩토리';
// bts.firstMember = '아이언맨';

  print(blackPink.firstMember); // 코드팩토리
  print(bts.firstMember); // 아이언맨
}

// Idol class
// name(이름) - 변수
// members(멤버들)- 변수
// sayHello(인사) - 함수
// introduce(멤버소개) - 함수

// constructor (생성자)
// immutable programming -> 인스턴스의 값이 바뀌길 원하지 않음
// 만약 바꾸고 싶다면 차라리 새로운 인스턴스를 만들길 바람.-> final 사용
// 인스턴스를 만들 때 선언이 된다.(class 안에선 거의 대부분 final 사용)

// getter / setter
// 데이터 가져올때 / 데이터를 설정할 때
// 설계도
// class 를 private로 만들고 싶다 -> 클래스이름 앞에 _ 표시한다.
// 현재 파일의 위치에서만 수정가능. 외부에서 import 하더라도 변경 불가
class _Idol {
  // final String name;
  // final List<String> members;
  String name;
  List<String> members;

  // 방법 1
  // Idol(String name, List<String> members)
  //     : this.name = name,
  //       this.members = members;

  // 방법2 -> constructor 이용 (외부에서 파라미터 받기)
  // const 안써도 되지만 const constructor 하는 순간
  // 인스턴스 만들고 나서 재할당 안되고 빌드타임에 알 수 있음
  _Idol(this.name, this.members);

  // 방법3 -> named constructor 이용 (외부에서 파라미터 받기)
  _Idol.fromList(List values)
      : this.members = values[0],
        this.name = values[1];

  void sayHello() {
    print("안녕하세요. ${this.name}입니다.");
  }

  void introduce() {
    print('저희 멤버는 ${this.members.join(', ')} 입니다.');
  }

  // getter 값을 가져옴
  String get firstMember {
    return this.members[0];
  }

  // setter 값을 지정 ( 잘 쓰진 않는다. )
  // set firstMember(String name) {
  //   this.members[0] = name;
  // }
}

inheritance

void main() {
  print('--------------Idol---------------');
  Idol apink = Idol(name: '에이핑크', membersCount: 5);

  apink.sayName();
  apink.sayMembersCount();

  print('--------------Boy Group---------------');

  BoyGroup bts = BoyGroup('BTS', 7);

  bts.sayName();
  bts.sayMembersCount();
  bts.sayMale();

  print('--------------Girl Group---------------');

  GirlGroup redVelvet = GirlGroup('Red Velvet', 5);

  redVelvet.sayMembersCount();
  redVelvet.sayName();
  redVelvet.sayFemale();

  print('-------------Type Comparision-----------');
  print(apink is Idol); // true
  print(apink is BoyGroup); // false
  print(apink is GirlGroup); // false

  print('-------------Type Comparision 2-----------');
  print(bts is Idol); // true
  print(bts is BoyGroup); // true
  print(bts is GirlGroup); // false

  print('-------------Type Comparision 3-----------');
  print(redVelvet is Idol); // true
  print(redVelvet is BoyGroup); // false
  print(redVelvet is GirlGroup);  // true
}

// 상속 - inheritance
// 상속을 받으면 부모 클래스의 모든 속성을
// 자식 클래스가 부여 받는다.

class Idol {
  // 이름
  String name;

  // 멤버 숫자
  int membersCount;

  // named parameter 사용
  Idol({
    required this.name,
    required 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: name, membersCount: membersCount);

  // 부모 클래스에서는 자식클래스에서 선언한 변수나,메서드를 사용할 순 없다.
  void sayMale() {
    print('저는 남자아이돌입니다.');
  }
}

class GirlGroup extends Idol {
  GirlGroup(
    String name,
    int membersCount,
  ) : super(name: name, membersCount: membersCount);

  void sayFemale() {
    print("저는 여자아이돌입니다.");
  }
}

Overriding

void main() {
  TimesTwo tt = TimesTwo(2);

  print(tt.calculate());

  TimesFour tf = TimesFour(2);

  print(tf.calculate());
}

// method - function (class 내부에 있는 함수)
// override - 덮어쓰다 ( 우선시하다 ) <- 상속한 자식에 대해서만 오버라이딩할수있다.

class TimesTwo {
  final int number;

  TimesTwo(
    this.number,
  );

  int calculate() {
    // 따로 number가 선언되어 있지 않으면 Dart에서는 this의 number를 가리킴.
    // return number * 2 이렇게 작성해도 됨
    return this.number * 2;
  }
}

class TimesFour extends TimesTwo {
  TimesFour(
    int number,
  ) : super(number);

  // 덮어쓰기를 하자! 그리고 Annotation 적어주는게 좀 직관적이다.
  //   @override 
  //   int calculate() {

  //     // return super.number * 4; 정석대로 super 씀, 하지만 삭제해도 무방
  //     return number * 4;

  //   }
  
  // 만약 override 한 상태에서 부모의 메서드를 그대로 살리고 싶으면 
  
  int calculate(){
    return super.calculate() * 2;
    // 여기서 실수로 만약 this.calculate() 해버리면 자기 자신 계속 호출해서 무한루프빠짐
  }
}

static

void main() {
  Employee seulgi = Employee('슬기');
  Employee chorong = Employee('초롱');

  chorong.name = '코드팩토리'; // 이것을 인스턴스에 귀속이 된다고 말을 한다.
  seulgi.printNameBuilding();
  chorong.printNameBuilding();

  Employee.building = '오투타워';

  seulgi.printNameBuilding();
  chorong.printNameBuilding();
  
  Employee.printBuilding(); // static 메서드는 객체 생성 없이 호출 가능
  
}

class Employee {
  // static은 instance에 귀속되지 않고 class에 귀속된다.

  // 알바생이 일하고 있는 건물
  static String? building; // 클래스에다가 귀속시킬 수 있다.!!!

  // 알바생 이름
  // final String name;
  String name;

  Employee(
    this.name,
  );

  void printNameBuilding() {
    print('제 이름은 $name 입니다. $building 건물에서 근무하고 있습니다.');
  }

  static void printBuilding() {
    print('저희는 $building 건물에서 근무중입니다.');
  }
}

interface, abstract

void main() {
  BoyGroup bts = BoyGroup('BTS');
  GirlGroup redVelvet = GirlGroup('레드벨벳');
  
  bts.sayName(); // 제 이름은 BTS 입니다
  redVelvet.sayName(); // 제 이름은 레드벨벳 입니다
  
  
  // 마찬가지로 상속이랑 똑같은 결과이다. 물려받지만 변경할 수 없는 물려받기라고 생각하면 된다.
  print(bts is IdolInterface); // true
  print(bts is BoyGroup); // true
  print(bts is GirlGroup); // false
}

// interface 다트에서는 그냥 class로 사용
// abstract class들은 인스턴스로 만들지 못한다.
// dart에선 인터페이스를 class 형태로 만들기 때문에 혹시나 실수 방지용..이기도 하다.
abstract class IdolInterface {
  String name;

  IdolInterface(this.name);

  void sayName() {} // 어차피 추상적이기 때문에 형태만 같으면 됨. 안에 내용은 지워도 됌.
}

// 형태를 똑같이 맞춰줘야 함 ( 강제하는 역할 )
class BoyGroup implements IdolInterface {
  String name;

  BoyGroup(this.name);

  void sayName() {
    print('제 이름은 $name 입니다');
  }
}

class GirlGroup implements IdolInterface {
  String name;

  GirlGroup(this.name);

  void sayName() {
    print('제 이름은 $name 입니다');
  }
}

Generic

void main() {
  Lecture<String,String> lecture1 = Lecture('123', 'lecture1');

  lecture1.printIdType();

  Lecture<int,String> lecture2 = Lecture(123, 'lecture2');

  lecture2.printIdType();
}

// generic - 타입을 변수처럼 외부에서 받을 때 사용(선언시에 타입이 결정 됨)
class Lecture<T, X> {
  final T id;
  final X name;

  Lecture(this.id, this.name);

  void printIdType() {
    print(id.runtimeType);
  }
}

출처 : [초급] Flutter 3.0 앱 개발 - 10개의 프로젝트로 오늘 초보 탈출! ( 코드팩토리 )

profile
성공은 제로섬 게임이 아니라 주변인들과 함께 나아가는 것이다.

0개의 댓글