본문으로 바로가기

클래스, 믹스인 (Mixin)

category JavaScript 2021. 7. 13. 11:56

믹스인이 무엇인지 살펴보기 전에 클래스의 상속에 대해 알아보고 그 과정에서 어떻게 믹스인이 사용되는지 알아보자

클래스

개발 중 오토바이를 나타내는 클래스가 필요해서 만들었다고 가정하자

class MotoCycle {
    constructor(name) {
        this.name = name;
    }

    // 시동
    startEngine(){ 
        ...
    }
    // 경적
    blowHorn(){
        ...
    }
}

이제 MotoCycle로 만들어진 모든 오토바이 인스턴스는 시동과 경적을 울릴 수 있다

const first = new MotoCycle('first');

first.startEngine(); // broom broom
first.blowHorn(); // honk!

개발을 계속하던 중 자동차 클래스가 필요해져서 자동차 클래스도 추가해서 인스턴스를 생성해주었고

코드는 다음과 같다

class MotoCycle {
    constructor(name) {
        this.name = name
    }

    startEngine(){
        console.log('broom broom');
    }
    blowHorn(){
        console.log('honk!');
    }
}
class CycleCar{
    constructor(name) {
        this.name = name
    }

    startEngine(){
        console.log('broom broom');
    }
    blowHorn(){
        console.log('honk!');
    }
}

const first = new MotoCycle('first');

first.startEngine(); // broom broom
first.blowHorn(); // honk!

const second = new CycleCar('first');

second.startEngine(); // broom broom
second.blowHorn(); // honk!

이제 불편해지는 사람이 생길 단계이다

오토바이와 자동차 클래스에 있는 시동, 경적 메서드는 동일한 코드에 동일한 역할을 수행한다

이런 코드의 중복을 피하기 위해서 우리는 상속을 이용한다

클래스 상속

오토바이와 자동차가 가지고 있는 메서드인 시동과 경적은 운송 수단으로 가지고 있는 특성이다

운송 수단이 공통적으로 가지고 있는 이 메서드들을 추상화시켜서 자식 클래스들(오토바이,자동차)에게 상속받게 하기 위해 부모 클래스를 하나 만들어보자

class Vehicle {
    constructor(name) {
        this.name = name
    }
    startEngine(){
        console.log('broom broom');
    }
    blowHorn(){
        console.log('honk!');
    }
}

class MotoCycle extends Vehicle{
    constructor(args) {
        super(...args);
        this.wheel = 2;
    }
}

class CycleCar extends Vehicle{
    constructor(args) {
        super(...args);
        this.wheel = 4;
    }
}
const first = new MotoCycle('first');

console.log(first.wheel); // 3
first.startEngine(); // broom broom
first.blowHorn(); // honk!


const second = new CycleCar('first');

console.log(second.wheel); // 4
second.startEngine(); // broom broom
second.blowHorn(); // honk!

오토바이와 자동차 클래스의 차이점을 주기위해 각각의 바퀴 수를 추가해주었다

Vehicle이라는 추상 클래스를 선언하고 오토바이와 자동차는 Vehicle를 상속받음으로서 시동과 경적 메서드는 그대로 사용할 수 있게 되었다

이후 메서드에 수정이 필요할 경우에는 Vehicle 클래스에서만 수정해주면 상속받는 모든 클래스들을 한 번에 수정해 줄 수 있다

상속의 문제점

이제 자전거 클래스가 하나 더 필요하게 되었다

자전거도 운송 수단이기 때문에 Vehicle 클래스를 상속 받아야하지만 자전거는 시동을 걸 수가 없다..

그럼 이제 Vehicle 클래스에서 startEngine()를 제거하고 오토바이와 자동차 클래스에 찾아가서 다시 붙여줘야한다

class Vehicle {
    constructor(name) {
        this.name = name
    }

    blowHorn(){
        console.log('honk!');
    }
}

class MotoCycle extends Vehicle{
    constructor(args) {
        super(...args);
        this.wheel = 2;
    }
    startEngine(){
        console.log('broom broom');
    }
}

class CycleCar extends Vehicle{
    constructor(args) {
        super(...args);
        this.wheel = 4;
    }
    startEngine(){
        console.log('broom broom');
    }
}

class biCycle extends Vehicle{
    constructor(args) {
        super(...args);
        this.wheel = 2;
    }
}

