问题
Good [your_time_of_day],
Today I have been learning about composition and factory functions in javascript (es6). I understand that composition should be preferred over inheritance and have come to agree with that (at least in javascript). Then, I realised that I have a situation where I should be using composition...
Question first:
Is there a way I can change my complex, inherited structure so classes are composed of functions without having ridiculous numbers of decorators? Have I missed something about composition in general (I feel like I have)?
Situation
I have a base class AudioPlayer:
class BaseAudioPlayer {
public track;
protected seekBar;
public togglePlay() {
//
}
public seek(time) {
//some seek methods using this.seekBar
}
}
And a few player classes would extend from this like so:
class MainAudioPlayer extends BaseAudioPlayer {
public loadTrack(track) {
//This is horrible
this.track = track;
}
public setSeekBar(seekBar) {
//This is horrible
this.seekBar = seekBar
}
}
Please bare in mind I actually have a lot more methods in parent and child classes and there are many methods in some children that are not in others. Of course, there is no multiple inheritance involved but I see at some point that might become a possibility with multiple alike child players (bad!).
I could use many decorators like @playable()
@seekable()
etc. but then I see that, eventually, the number of mixins would become huge. I guess I could also use factory functions in a similar manner but see the same problem.
Full disclosure: I am using Angular2 and have cut back the code a lot to keep any discussion about which design pattern to use and not about an implementation in a specific framework.
Update 1:
As @JocelynLecomte commented, my question may be unclear.
The MainAudioPlayer (and other players) inherit from BaseAudioPlayer since all audio players must have
togglePlay
,seek
, and a few other methods (angular2 specific so not included here).Currently, there are three classes that inherit from BaseAudioPlayer: MainAudioPlayer, DetailAudioPlayer and CardAudioPlayer. In the future there may be more and each has there own specific methods.
Inheritance was used to avoid duplication and all players are BaseAudioPlayers. However, all players also have
togglePlay
andseek
methods.I'd like to use composition since I could see a possibility of a player that does not have a
seek
method or something along those lines in the future.It seems to that using composition would lead to a lot of boiler plate code in all player classes and I'd like to avoid this.
- Could have many decorators (
@playable
,@seekable
) - Could use a base player service (as in @amuse 's answer) and have redundant methods.
- Could have many decorators (
回答1:
I think if you want to reuse the base method in base class,you may want to use composition instead of inheritance (ie:define BasePlayerComponent as a property of 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);
}
}
回答2:
Coming up with the best fitting composition approach for the OP's given scenario, really depends on how data is considered being hidden and accessed. The base architecture of course should be a good mix of base/sub types (classes, inheritance) and function based mixins.
Thus the approach shown with the next given example code is a direct result of what the OP did provide within the BaseAudioPlayer
, a public track
and a protected seekBar
. Changing visibility and read write access of such properties will have a big impact of how all classes and mixins then need to be refactored accordingly.
Here is, what I have come up with so far ...
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; }
来源:https://stackoverflow.com/questions/40895466/composition-inheritance-factory-best-pattern-for-this-case