Flutter(플러터)

Dart #2 객체지향 프로그래밍(Object Oriented Programming)

백코딩 2023. 9. 18. 09:40
728x90

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

 

int, String과 같은 기본 데이터 타입을 사용하여 변수를 선언하면, 해당 변수는 그 타입의 데이터만 저장할 수 있습니다. 클래스를 사용하는 것은 단순히 변수에 저장할 수 있는 데이터의 타입을 확장하는 것 이상의 의미가 있습니다. 클래스를 사용하면, 여러 변수(프로퍼티)와 함수(메서드)를 하나의 유닛으로 그룹화할 수 있습니다. 이렇게 하면, 코드를 더 구조화하고 재사용할 수 있으며, 더 복잡한 데이터 구조와 알고리즘을 더 쉽게 표현할 수 있습니다. 예를 들어, 당신의 Idol 클래스는 두 개의 프로퍼티(name과 members)와 두 개의 메서드(sayHello와 introduce)를 포함하고 있습니다. 이 클래스를 사용하여 Idol 객체를 생성하면, 이 객체는 그룹의 이름과 멤버 리스트를 저장할 수 있으며, 인사말과 멤버 소개를 출력할 수 있는 메서드를 가지게 됩니다. 만약 당신이 int 또는 String과 같은 기본 데이터 타입만 사용하려면, 당신은 이러한 프로퍼티와 메서드를 그룹화하는 방법이 없게 됩니다. 따라서, 클래스를 사용하면 코드를 더 잘 구조화하고 관리할 수 있으며, 더 복잡한 동작을 캡슐화할 수 있습니다.

 

 

final 과 const 키워드는 변수가 한 번만 할당 될 수 있음을 나타내지만, 그들 사이에는 몇 가지 차이점이 있습니다.

1. final

- final 변수는 한 번 초기화되면 그 값이 변경될 수 없습니다.

- final 변수는 런타임에 할당 될 수 있습니다. 즉, 런타임 도중에 한 번만 값을 할당할 수 있습니다.

- final 변수의 값은 실행 시간에 계산될 수 있습니다.

 

2. const

- const 변수는 컴파일 타임에 상수 값으로 초기화 됩니다. 즉, 변수의 값이 소스 코드에서 직접 지정되어 컴파일 시점에 알려져야 합니다.

- const 변수는 변경할 수 없는 상수 값을 참조해야 합니다.

 

void main() {
  // Idol로 타입을 정해준다.
  // new Idol 새로운 인스턴스를 생성하겠다.라는 의미이다.
  Idol blackPink = const Idol(
    '블랙핑크',
    ['지수', '제니', '리사', '로제'],
  );

  Idol blackPink2 = const Idol(
    '블랙핑크',
    ['지수', '제니', '리사', '로제'],
  );

  print(blackPink.name);
  print(blackPink.members);
  blackPink.sayHello();
  blackPink.introduce();
  
  print('----------');
  
  print(blackPink == blackPink2);
  // const를 안쓰면 서로 false가 나온다. 새로운 객체가 생성되면 다른 메모리에 저장된다.
  // const를 쓰면 같은 인스턴스가 된다.

  Idol bts = new Idol(
    'BTS',
    ['RN', '진', '슈가', '제이홉', '지민', '뷔', '정국'],
  );

  print(bts.name);
  print(bts.members);
  bts.sayHello();
  bts.introduce();
}

// Idol class
// name(이름) - 변수
// members(멤버들) - 변수
// sayHello(인사) - 함수
// introduce(멤버 소개) - 함수
// constructor(생성자)
// Immutable 프로그래밍

class Idol {
  final String name; // 거의 대부분에 클래스에는 final을 만들어 버그를 예방해야 한다.
  final List<String> members;

//   Idol(String name, List<String> members)  // constructor(생성자)
//       : this.name = name,
//         this.members = members;

//   Idol(this.name, this.members); // 기본 constructor
  
  const Idol(this.name, this.members); // 기본 constructor
  

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

  void introduce() {
    print('저희 멤버는 ${this.members}가 있습니다.');
  }
}

1. 클래스 선언 :

class Idol {
  ...
}

여기서 첫 번쩨 'Idol'은 클래스의 이름을 선언하는데 사용됩니다. 이 이름은 클래스의 타입을 참조할 때와 객체를 생성할 때 사용됩니다.

 

2. 생성자 선언

Idol(String name, List<String> members)
    : this.name = name,
      this.members = members;

여기서 두 번째 'Idol'은 클래스의 생성자를 선언하는데 사용됩니다. 생성자는 클래스의 객체를 생성할 때 호출되며, 필요한 초기화 작업을 수행합니다. 이 경우, 생성자는 'name'과 'members'라는 두 개의 파라미터를 받아 클래스의 프로퍼티를 초기화합니다.

 

'String name '은  두 가지 다른 문맥에서 사용되었습니다.

1. 클래스의 프로퍼티 선언 : 객체의 상태를 저장합니다.

2. 생성자의 매개변수 : 객체 생성 시 초기값을 받습니다.

 

그리고 이 생성자에서, 'this.name = name;' 구문을 사용하여 생성자의 'name' 매개변수로 받은 값을 클래스의 'name' 프로퍼티에 할당합니다.

 

'this.name'과 'name'은 두 개의 서로 다른 것을 참조합니다.

1. 'this.name' : 여기서 'this.name'은 Idol 클래스의 'name' 프로퍼티를 참조합니다. 'this'키워드는 현재 인스턴스(즉, 생성될 객체)를 참조하므로, 'this.name'는 현재 인스턴스의 'name'프로퍼티를 참조합니다.

 