지금이야 간단한 예제이기 때문에 쉽게 고칠 수 있지만 만약 이게 큰 프로젝트고 어느정도 진행된 중간 단계였다면 많은 시간을 들여야할 것 같다

아무리 추상화를 잘 했다고 해도 이러한 문제는 얼마든지 발생할 수 있다

이러한 문제를 해결하기 위해 나온 것이 믹스인(Mixin)이다

믹스인(Mixin)

믹스인은 특정 메서드만 담긴 클래스로 다른 클래스와는 달리 그 자체로 쓰이는 경우는 없으며 다른 클래스에 상속되어 쓰일 목적으로 만들어진 상속 전용 클래스라고 생각하면 쉽다

믹스인 구현, 사용하기

믹스인은 메서드를 상속받을 클래스를 인자로 받는 함수형태로 구현되어있다

위에서 골칫덩어리였던 startEngine 메서드를 믹스인으로 구현하면 다음과 같다

function mixinStartEngine (superclass){
    return class extends superclass {
        startEngine (){
            console.log('broom broom');
        }
    }
}

화살표 함수로 구현하기

function mixinStartEngine (superclass){
    return class extends superclass {
        startEngine (){
            console.log('broom broom');
        }
    }
}

// 화살표 함수
const mixinStartEngine = (superclass) => class extends superclass{
    startEngine(){
        console.log('broom broom');
    }
}

사용하기

startEngine 메서드를 상속받을 superclass를 변수로 받아서 그 안에 믹스인 메서드를 추가해 준 뒤 클래스로 리턴하는 방식으로 작동한다

사용법은 다음과 같다

const mixinStartEngine = (superclass) => class extends superclass{
    startEngine(){
        console.log('broom broom');
    }
}
class Vehicle {
    constructor(name,wheel) {
        this.name = name;
        this.wheel = wheel;
    }

    blowHorn(){
        console.log('honk!');
    }
}

class MotoCycle extends mixinStartEngine(Vehicle){
    constructor(...args) {
        super(...args);
    }
}

const moto = new MotoCycle('moto',2);

moto.startEngine(); // broom broom
moto.blowHorn(); // honk!

믹스인 함수는 화살표 함수를 사용해서 좀 더 간단하게 사용할 수 있는 대신 호이스팅이 되지않기 때문에 항상 상위에 있어야한다는 것을 명심하자

두 개 이상의 믹스인 다중 상속

믹스인을 사용해서 리턴되는 클래스를 다시 믹스인 함수의 파라미터로 넣어주면 다중 상속이 가능하다

const mixinStartEngine = (superclass) => class extends superclass{
    startEngine(){
        console.log('broom broom');
    }
}
const mixinBlowHorn = (superclass) => class extends superclass{
    blowHorn(){
        console.log('honk!');
    }
}

class Vehicle {
    constructor(name,wheel) {
        this.name = name;
        this.wheel = wheel;
    }
}

class MotoCycle extends mixinBlowHorn(mixinStartEngine(Vehicle)){
    constructor(...args) {
        super(...args);
    }
}

하지만 상속시킬 믹스인이 많아질수록 상속 구문이 지저분해질 것은 뻔하다

이 곳에서 해결 방법을 찾았는데

 

javascript es6 class 및 mixin 사용해보기

 

blog.seotory.com

class MixinBuilder {
    constructor(superclass) {
        this.superclass = superclass;
    }

    with(...mixins) {
        return mixins.reduce((c, mixin) => mixin(c), this.superclass);
    }
}
let mix = (superclass) => new MixinBuilder(superclass);

class MotoCycle extends mix(Vehicle).with(mixinStartEngine, mixinBlowHorn){
    constructor(...args) {
        super(...args);
    }
}

이런식으로 사용 가능

lodash 라이브러리를 이용해도 동일한 기능을 사용할 수 있다

 

Class Composition in JavaScript

Understand an alternative way to use JavaScript classes for composition instead of inheritance.

alligator.io

 

'JavaScript' 카테고리의 다른 글

Object.defineProperty  (0) 2021.09.06
defer, async 스크립트  (0) 2021.07.12
주요 노드 프로퍼티  (0) 2021.07.09
DOM 수정  (0) 2021.07.08
modal에 keydown 이벤트 추가하기 (tabindex)  (0) 2021.06.02