클래스를 사용할 때, ts와js가 어떻게 다른지를 보자.
여기서는 ts로 객체지향 프로그래밍을 해보자. 왜냐면 ts가 클래스 같은 객체지향 기술을 구현하고 많은 기능을 갖고있다고 생각하기 때문이다.
그리고 ts가 많은 양의 반복되는 코드드를 쓰지 않도록 어떻게 막아주는지도 알아보자.
요구: Player라는 함수를 만들고 몇몇의 property들이 있게 해보자. 대게 js에서는 this.firstName = firstName 혹은 this.lastName = lastName
으로 쓰여진다.
ts에서는 파라미터들을 써주기만하면 ts가 알아서 constructor함수를 만들어준다.
class Player {
constructor(
private firstName:string,
private lastName:string,
){}
}
으로 쓰면 된다.
private는 js로 컴파일되면서 사라진다.
public도 추가해보자.
class Player {
constructor(
private firstName:string,
private lastName:string,
public nickname:string
){}
}
const coco = new Player('coco','kay','꼬꼬'); // 정상 동작
마찬가지로 public도 js로 컴파일되면서 사라진다. ts에서만 보호해주는걸로 작동한다.
만약
coco.fistName
으로 코드를 추가했을 때, 오류가 발생한다. 왜냐하면 ts에서 fistName은 private이기 때문이다.
하지만
coco.nickname
은 정상동작한다. public이기 때문이다.
클라스의 가장 멋있는 점은 추상클래스(Abstract Class)
이다.
abstract class User {
constructor(
private firstName:string,
private lastName:string,
public nickname:string
){}
}
class Player extends User{
}
const coco = new Player('coco','kay','꼬꼬');
추상클래스(Abstract Class)는 오직 다른클래스에서 상속받을 수만 있는 클래스이다.
하지만 이 클래스는 직접 새로운 인스턴스를 만들수는 없다.
ex) new User는 추상클래스가 인스턴스를 만들수 없다고 경고 오류를 발생시킨다.
new Player만 가능하다.
추상클래스안의 메소드와 abstract method를 보자.
예를 들어 getFullName()
의 method를 만들어보자.
abstract class User {
constructor(
private firstName:string,
private lastName:string,
public nickname:string
)
private getFullName(){
return `${this.fistName} ${this.lastName}`
}
}
class Player extends User{
}
const coco = new Player('coco','kay','꼬꼬');
nico.getFullName();
private 및 public이 property뿐만 아니라 metho에서도 작동한다.
getFullName이 private이라서 정상적으로 작동하지 않아야함에도 불구하고 js는 정상적으로 작동한다.
하지만 ts에서는 오류를 발생시키며 동작하지 않는다.
위의 코드 예제는 ts가 에러가 생기기전에 코드를 어떻게 보호해주는지 알려주기위한 예를 보여주기위해 getFullName메소드 앞에 private를 붙인것이다.
private를 제거하면 ts에서도 정상적으로 동작한다.
추상메소드를 만들려면, 메소드를 클래스 안에서 구현하지 않으면 된다.
getFullName(){
return `${this.fistName} ${this.lastName}`
}
위의 코드 중 return `${this.fistName}` `${this.lastName}
이 부분이 메소드의 implementation(구현)이다.
하지만 우리는 메소드를 구현해서는 안되고 대신에 메소드의 call signature만 적어둬야한다.
예를 들어, User추상클래스안에 getNickName인 추상메소드가 있다고하자(말이 안되지만 예를 들어보자)
abstract class User {
constructor(
private firstName:string,
private lastName:string,
private nickname:string // 수저
)
abstract getNickName():void; // new
private getFullName(){
return `${this.fistName} ${this.lastName}`
}
}
class Player extends User{
}
const coco = new Player('coco','kay','꼬꼬');
nico.getFullName();
new부분을 보면 return이 void인 메소드의 call signature만 가지고있는걸 볼 수 있다.
abstract getNickName(arr:string):void;
처럼 argument를 가질 수 있다.
ts는 Player가 getNickName을 구현해야한다고 알려주고 있다.
Player함수에 마우스를 호버해보면 ts가 Player가 getNickName을 구현하지 않았다고 알려주고 있는걸 확인할 수 있다.
그러면 getNickName을 구현하자.
abstract class User {
constructor(
private firstName:string,
private lastName:string,
private nickname:string // 수정
)
abstract getNickName():void; // new 추상메소드 부분.
private getFullName(){
return `${this.fistName} ${this.lastName}`
}
}
class Player extends User{
getNickName(){ // new
console.log(this.nickName) // new
}
}
const coco = new Player('coco','kay','꼬꼬');
nico.getFullName();
문제는 여기서 console.log(this.nickName)
를 사용할 수 없다는 점이다. (this.까지만 입력해도 오류 발생)
왜냐하면 우리가 이것을 private property로 만들었기 때문이다. (private nickname:string).
만약 우리가 property를 private으로 만든다면, 그 클래스를 상속하였을지라도 그 property에 접근할 수 없다.
여기서 보다시피, Player가 User를 상속받지만 Player에서는 this.nickname을 접근할 수 없다.
이러한 것은 필드를 보호하기 위한 방법이 두개(private, public)만 있는게 아니라서 그렇다.
protected
라고 또 다른게 하나가 더 있다.
protected
는 private
과 다르다. 만약 private필드가 있다면 private property를 가진 인스턴스는 밖에서 당연히 접근할 수가 없고, 다른 자식클래스에서도 접근할 수 없다.
private
는 말 그대로 개인적인것을 말하고, User클래스의 인스턴스나 메소드에서 접근할 수 있으나 이 User 클래스는 추상클래스여서 인스턴스화 할 수 없다.
필드가 외부로부터는 보호되지만 다른 자식 클래스에서 사용되기를 원한다면 private
을 쓰지말자.
그럴때는 대신하여 protected
를 써라.
abstract class User {
constructor(
protected firstName:string, // protected로수정
protected lastName:string, // protected로수정
protected nickname:string // protected로수정
)
abstract getNickName():void;
private getFullName(){
return `${this.fistName} ${this.lastName}`
}
}
class Player extends User{
getNickName(){
console.log(this.nickName) // 여기!, protect로 수정했기때문에 접근 가능.
}
}
const coco = new Player('coco','kay','꼬꼬');
nico.getFullName();
coco.firstName // new. property를 protected로 수정했기때문에 불가능
이 접근이 불가능하다. 클래스 밖에서는 여기를 접근할 수 없다는 것이다.
하지만 User를 상속하면 user.nickname에 접근할 수 있다.
private
,public
,protected
는 접근제어자===보호등급 을 뜻함. 기본적으로 public이다.
실습: 단어사전 만들기. 지금까지 배운것을 이용해서 해시맵을 만들어보자. 해시알고리즘을 쓰는 완벽한 해시맵이 될 것이다.
type Words = { // Words 타입이 string만을 property로 가지는 object라는 것을 의미
[key:string]: string
}
// ex
// let dict :Wors = {
// "potato": "food"
// }
class Dict{
private words: Words
}
위의 코드대로 하면 Dict 클래스함수의 private words:Words
에 에러가 뜬다.
왜냐하면 initialzer가 없기 때문이다.
일반적이라면 코드를
class Dict{
constructor{
private words: Words
}{}
}
//js 컴파일
class Dict{
constructor(words){
this.words = words;
}
}
이렇게 constructor
안에 쓰기 때문이다. 하지만 js컴파일을 보면 constructor가 words를 지정해는 방식이다.
우리는 지정해주기를 원하지 않는 방식으로 구현하려한다. 그러니 이런식으로 하면 안된다.
그렇기에 우리는 word를 initializer없이 선언해주고 constructor에서 수동으로 초기화시켜줄 것이다.
type Words = {
[key:string]: string
}
// 1이것이 내가 만든 사전이다.
class Dict{
private words: Words; // initializer없이 선언
constructor(){
this.words = {}; // constructor에서 수동으로 초기화
}
// 4단어를 추가하기 위한 메서드를 만들자.
add(word:Word){
if(this.words[word.term] === undefined){
// 주어진 단어가 아직 사전에 존재하지 않을때
this.words[word.term] = word.def;
}
}
// 6term을 이용해서 단어를 불러오는 기능을 만들자.
def(term:string){
return this.words[term];
}
}
// 2각각의 단어 구현을 Word라는 이름을 가진 클래스 함수로 만들자.
class Word{
constructor(
public term:string,
public def:string.
){}
}
// 3새로운 Word생성하자.
const kimchi = new Word("kimchi","없으면 밥 못먹어");
// 5잘 동작하는지 테스트해보자.
const dist = new Dict();
dict.add(kimchi); // 잘 작동
// 7마찬가지로 잘 동작하는 테스트해보자. 이부분에서 kimchui의 definition을 찾을 수 있어야한다.
dict.def("kimchi")"
words가 private이므로, dictionary안에서만 words를 보기를 원한다.
하지만 그 전에 term을 이용해서 단어를 불러오는 기능을 먼저 만들자.
add
메소드 하단에 def
메소드를 추가하자.
여기서 Dict클래스의 constructor안에서 words를 수동으로 초기화했기때문에 Word클래스를 Dict클래스의 add메서드에서 타입처럼 사용할 수 있는것이다. (add(word:Word)
부분).
add메서드의 파라미터부분에 클래스를 입력해준적이 없지만, 클래스를 타입으로 쓸때는 가능한것이다. 보다시피, 여기서 이 파라미터가 이 클래스의 인스턴스이기를 원하면 이렇게 쓸 수 있다.
지금까지 우리는 concrete타입과 generic을 알아봤다.
클래스를 타입으로 쓴 것은 이번이 처음이다.
위 예제를 확장시켜서 추가 삭제등을 추가해보자. === 과제
Dict클래스에서 단어를 삭제하고 단어를 업데이트하는 메소드를 만들자.
Word클래스에서는 단어의 정의를 추가하거나 수정하는 메소드, 그리고 단어를 출력하는 메소드를 만들자.
클래스랑 메소드, 그리고 private나 public같은 것들을 사용해서 뭔가를 만들어봐라.