2. 'name' : 생성자의 매개변수로 전달된 'name' 값을 참조합니다. 이 'name'은 생성자 함수 내에서만 사용되며, 생성자가 호출될 때 전달된 인수를 저장합니다.

 

초기화 구문 'this.name = name;' 에서 이 두 참조가 함께 사용됩니다. 이 구문은 생성자에 전달된 'name' 매개변수의 값을 현재 인스턴스의 'name'프로퍼티에 할당하는 역할을 합니다. 

 

void main() {
  // Idol로 타입을 정해준다.
  // new Idol 새로운 인스턴스를 생성하겠다.라는 의미이다.
  _Idol blackPink = _Idol(
    '블랙핑크',
    ['지수', '제니', '리사', '로제'],
  );

  print(blackPink.firstMember);
  
  blackPink.firstMember = '하하';
  
  print(blackPink.firstMember);
}

// getter / setter
// 데이터를 가져올 떄 / 데이터를 설정할때
// _(언더바) : Private 기능 같은 파일에서만 사용할 수 있다.

class _Idol {
  String name;
  List<String> members;

  _Idol(this.name, this.members); // 기본 constructor

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

  void introduce() {
    print('저희 멤버는 ${this.members}가 있습니다.');
  }

  // getter
  String get firstMember {
    return this.members[0];
  }
  
  // setter  하나에 파라미터만 받을 수 있다. 잘 안쓰는 기능이다.
  set firstMember(String name){
    this.members[0] = name;
  }
}
void main() {
  print('------Idol-------');

  Idol apink = new Idol(name: '에이핑크', membersCount: 5);
  apink.sayName();
  apink.sayMemversCount();

  print('------BoyGroup------');

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

  bts.sayName();
  bts.sayMemversCount();
  bts.sayMale();

  print('------GirlGroup------');

  GirlGroup blackPink = new GirlGroup(name: '블랙핑크', membersCount: 4);
  blackPink.sayName();
  blackPink.sayMemversCount();
  blackPink.sayFeMale();

  print('--------Type Comparison ---------');
  print(blackPink is Idol);
  print(blackPink is GirlGroup);
  print(blackPink is BoyGroup);

  print('--------Type Comparison2 ---------');
  print(bts is Idol);
  print(bts is GirlGroup);
  print(bts is BoyGroup);

  print('--------Type Comparison2 ---------');
  print(apink is Idol);
  print(apink is GirlGroup);
  print(apink is BoyGroup);
}

// 상속 - inheritance
//
// 상속을 받으면 부모 클래스의 모든 속성을
// 자식 클래스가 부여받는다.
// 프로그램에서 상속은 무조건 부모 클래스에서 자식클래스에게 속성을 넘겨준다. 자식에서 부모는 안된다.

class Idol {
  // 이름
  String name;
  // 멤버 숫자
  int membersCount;

  Idol({
    required this.name,
    required this.membersCount,
  });

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

  void sayMemversCount() {
    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({required String name, required int membersCount})
      : super(
          name: name,
          membersCount: membersCount,
        );

  void sayFeMale() {
    print('저는 여자 아이돌입니다.');
  }
}
void main(){
  TimesTwo tt = new TimesTwo(2);
  
  print(tt.calculate());
  
  TimesFour tf = new TimesFour(2);
  
  print(tf.calculate());
}

// method - functin(class 내부에 있는 함수)
// override - 덮어쓰다 (우선시하다.)


class TimesTwo {
  final int number;
  
  TimesTwo(
    this.number,
  );
  
  //method
  int calculate() {
    return number * 2;
  }
}


class TimesFour extends TimesTwo {
  TimesFour(int number,
           ) : super(number);
  
  @override
//   int calculate() {
//     return super.number * 4;
//   }
  int calculate(){
    return super.calculate() * 2;
  }
}
void main(){
  Employee seulgi = Employee('슬기');
  Employee chorong = Employee('초롱');
  
  seulgi.name = '코드팩토리';
  seulgi.printNameAndBuilding();
  
  Employee.building = '오투타워';
  
  seulgi.printNameAndBuilding();

}

class Employee {
  // static은 instance에 귀속되지 않고 class에 귀속된다.
  // 알바생이 일하고 있는 건물
  static String? building;
  
  // 알바생 이름
  String name;
  
  Employee(
    this.name,
  );
  
  void printNameAndBuilding(){
    print('제 이름은 $name입니다. $building 건물에서 근무하고 있습니다.');
  }
  
  static void printBuilding(){
    print('저는 $building 건물에서 근무중입니다.');
  }
  
  
}
void main(){
  BoyGroup bts = new BoyGroup('BTS');
  
  bts.sayName();
  
}

// interface
// abstract 우리가 인스턴스를 만들 때 사용하지 말아라
// class IdolInterface{
//   String name;
  
//   IdolInterface(this.name);
  
//   void sayName(){}
// }

abstract class IdolInterface{
  String name;
  
  IdolInterface(this.name);
  
  void sayName(){}
}


class BoyGroup implements IdolInterface{
  String name;
  
  BoyGroup(this.name);
  
  void sayName(){
    print('제 이름은 $name입니다.');
  }
}

// 상속에 경우는 속성과 기능을 물러주기 위해서
// 인터페이스는 어떤 특수한 구조를 강제하는 것
void main() {
  Lecture<String, String> lecture1 = new Lecture('123', 'lecture');
  lecture1.printIdType();
  
  Lecture<int, String> lecture2 = new 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);
  }
}
void main(){
  Test test = new Test();
  
 
}


// Object Oriented Programing
// 객체지향 프로그래밍
// 모든 클래스는 Object를 상속받고 있다.
class Test extends Object{}
728x90