안녕하세요.
어느덧 제가 준비한 TypeScript 개론의 그 마지막 포스팅입니다.
이번 포스팅에서는 TypeScript의 Class와 객체에 대해 설명드린 후 생성자, 접근 제한자에 대한 설명을 드리도록 하겠습니다.
우리가 살고있는 세상은 수많은 객체들로 이루어져있습니다. 우리 자신, 우리가 살고 있는 아파트, 우리가 타고다니는 자가용 그 모든 것을 객체라고 할 수 있지요.
용성이는 집에서 아침식사를 한 후 자가용을 타고 회사로 출근하였다.
해당 문장에서 '용성', '집', '아침식사', '자가용', '회사', '출근' 그 모든 것을 객체라고 볼수가 있죠.
이러한 객체를 기반으로하는 프로그래밍인 객체 지향 프로그래밍은 컴퓨터 프로그래밍 패러다임중 하나로, 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다. 쉽게 말해 연관된 변수와 함수들을 한 덩어리로 묶어서 구조화하여 표현하는 프로그래밍 스타일을 바로 객체 지향 프로그래밍이라고 한답니다.
프로젝트 규모가 커지게 되면 프로그램을 작성하기 위해서 수많은 코드들이 필요합니다. 코드가 이리저리 흩어져있으면 아무래도 유지보수가 힘들어지겠죠? 그럴때는 이 객체 지향 프로그래밍을 선택함으로써 이러한 부분들로부터 자유로워질 수가 있습니다. 어떠한 프로그램을 특정한 객체 단위로 쪼갠 후, 그것들이 상호작용함으로써 시스템이 동작하는 것이죠.
C++, Java, Python 등이 대표적인 객체 지향 프로그래밍 언어입니다.
이제는 객체 지향 프로그래밍과는 뗄래야 뗄 수 없는 Class라는 녀석에 대해 알아보도록 하겠습니다.
Class = 객체 라고 말해도 과언이 아닐 정도로 Class와 객체는 매우 밀접한 관계를 맺고 있는데요.
객체들은 Class를 통해서 만들어질 수 있고, Class는 객체들의 뼈대, 설계도라고 할 수 있습니다.
// app.ts
let name: string;
let age: number;
let singTitle: string;
let albumCount:number;
let printSingerInfo = (name: string, age: number, singTitle: string, albumCount: number): void => {
console.log(`${name}은 ${age}살이고, 현재까지 ${albumCount}개의 앨범을 냈습니다. 최근 ${name}가 낸 대표곡의 이름은 ${singTitle}이라는 노래입니다.`)
}
위 코드는 특정 가수의 정보를 printSingerInfo라는 함수를 통해 console에 찍어내는 코드입니다. 사실 뭐가 뭔지 잘 보이지 않는 코드이죠. 저희는 이것을 Class를 사용해서 Singer라는 객체를 만든 뒤 더 가독성이 좋은 코드로 만들어낼 수 있습니다. 다음 코드를 보겠습니다.
// app.ts
class Singer{
name: string;
age: number;
singTitle: string;
albumCount:number;
printSingerInfo = (): void => {
console.log(`${this.name}은 ${this.age}살이고, 현재까지 ${this.albumCount}개의 앨범을 냈습니다. 최근 ${this.name}가 낸 대표곡의 이름은 ${this.singTitle}이라는 노래입니다.`)
}
}
처음에 작성된 코드와 대조되는 것이 몇가지 있습니다. 한번 살펴볼까요?
let, const가 사라졌네요?
네 맞습니다. Class 내부에선 let, const 등의 변수 선언 명시가 필요치 않습니다. 또한 이렇게 Class 내에 정의된 변수들을 Property라고 부릅니다.
this라는 것이 보이고, 매개변수가 사라졌어요.
네 맞아요. 매개변수가 사라지고, 함수 내에서 사용되는 매개변수 앞에 this라는 녀석이 붙었죠?
Class 속에서 정의된 함수들은 Class 내 정의된 변수들에게 this로써 접근할 수가 있게 됩니다. 그렇기에 해당 함수들은 상대적으로 적은 매개변수를 가지게 됩니다.
매개변수가 적을 수록 쉬운 유지보수가 가능하기에 더욱 깨끗한 코드라고 말할 수 있습니다.
이렇게 Class 내에 정의된 함수들은 method라고 불립니다.
제가 위에서 언급했듯이 Class는 객체들의 뼈대, 설계도라고 할 수 있는데요. Class를 바탕으로 새로운 객체를 만들어내는 코드를 작성해보도록 하겠습니다.
// app.ts
class Singer{
name: string;
age: number;
singTitle: string;
albumCount:number;
printSingerInfo = (): void => {
console.log(`${this.name}은 ${this.age}살이고, 현재까지 ${this.albumCount}개의 앨범을 냈습니다. 최근 ${this.name}가 낸 대표곡의 이름은 ${this.singTitle}이라는 노래입니다.`)
}
}
let singer1:Singer = new Singer();
singer1.name='KSY';
singer1.age=26;
singer1.albumCount: 7;
singer1.singTitle: 'Dont know'
singer1.printEmployeeDetails(); // Singer 내부의 method에 접근 가능.
singer1은 Singer라는 Class를 바탕으로 만들어진 객체 이기에 고스란히 Singer 내부의 property와 method에 접근할 수 있습니다. 따라서 위 코드처럼 singer1이 printEmployeeDetails()라는 method를 사용할 수 있게되죠. 아마 여러분들이 사용하는 코드편집기에서 singer1. 까지 입력할 경우 참조할 수 있는 method들과 property들이 하단에 나란히 나타날 것을 볼 수 있을겁니다.
이렇게 입력을 한후 node app.js를 입력하여 해당 ts파일을 컴파일 하면 다음과 같이 출력이 됨을 볼 수 있을겁니다.
KSY는 26살이고, 현재까지 7개의 앨범을 냈습니다. 최근 KSY가 낸 대표곡의 이름은 Dont know이라는 노래입니다.
이런식으로 Class를 사용하여 독립된 객체들을 만들어낼 수 있답니다.
위 코드에서 보면 Class를 이용하여 객체를 선언할 때 각각의 property를 한줄 한줄 입력해주어야 하는 번거로움이 존재함과 동시에 코드의 가독성 또한 떨어진다는 문제점을 볼 수 있습니다. 이러한 문제점을 해결하기 위해 존재하는 것이 바로 생성자 흔히 constructor라고 불리우는 것입니다.
모든 Class는 Constructor라는 method를 가질 수 있으며 Constructor은 Class로부터 객체를 생성할 때 호출되며, 객체의 초기화를 담당 합니다.
// app.ts
class Singer{
name: string;
age: number;
singTitle: string;
albumCount:number;
constructor(name: string, age: number, singTitle: string, albumCount: number) {
this.name= name;
this.age= age;
this.singTitle= singTitle;
this.albumCount= albumCount;
} // constructor 생성
printSingerInfo = (): void => {
console.log(`${this.name}은 ${this.age}살이고, 현재까지 ${this.albumCount}개의 앨범을 냈습니다. 최근 ${this.name}가 낸 대표곡의 이름은 ${this.singTitle}이라는 노래입니다.`)
}
}
let singer1:Singer = new Singer('KSY',26,'Dont Know',7); // 간단한 객체 생성
singer1.printEmployeeDetails();
이렇게 constructor를 사용함으로써 코드의 줄도 줄이고, 가독성도 뛰어나졌습니다.
✋ 여기서 주의할 점
constructor가 존재하는 Class를 통해 객체를 생성할 때에는 반드시 constructor에 정의된 매개변수 값이 Arguments로 전달되어야 합니다!!
그렇다면 이제 선택적 매개변수에 대해 알아보아야겠지요? Class 역시 Interface와 마찬가지로 property들에게 optional을 부여할 수 있습니다. 심지어 방법도 같아요.
albumCount라는 property에 optional를 부여해보도록 하겠습니다.
// app.ts
constructor(name: string, age: number, singTitle: string, albumCount?: number) {
this.name= name;
this.age= age;
this.singTitle= singTitle;
this.albumCount= albumCount;
} // constructor 생성
albumCount 뒤에 물음표하나만 붙이면 해당 property에 optional을 부여할 수 있어요.
상당히 간단하지 않나요?
그렇지만 여기서도 주의해야할 점이 존재합니다!!
optional이 부여된 property는 반드시 그렇지 않은 property들보다 뒤에 존재해야해요.
TypeScript의 규칙상 하나의 property가 선택적 property가 되면 나머지 뒤에 존재하는 property들이 optional property가 되기 때문이죠.
이제 어느정도 Class와 constructor에 대해 알아보았는데요. 여기서 만약 제가 이런식의 선언을 한다면 어떻게 될까요?
// app.ts
let singer1:Singer = new Singer('KSY',26,'Dont Know',7); // 간단한 객체 생성
singer1.name='PMC' // singer1의 name 재할당
이렇게 singer1의 name property를 변경시킬 경우
PMC는 26살이고, 현재까지 7개의 앨범을 냈습니다. 최근 PMC가 낸 대표곡의 이름은 Dont know이라는 노래입니다.
잘 변경됨을 볼 수 있습니다. 하지만 우리가 프로그램을 만들 때 외부로부터 데이터를 보호하고자 이런식으로 객체의 property에 접근하는 것을 막아줘야하는 경우가 많습니다.
이제 이러한 것들을 가능하게 해주는 Access Modifier라는 것에 대해 배워보도록 하겠습니다.
Access Modifiers는 Class 속 property들과 method에 적용될 수 있는 키워드로 Class 외부로부터의 접근을 통제하는 역할을 맡고 있습니다. 이는 버그를 줄여주고 코드를 안정화시키는데 많은 도움이 됩니다.
TypeScript의 Access Modifier로는 Public, Protected, Private가 존재합니다.
하나하나 살펴보도록 하겠습니다.
public 키워드가 붙은 변수들은 Class 외부에서 접근이 가능한 변수가 됩니다.
TypeScript에서는 기본적으로 Class 내부 멤버들이 Public의 성향을 가지고 있습니다. 별다른 명시가 없다면 Public으로 처리가 되는 것이죠. 필자는 보다 간결한 코드를 선호하기에 코드를 작성할 때 Public이 아닌 멤버가 한개 이하라면 별도로 public 키워드는 붙여주지 않습니다. 그렇지만 그런 상황이 아니라면 (public이 아닌 멤버가 여러개인 상황에서는) public 키워드를 명시해줍니다. 헷갈릴 수 있기 때문이죠.
protected가 붙은 변수들은 Class 내부, 그리고 상속받는 자식 Class에서 접근이 가능합니다.
private 키워드가 붙은 변수들은 Class 내에서만 접근이 가능하며 Class 외부에서는 접근이 불가능해지게 됩니다.
또한 Class 내부에서 private 키워드가 붙은 멤버에 대해 앞에 '_'를 붙여주는 것은 많은 언어에서 convension으로 통합니다. 이러한 습관을 들여놓는 것이 좋습니다.
// app.ts
class Singer{
private _name: string; // name 멤버를 private로 선언
age: number;
singTitle: string;
albumCount:number;
constructor(name: string, age: number, singTitle: string, albumCount: number) {
this.name= name;
this.age= age;
this.singTitle= singTitle;
this.albumCount= albumCount;
}
printSingerInfo = (): void => {
console.log(`${this._name}은 ${this.age}살이고, 현재까지 ${this.albumCount}개의 앨범을 냈습니다. 최근 ${this.name}가 낸 대표곡의 이름은 ${this.singTitle}이라는 노래입니다.`)
}
}
let singer1:Singer = new Singer('KSY',26,'Dont Know',7);
singer1.name='PMC' // singer의 name은 private 요소임으로 에러 발생
singer1.printEmployeeDetails();
그대로 위 코드를 작성해보면 singer.name='PMC'에서 name 밑에 빨간 줄이 뜨는 것을 볼 수 있을 겁니다. 에러 키워드를 확인해보면 다음과 같습니다.
Property 'name' is private and only accessible within class 'Singer'.
변경하는 방법에 대해서는 아래에서 설명하겠습니다.
이번에는 name 값을 출력하고자 합니다.
// app.ts
console.log(singer1.name); //에러 발생
이것은 private 키워드의 강력함을 알 수 있게 해주는 대목으로, 비공개 멤버에 대해 접근하는 것 자체를 막기 때문에 출력도 불가능한 것인데요. 우리가 저 코드를 실행하기 위해서는 어떤 방법을 사용해야 할까요?
바로 TypeScript에서 제공하는 Getter와 Setter을 사용하면 됩니다.
Getter와 Setter은 get과 set 키워드를 통해 사용할 수 있습니다.
// app.ts
class Singer{
private _name: string; // name 멤버를 private로 선언
age: number;
singTitle: string;
albumCount: number;
constructor(name: string, age: number, singTitle: string, albumCount: number) {
this.name= name;
this.age= age;
this.singTitle= singTitle;
this.albumCount= albumCount;
}
get name () { // Getter
return this._name;
}
set name (value: string) { //Setter
this._name=value;
}
printSingerInfo = (): void => {
console.log(`${this._name}은 ${this.age}살이고, 현재까지 ${this.albumCount}개의 앨범을 냈습니다. 최근 ${this.name}가 낸 대표곡의 이름은 ${this.singTitle}이라는 노래입니다.`)
}
}
let singer1:Singer = new Singer('KSY',26,'Dont Know',7);
console.log(singer1.name); // get name을 통해 가능해짐
singer1.name='PMC' // set name을 통해 가능해짐.
singer1.printEmployeeDetails();
PMC는 26살이고, 현재까지 7개의 앨범을 냈습니다. 최근 PMC가 낸 대표곡의 이름은 Dont know이라는 노래입니다.
이처럼 name이라는 private 멤버를 Getter와 Setter을 통해 참조하고 변경할 수 있다는 것을 볼 수 있습니다.
마지막으로 Constructor에서 Access Modifiers를 사용하는 방법에 대해 알아보도록 하겠습니다! 더 간결한 코드를 만드는데 도움이 됩니다!
우리가 최종적으로 작성한 코드들을 보면 멤버가 constructor 외부에서도 선언되고, 내부에서도 선언되고 상당히 장황하고 반복되며 보기 좋지 않음을 알 수 있습니다. 이러한 것들을 constructor 내부에 다 정의하고 Access Modifiers 또한 constructor 내부에서 적용할 수 있습니다.
// app.ts
class Singer{
//기존의 property들 삭제
constructor(
private _name: string,
private _age: number,
private _singTitle: string,
public albumCount: number){
}
get name () { // Getter
return this._name;
}
set name (value: string) { //Setter
this._name=value;
}
printSingerInfo = (): void => {
console.log(`${this._name}은 ${this.age}살이고, 현재까지 ${this.albumCount}개의 앨범을 냈습니다. 최근 ${this.name}가 낸 대표곡의 이름은 ${this.singTitle}이라는 노래입니다.`)
}
}
let singer1:Singer = new Singer('KSY',26,'Dont Know',7);
singer1.printSingerInfo();
그 이전과 비교할 때 코드가 훨씬 간결해졌음을 볼 수 있습니다.
이렇게 객체가 생성될 때 constructor의 매개변수로 전달된 값은, 암묵적으로 객체의 property 값으로 초기화되고 할당됩니다. 또한 Access Modifiers도 적용됩니다. 여러분들은 이를 통해 class를 더 간단하게 만들 수가 있습니다.
자 이렇게 이번 포스팅에서는 TypeScript의 Class, Constructor, Access Modifiers, Getter/Setter에대해 알아보았습니다. 감사합니다 :)
5편의 TypeScript 개론 시리즈를 내면서 그 이전에는 저 스스로 TypeScript에 대해 많이 안다고 자부했었지만 확실히 포스팅을 하다보니 '내가 모르는 것이 많았구나..' 하는 생각이 들고 많이 찾아보았던 것 같습니다.
TypeScript는 만약 JavaScript framework를 사용하는 그룹에서 개발자로 일을 하고싶다면 반드시 알고 익혀두어야 하는 것이라 생각합니다. 1편에서도 언급했다시피 많은 human error를 줄여주고, 체계적인 협업에 필수적이기 때문이죠. 많은 분들이 저의 포스팅을 보고 TypeScript의 기본적인 개념들에 대해 많이 알아가셨으면 좋겠습니다. 틀린 부분이 있다면 댓글을 통해 가르쳐주시면 정말 감사할 것 같습니다.
앞으로도 좋은 주제로 포스팅을 진행하는 김용성이 되도록 하겠습니다.
긴 글 읽어주셔서 정말 감사합니다 :)
타입스크립트를 이해함은 물론이고 class와 constructor에 대해서도 잘 알게 되었습니다. 좋은 글 감사합니다!