Composition/Inheritance/Factory - 这种情况下的最佳模式

Composition/Inheritance/Factory - Best Pattern For This Case

好[your_time_of_day],

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

先提问:

有没有办法改变我复杂的继承结构,使 classes 由函数组成,而没有大量的装饰器?我是不是错过了一般的构图(我觉得我错过了)?

情况

我有一个基础class AudioPlayer:

class BaseAudioPlayer {
    public track;
    protected seekBar;

    public togglePlay() {
        //
    }

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

一些玩家 classes 会像这样从这里扩展:

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

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

请记住,我实际上在父子 classes 中有很多方法,并且有些子项中有许多方法,而其他子项中没有。当然,不涉及多重继承,但我看到在某些时候可能会出现多个相似的儿童玩家(糟糕!)。

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

完全公开:我正在使用 Angular2 并大量削减了代码,以保留关于使用哪种设计模式的讨论,而不是关于特定框架中的实现的讨论。

更新 1:

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

我认为如果你想重用base class中的base方法,你可能想使用组合而不是继承(即:将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);
    }
}

为 OP 的给定场景提出最合适的组合方法,实际上取决于如何考虑隐藏和访问数据。基础架构当然应该是 base/sub 类型(类、继承)和基于函数的混合的良好组合。

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

这是我到目前为止的想法...

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; }