객체지향 프로그래밍... 개발공부를 시작했을 때 개념강의를 듣고 멘붕(?)이 왔던 용어였습니다. 도대체 무슨 말을 하고 있는 건지... 그래서 이게 뭘 한다는 건지 그때는 전혀 이해하지 못했습니다. 그 때 본 글이 있었는데 프로그래밍에 대해서 계속 공부를 하고 코드를 작성하다보면 저절로 모든 게 이해가 다 될거라는 글이었습니다.
사실, 아직도 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;
// }
}
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("저는 여자아이돌입니다.");
}
}
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() 해버리면 자기 자신 계속 호출해서 무한루프빠짐
}
}
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 건물에서 근무중입니다.');
}
}
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 입니다');
}
}
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개의 프로젝트로 오늘 초보 탈출! ( 코드팩토리 )