JavaScript로 interface 모사하기

"Ukiyo-e painting of the kermit the frog, full body." from DALL-E-2

소개

JavaScript에는 class만 존재하고, interface가 없습니다. 따라서, interface를 이용한 아키텍처를 불완전하게 적용할 수 밖에 없습니다. class만으로 interface와 비슷한 기능을 하도록 모사하는 방법을 제안합니다.

Step 1. interface base 생성

프로젝트에서 소스 폴더 내에 interfaces 폴더를 생성하고, 그 안에 base.interface.js 파일을 만들어 다음과 같이 작성합니다.

 

/**
 * 인터페이스 정의 헬퍼 함수
 * 
 * @template T
 * @param {T} newInterface 
 * @returns {T}
 */
function defineInterface(newInterface){
    const NOT_IMPLEMENTED = "throw new Error('not implemented')"
    newInterface.prototype._init = function(){
        for(const propName of Object.getOwnPropertyNames(newInterface.prototype)){
            if(propName === 'constructor') continue
            if(!this[propName].toString().includes(NOT_IMPLEMENTED)) continue
            throw new Error(`${propName} is not implemented`)
        }
    }
    return newInterface
}

module.exports = {
    defineInterface
}

defineInterface 함수는 클래스 명세를 받는데, 클래스 내의 메서드들을 순회하면서 코드 중에 “throw new Error(’not implemented’)”라는 문자열이 있는지 검사합니다.

Step 2. interface 생성

이제 실제로 interface를 정의합니다. 예를 들어, IPlayer라는 인터페이스에 play(name) 메서드와 stop() 메서드가 있다고 할 때, 다음과 같이 player.interface.js 라는 파일에 작성합니다.

 

const {defineInterface} = require('./base.interface')

const IPlayer = defineInterface(class {
    constructor(){
        this._init()
    }
    play(name) {
        throw new Error('not implemented')
    }
    stop() {
        throw new Error('not implemented')
    }
})

module.exports = {
    IPlayer
}

 

생성자에는 defineInterface() 함수 내에 정의한 _init() 함수를 호출하는데, 이를 통해 클래스 내 메서드들이 구현되었는지 검사합니다.

Step 3. interface 구현

이제 작성한 인터페이스를 구현하기 위해 index.js에 다음과 같이 작성해봅니다.

 

const {IPlayer} = require('./interfaces/player.interface')

class RadioPlayer extends IPlayer {
    constructor(){
        super()
    }

    play(name){
        console.log('Radio play ' + name)
    }

    stop(){
        console.log('Radio stop')
    }
}

const radio = new RadioPlayer()
radio.play('노래')

 

RadioPlayer 클래스가 IPlayer interface 메서드인 play()stop()을 구현합니다. 그리고 인스턴스를 생성하여 메서드를 호출하면 다음과 같이 결과가 출력됩니다.

 

Radio play 노래

 

만약, RadioPlayer 클래스에서 stop() 메서드를 구현하지 않고 인스턴스를 생성하면 다음과 같은 에러가 발생합니다.

 

Error: stop is not implemented

마무리

이번에 제안한 방법도 한계가 있습니다. 만약 정의한 인터페이스 내에 throw new Error(’not implemented’)라는 코드를 작성하지 않으면 구현 여부를 검사할 수 없습니다. interface 기능을 제대로 사용하고 싶다면 타입스크립트 사용을 권장합니다.