组合/继承/工厂 - 本案例的最佳模式

时间:2016-11-30 18:18:31

标签: javascript inheritance design-patterns composition

好[your_time_of_day],

今天我一直在学习javascript(es6)中的构图和工厂函数。我理解composition should be preferred over inheritance并且已经同意(至少在javascript中)。然后,我意识到我的情况是我应该使用合成......

问题第一:

有没有办法可以改变我复杂的,继承的结构,所以类是由函数组成而没有荒谬的装饰器数量?我是否错过了一般的构图(我觉得我有)?

情况

我有一个基类AudioPlayer:

class BaseAudioPlayer {
    public track;
    protected seekBar;

    public togglePlay() {
        //
    }

    public seek(time) {
       //some seek methods using this.seekBar
    }
}

一些玩家类会像这样延伸:

class MainAudioPlayer extends BaseAudioPlayer {
    public loadTrack(track) {
        //This is horrible
        this.track = track;
    }

    public setSeekBar(seekBar) {
        //This is horrible
        this.seekBar = seekBar
    }
}

请记住我实际上在父类和子类中有更多的方法,并且在一些孩子中有许多方法不在其他孩子中。当然,没有涉及多重继承,但我认为在某些情况下可能会成为多个相同的儿童玩家的可能性(糟糕!)。

我可以使用许多装饰器,如@playable() @seekable()等,但后来我发现,最终,mixin的数量会变得很大。我想我也可以以类似的方式使用工厂功能,但看到同样的问题。

完全披露:我正在使用Angular2并且已经大量削减了代码,以便继续讨论使用哪种设计模式,而不是关于特定框架中的实现。

更新1:

正如@JocelynLecomte评论的那样,我的问题可能不清楚。

  • MainAudioPlayer(和其他播放器)继承自BaseAudioPlayer,因为所有音频播放器必须具有togglePlayseek和其他一些方法(特定于angular2,因此不包括在内)。

  • 目前,有三个类继承自BaseAudioPlayer:MainAudioPlayer,DetailAudioPlayer和CardAudioPlayer。在未来可能会有更多,每个都有自己的具体方法。

  • 继承用于避免重复,所有玩家都是 BaseAudioPlayers。但是,所有玩家都有 togglePlayseek方法。

  • 我想使用合成,因为我可以看到玩家未来可能没有seek方法或类似的东西。

  • 似乎使用合成会在所有玩家类中产生大量的锅炉板代码,我想避免这种情况。

    • 可以有很多装饰器(@playable@seekable
    • 可以使用基本播放器服务(如@amuse的回答)并且有多余的方法。

2 个答案:

答案 0 :(得分:2)

我认为如果你想在基类中重用基本方法,你可能想要使用组合而不是继承(即:将BasePlayerComponent定义为MainAudioPlayer的属性):

class MainAudioPlayer{
    constructor(){
    this.basePlayerComponent=new BasePlayerComponent();
    }
    public loadTrack(track) {
        //This is horrible
        this.track = track;
    }

    public setSeekBar(seekBar) {
        //This is horrible
        this.seekBar = seekBar
    }

    public togglePlay() {
        this.basePlayerComponent.togglePlay();
    }

    public seek(time) {
        this.basePlayerComponent.seek(time);
    }
}

答案 1 :(得分:1)

为OP的给定方案提出最佳拟合组合方法,实际上取决于如何隐藏和访问数据。当然,基础架构应该是基础/子类型(类,继承)和基于函数的mixin的良好组合。

因此,下一个给定示例代码所示的方法是OP在BaseAudioPlayerpublic trackprotected seekBar内提供的直接结果。更改此类属性的可见性和读写访问权将对所有类和mixin的相应重构方式产生重大影响。

这是我到目前为止所提出的......



function withTrackManagement(stateValue) {          // composable fine grained behavioral unit of reuse (mixin/trait).
  var
    defineProperty = Object.defineProperty;

  // writing the protected `track` value.
  function loadTrack(track) {
    return (stateValue.track = track);
  }
  // public `loadTrack` method (write access).
  defineProperty(this, 'loadTrack', {
    value     : loadTrack,
    enumerable: true
  });
}

function withSeekBar(stateValue) {                  // composable fine grained behavioral unit of reuse (mixin/trait).
  var
    defineProperty = Object.defineProperty;

  // writing the protected `seekBar` value.
  function setSeekBar(seekBar) {
    return (stateValue.seekBar = seekBar);
  }
  // public `setSeekBar` method (write access).
  defineProperty(this, 'setSeekBar', {
    value     : setSeekBar,
    enumerable: true
  });
}


class BaseAudioPlayer {                             // base type.
  constructor(stateValue) {
    var
      defineProperty = Object.defineProperty;

    // reading the protected `track` value.
    function getTrack() {
      return stateValue.track;
    }

    function togglePlay() {
      //
    }
    function seek(time) {
      // some seek methods using `stateValue.seekBar`
    }

    // public protected `track` value (read access).
    defineProperty(this, 'track', {
      get       : getTrack,
      enumerable: true
    });

    // public `togglePlay` method.
    defineProperty(this, 'togglePlay', {
      value     : togglePlay,
      enumerable: true
    });
    // public `seek` method.
    defineProperty(this, 'seek', {
      value     : seek,
      enumerable: true
    });

  }
}

class MainAudioPlayer extends BaseAudioPlayer {     // composite type ... extended class with mixin/trait composition.
  constructor(stateValue) {
    stateValue = (
         ((stateValue != null) && (typeof stateValue == "object") && stateValue)
      || {}
    );
    super(stateValue);

    withTrackManagement.call(this, stateValue);
    withSeekBar.call(this, stateValue);
  }
}


var mainPlayer = (new MainAudioPlayer);

console.log("mainPlayer : ", mainPlayer);
console.log("mainPlayer.track : ", mainPlayer.track);

console.log("(mainPlayer.track = 'foo bar') : ", (mainPlayer.track = 'foo bar'));
console.log("mainPlayer.track : ", mainPlayer.track);

console.log("mainPlayer.loadTrack('favourit track') : ", mainPlayer.loadTrack('favourit track'));
console.log("mainPlayer.track : ", mainPlayer.track);

console.log("mainPlayer : ", mainPlayer);


class DetailAudioPlayer extends BaseAudioPlayer {   // composite type ... extended class with mixin/trait composition.
  constructor(stateValue) {
    stateValue = (
         ((stateValue != null) && (typeof stateValue == "object") && stateValue)
      || {}
    );
    super(stateValue);                              // - extending/sub-typing.

  //withSpecificBehavior.call(this, stateValue);    // - composition.
    withTrackManagement.call(this, stateValue);     //
                                                    //
  //withOtherBehavior.call(this, stateValue);       //
  }
}

class CardAudioPlayer extends BaseAudioPlayer {     // composite type ... extended class with mixin/trait composition.
  constructor(stateValue) {
    stateValue = (
         ((stateValue != null) && (typeof stateValue == "object") && stateValue)
      || {}
    );
    super(stateValue);                              // - extending/sub-typing.

  //withSpecificBehavior.call(this, stateValue);    // - composition.
    withSeekBar.call(this, stateValue);             //
                                                    //
  //withOtherBehavior.call(this, stateValue);       //
  }
}

.as-console-wrapper { max-height: 100%!important; top: 0; }