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 评论的那样,我的问题可能不清楚。
MainAudioPlayer(和其他播放器)继承自 BaseAudioPlayer,因为所有音频播放器都必须具有 togglePlay
、seek
和一些其他方法(特定于 angular2,因此此处未包含).
目前有3个class继承自BaseAudioPlayer:MainAudioPlayer、DetailAudioPlayer和CardAudioPlayer。将来可能会有更多,每个都有自己的特定方法。
继承用于避免重复,所有播放器都是 BaseAudioPlayers。然而,所有玩家也有togglePlay
和seek
方法。
我想使用组合,因为我可以看到一个玩家在未来可能没有 seek
方法或类似的东西。
似乎使用组合会导致所有播放器 classes 中出现大量样板代码,我想避免这种情况。
- 可以有很多装饰器(
@playable
、@seekable
)
- 可以使用基本播放器服务(如@amuse 的回答)并使用冗余方法。
我认为如果你想重用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 在 BaseAudioPlayer
、public track
和 protected 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; }
好[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 评论的那样,我的问题可能不清楚。
MainAudioPlayer(和其他播放器)继承自 BaseAudioPlayer,因为所有音频播放器都必须具有
togglePlay
、seek
和一些其他方法(特定于 angular2,因此此处未包含).目前有3个class继承自BaseAudioPlayer:MainAudioPlayer、DetailAudioPlayer和CardAudioPlayer。将来可能会有更多,每个都有自己的特定方法。
继承用于避免重复,所有播放器都是 BaseAudioPlayers。然而,所有玩家也有
togglePlay
和seek
方法。我想使用组合,因为我可以看到一个玩家在未来可能没有
seek
方法或类似的东西。似乎使用组合会导致所有播放器 classes 中出现大量样板代码,我想避免这种情况。
- 可以有很多装饰器(
@playable
、@seekable
) - 可以使用基本播放器服务(如@amuse 的回答)并使用冗余方法。
- 可以有很多装饰器(
我认为如果你想重用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 在 BaseAudioPlayer
、public track
和 protected 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; }