#1 Classes
Typescript 역시 다른 객체지향 언어들과 마찬가지로 객체지향 프로그래밍을 할 수 있다.
class Player{
constructor(
private firstName: string,
private lastName:string,
public nickname:string
){}
}
위와 같이 class를 선언할 수 있다. 이렇게 선언한 코드는 javascript에서 다음과 같이 표현된다.
class Player {
constructor(firstName, lastName, nickname){
this.firstName = firstName;
this.lastName = lastNamel;
this.nickname = nickname;
}
}
우리가 다른 코드에서 this.{variable}로 표현하던 과정을 생략할 수 있다!
확인해보면 private과 public의 차이가 없는데, 이는 private와 public이 typescript 내에서 코드를 보호하기 위한 것임을 보여준다.
Abstract Class
추상 클래스는 다른 클래스가 상속받을 수 잇는 클래스이다. 하지만 추상 클래스는 새로운 인스턴스를 생성하지는 못한다. 즉, ‘상속’만 받을 수 있는 클래스이다.
추상 클래스 역시 javascript로 컴파일 했을 때에는 일반 클래스와 같게 표현된다.
abstract class User {
constructor(
private firstName: string,
private lastName:string,
private nickname:string
){}
}
추상 클래스에는 추상 메소드(abstract method)라는 것이 존재한다. 추상 메소드는 구현되지 않은 메소드로, call signature만 구현해 놓은 것을 말한다.
abstract class User {
constructor(
private firstName: string,
private lastName:string,
private nickname:string
){}
abstract getNickName():void // abstract method
}
class Player extends User {
getNickName() { //상속한 class의 추상 메소드는 반드시 구현해야 한다!
console.log(this.nickname) //error
}
}
추상 메소드는, 추상 클래스를 상속받는 모든 것들이 구현해야 한다. 그렇지 않으면 오류가 발생한다.
위의 코드에서는 오류가 발생한다. 왜냐하면 Player가 상속받은 User class에서 nickname을 private로 선언했는데 Player에서 이를 참조하려 했기 때문이다.
이를 해결하기 위해, 자신과 자신을 상속받은 클래스에서는 참조할 수 있도록 ‘protected’ 를 사용할 수 있다.
abstract class User {
constructor(
protected firstName: string,
protected lastName:string,
protected nickname:string
){}
abstract getNickName():void
}
class Player extends User {
getNickName() {
console.log(this.nickname)
}
}
const jin = new Player("jin", 'x', 'x')
jin.getNickName()
위와 같이 코드를 작성하면 오류 없이 작동한다.
단어사전을 만드는 예시를 통해 class에 대해 애해해보자.
- type을 선언할 때 변수가 어떤 이름으로 올 지 모르는 경우 선언하는 방법
type Words = {
[key:string] :string
}
- class에서 특정 변수를 선언할 때 construcor()로 초기화하지 않고 선언하는 방법
class Dict {
private words: Words
constructor() {
this.words = {}
}
- class를 type처럼 사용하는 방법
type Words = {
[key:string] :string
}
class Dict {
private words: Words
constructor() {
this.words = {}
}
add(word: Word){
if(this.words[word.term]===undefined){
this.words[word.term] = word.def;
}
}
def(term: string){
return this.words[term]
}
}
class Word{
constructor(
readonly public term: string,
readonly public def: string
){}
}
1, 2, 3번 방법을 새롭게 알아보았다. 우리는 이렇게 코드를 구현해서, 사전에 단어를 추가하고 단어를 확인해보는 class를 생성하였다. 실제로 이 class를 아래 코드와 같이 사용해볼 수 있다.
const Kimchi = new Word('Kimchi', '한국의 음식')
const dict= new Dict()
dict.add(Kimchi)
dict.def("kimchi")
추가로 Word 클래스 내부에 term과 def가 readonly이므로 수정이 불가능하다는 것을 알 수 있다.
#2 Interfaces
우리는 전에 type 키워드로 다양한 타입을 만들어 낼 수 있음을 확인했다.
그런데 type으로는 다음과 같은 기능도 수행할 수 있다.
type Team = 'red' | 'blue' | 'yellow'
type Health = 1 | 5 | 10
type Player = {
nickname: string,
team: Team
helath: Health
}
const jin: Player={
nickname: 'jin',
team: 'red',
helath: 10
}
다음과 같이 선언하면 Player type의 team은 red, blue, yellow 셋 중 하나만, health는 1, 5, 10 셋 중 하나만 가능하다. 특정 값만을 가지도록 제한하는 것이다.
위에서 쓰인 type의 기능 중 특정 기능을 활용할 수 있는 다른 키워드가 존재한다. 바로 ‘interface’다.
interface는 객체의 모양을 typescript에게 설명해주기 위해 사용되는 키워드이다. 위의 Player 부분은 다음과 같이 작성할 수 있다.
interface Player {
nickname: string,
team: Team
helath: Health
}
하지만 interface에서는 type처럼 새로운 type을 선언하는 은 불가능하다.
interface Hello = string // error!
interface를 사용하면 상속과 같은 기능도 사용할 수 있기에 조금 더 객체지향 프로그래밍 같아 보일 수 있는 장점이 생긴다.
또한, 새로운 이름의 interface를 여러개 선엄함으로써 값들을 축적시킬 수도 있다.
interface User{
name: string
}
interface User{
nickname: string
}
interface User{
lastname: string
}
const jin : User ={
name:'jin',
nickname:'x',
lastname: 'x'
}
위와 같이 User interface를 세번 작성하더라도 typescript에서는 알아서 하나의 interface로 인식하게 된다.
전에 우리는 abstract class에 대해 배웠다. 추상 클래스를 사용하는 이유는 해당 클래스를 상속받는 클래스들에서 어떠한 property와 method를 선언할 지에 대한 청사진을 보여주기 때문이다.
그러나 추상 클래스를 컴파일된 javascript에서 보면 일반 클래스와 같게 보인다. 이는 자바스크립트로 보았을 때 좋지 않지 않은가..?
따라서 추상 클래스 역시 interface로 바꿔서 표현할 수 있다. 아래와 같은 추상 클래스가 있다고 해보자.
abstract class User{
constructor(
protected firstName: string,
protected lastName: string
){}
abstract sayHi(name:string):string
abstract fullName():string
}
class Player extends User{
fullName(){
return '${this.firstName} ${this.lastName}'
}
sayHi(name: string){
return 'Hello ${name}. My name is ${this.fullName()}.'
}
}
위의 추상 클래스는 아래와 같이 interface로 바꿀 수 있다.
interface User{
firstName: string,
lastName: string
sayHi(name:string):string
fullName():string
}
추상 클래스를 인터페이스로 선언하면 자바스크립트 코드에서 보이지 않게 된다. 하지만 속성들을 private이나 protected로 선언할 수 없다.
interface를 상속하는 방법은 다음과 같이 constructor를 추가해주면 된다.
class Player implements User{
constructor(
public firstName: string,
public lastName: string
){}
fullName(){
return '${this.firstName} ${this.lastName}'
}
sayHi(name: string){
return 'Hello ${name}. My name is ${this.fullName()}.'
}
}
추가로, interface 여러 개를 한번에 상속할 수도 있다.
type과 interface가 굉장히 비슷하다고 생각할 수 있다. 그래서 언제는 type을 쓰고 언제는 interface를 써야할 지 고민될 수 있다.
간단히 정리하면, interface는 객체의 형태를 지정해줄 때만 사용하고, 다른 때에는 type을 사용하면 된다.
type과 interface의 차이를 정리한 것은 typescript의 공식 사이트에서 확인할 수 있다.
#3 Polymorphism
다형성(polymorphism)은 다른 모양의 코드를 가질 수 있게 해주는 것으로, generic에 관련해서 설명할 때 보았다. placeholder 타입이 concrete 타입으로 추론되어 바뀌게 될 것이다.
interface _Storage<T>{
[key:string]: T
}
//for design of API
class LocalStorage<T>{
private storage: _Storage<T>= {}
set(ex:string, value:T){
this.storage[ex]=value
}
remove(ex:string){
delete this.storage[ex]
}
get(ex:string):T {
return this.storage[ex]
}
clear(){
this.storage={}
}
}
const stringsStorage = new LocalStorage<string>
stringsStorage.get("ex")
stringsStorage.set("hello","how are you?")
const booleansStorage = new LocalStorage<boolean>
booleansStorage.get("xxx")
booleansStorage.set("hello",true)
위의 코드는 generic을 적절히 활용하여 polymorphism을 구현한 코드이다.
처음에 interface를 선언해주었고, 변수 명이 어떻게 될지 몰라서 [key:string]으로 표현해주었다.
그리고 <T>라는 generic을 만들어주었고, 이를 LocalStorage라는 class에서 계속 받아서 사용하는 것도 확인할 수 있다.
stringsStorage에는 <T>에 string이 들어갔기에 ‘stringsStorage.set("hello","how are you?")’이 가능해졌고,
booleansStorage에는 <T>에 boolean이 들어갔기에 ‘booleansStorage.set("hello",true)’이 가능해졌다.
** 본 글은 노마드코더의 ‘타입스크립트로 블록체인 만들기’ 강의를 바탕으로 작성했습니다. **