본문으로 바로가기

implements

 

클래스가 특정 interface의 조건을 만족하는지 체크하기 위해서 implements를 사용합니다.

object를 interface로 typing하듯, class도 interface로 typing한다고 생각하면 된다.

interface ediable {
  eat: () => boolean
}

// Cheeze는 ediable interface를 만족해야하므로 eat 함수를 가져야 함
class Cheeze implements ediable {
  eat () {
    return true
  }
}

// eat 함수를 가지고 있지 않아 에러 발생
// Class 'Mac' incorrectly implements interface 'ediable'.
// Property 'eat' is missing in type 'Mac'
class Mac implements ediable {
  typing () {
    return 'no'
  }
}

 

아래와 같이 여러 개의 interface를 연달아 implements 할 수도 있다.

interface ediable {
  eat: () => boolean
}

interface drinkable {
  drink: () => boolean
}

class Cheeze implements ediable, drinkable {
  eat () {
    return true
  }
  drink () {
    return false
  }
}

 

implements에서 주의할 점

 

아래 예시에서 메서드의 인자인 isExpired는 boolean이 아닌 any로 추론되었다.

왜? implements는 클래스 내부에서 타입을 체크, 추론하는 방식을 변화시키지 않고, 그대로 두기 때문이다.

따라서, 클래스를 작성할 때 interface를 신뢰하기 보다는 직접  strict하게 타입을 부여하는 것이 더 안정성있다.

interface ValidUser {
  check: (isExpired: boolean) => boolean
}

class User implements ValidUser {
  // Parameter 'isExpired' implicitly has an 'any' type.
  check (isExpired) {
    return !isExpired
  }
}

 

 

extends (클래스 상속) -> abstract가 아닌 일반 class extends를 다룬다.

 

extends를 기본적으로 활용해보면 다음과 같다.

class Animal {
  move () {
    console.log('Moving along')
  }
}

class Dog extends Animal {
  woof (times: number) {
    console.log('Woof! '.repeat(times))
  }
}

const myDog = new Dog()

myDog.move() // 부모 클래스 메서드
myDog.woof(3) // 자식 클래스 메서드

 

 

만약 자식 클래스에서 부모 클래스의 메서드를 overriding 하고 싶다면, 아래와 같은 방식으로 고칠 수 있다.

class Animal {
  move () {
    console.log('Moving along')
  }
}

class Dog extends Animal {
  move (place?: string) {
    if (place === undefined) {
      return super.move() // 부모 클래스의 메서드 실행
    }
    console.log('Walking to ' + place) // 자식 객체의 메서드 
  }
  woof (times: number) {
    console.log('Woof! '.repeat(times))
  }
}

const myDog = new Dog()

myDog.move()
myDog.move('LA')
myDog.woof(3)

 

다만 조심해야할 점은 부모 클래스인 Animal에서 move는() => void 꼴의 타이핑이 이미 이루어졌기에, 자식 객체의 인자는 반드시 optional하게 들어가야 한다는 것이다. 만약 자식 클래스인 Dog의 오버라이딩된 메서드인 move를 아래와 같이 수정해보자.

base type과 일치하지 않는다는 에러를 확인할 수 있다.

class Dog extends Animal {
  // Property 'move' in type 'Dog' is not assignable to the same property in base type 'Animal'.
  move (place: string) {
    console.log('Walking to ' + place)
  }
  ... omitted
}

 

* extends된 클래스의 실행 순서에 유의할 것

아래 코드는 Person class가 먼저 메모리에 올라간 뒤 그 다음에 SecretAgent가 메모리에 올라간다.

Person의 contructor 내부가 실행되는 시점에서는 SecretAgent는 존재하지 않으므로 joe가 출력된다.

class Person {
  public name: string = 'joe'
  constructor () {
    console.log('my name is', this.name)
  }
}

class SecretAgent extends Person {
  public name: string = 'bond'
}

const JamesBond = new SecretAgent() // my name is joe

 

 

Abstract Class

https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-classes-and-members

 

추상 클래스는 다른 클래스의 기반이 되는 클래스입니다.

추상 클래스를 통해서 extends한 클래스는 추상 메서드를 구체화한 메서드를 반드시 작성해야 합니다.

그리고, 추상적인 클래스이므로 직접적으로 인스턴스를 생성할 수는 없습니다.

// 추상 클래스 내부에 추상 메서드를 정의했다면 이를 상속 받은 클래스는 반드시 해당 메서드를 정의해야 한다.
abstract class Animal {
  // 추상 메서드
  abstract makeSound(): void;

  // 일반 메서드
  move(): void {
    console.log("roaming the earth...");
  }
}

// Cannot create an instance of an abstract class.
const myPet = new Animal()

 

추상 클래스 내부에 추상 메서드를 정의했다면 이를 상속 받은 클래스는 반드시 해당 메서드를 정의해야 합니다.

위의 경우에는 makeSound라는 추상 메서드를 반드시 이용하여 자식 클래스에서는 구체적인 메서드를 만들어야 합니다.

class Cat extends Animal {}

아래와 같이 Animal을 extends를 하려고 하니  추상 메서드를 구현하라고 경고가 뜬 것을 볼 수 있습니다.

 

이를 반영해서 다음과 같이 추상 클래스 상속받은 자식 클래스를 정의하고 활용할 수 있습니다.

class Cat extends Animal {
  makeSound() {
    console.log("miaw miaw");
  }
}

const myCat = new Cat();
myCat.makeSound();
myCat.move();



abstract vs interface의 차이는 무엇인가

// abstract class를 extends한 class는 해당 함수는 오버라이딩할 수 있지만, 하지 않을 수도 있다.
abstract class Creature {
  live() {
    console.log("I am alive");
  }
}

// interface를 implements한 class는 반드시 해당 interface를 구현해야 한다.
interface Talkable {
  talk: () => void;
}

class Person extends Creature implements Talkable {
  // talk 정의하지 않으면 컴파일 에러 발생
  talk() {
    console.log("bla bla bla");
  }
}

class Animal extends Creature {}

 

 

ref)

https://levelup.gitconnected.com/design-patterns-with-typescript-interfaces-vs-abstract-classes-b6aab6e2ad21

https://myjamong.tistory.com/150


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체