I'm writing an app that manages playlists. Basically, my actual logic looks like
//define playlist props
class Playlist{
public tracks = [];
}
class ApiPlaylist extends Playlist{
//fill playlist withs (single page) data from API
public async loadPage(paginationSettings){
const pageTracks = await...
this.tracks = [...this.tracks,...pageTracks]; //concat tracks
}
}
class Paginated extends ApiPlaylist{
private iterations = 0;
private endReached = false;
public async loadAll(){
while (!this.endReached){
this.loadNext();
}
}
public async loadNext(){
this.iterations++;
await this.loadPage(); //call ApiPlaylist method
if(...){
this.endReached = true; //stop iterating
}
}
}
const playlist = new Paginated();
playlist.loadAll();
It works.
But what if I have different others paginated datas to get, that are not related to playlists ?
I would like to use the mechanism from PaginatedPlaylist with another classes, without having to duplicate it.
Acually, Paginated extends ApiPlaylist.
Is there a simple way to implement the methods from Paginated to ApiPlaylist without using extends ?
Something like
class ApiPlaylist [implements] Paginated{}
class ApiPost [implements] Paginated{}
class ApiPage [implements] Paginated{}
Thanks for your help !
As it already has been pointed out, the correct pattern to use is mixin or trait based composition.
The next provided example code demonstrates the usage of a function-based mixin which implements and applies the feature/behavior of "loadable tracks".
The above example code did not provide a full scale implementation of what the OP wants to achieve. Trying the latter shows design flaws in the OP's modeling.
In my opinion, the sole
loadPagemethod of the OP'sApiPlaylistimplementation, where the latter extendsPlaylist, collides with theloadNextmethod of the OP'sPaginatedimplementation, wherePaginatedextendsApiPlaylist.loadNextforwards toloadPageand is all of a sudden smart enough of being able to provide additional informations about possibly next loadable tracks.But actually this information can only be provided from the API-call's return value; therefore
loadPagewhich triggers this call already has all the necessary data first hand. Thus,loadPageneeds to implement some, if not all characteristics that come with the OP's originalloadNextwhich renders the latter method obsolete.The next provided code uses a more advanced variant of a function based mixins which is capable of privately sharing locally encapsulated, mutable state (thus the latter can not be accessed from outside).
Any of the features, intended by the OP, are implemented within and applied through this mixin which therefore is named
withLoadablePaginatedTracks.Relying on
bindnaturally supports default configurations for the OP'spaginationSettingsparameter which gets passed toloadPage.In addition one too can pass said pagination configuration to any constructor function which is going to apply this mixin. In case
loadPageandloadAlldo not receive such a parameter, the default settings (passed at instantiation time or from the internal defaults) take place.The above presented example code exclusively composes features through a function based mixin where the mixin-function is responsible for the delegation.
Though it is a lean approach for JavaScript's pure vanilla flavor, the pattern is not suitable for TypeScript. So called "TypeScript Mixins" are based on dynamically sub-typed/subclassed classes, thus on pure inheritance.
Another answer to the OP's Q. already did mention this pattern, just missing on its correct technical terminology.
Further reading on that at SO (self promotion warning)
"How to implement a class creation pattern where one class would conditionally extend any other given class in Typescript?"
"How to dynamically create a subclass from a given class and enhance/augment the subclass constructor's prototype?"
"How to implement Web-API EventTarget functionality into a class which already extends another class?"
The next provided example code straightforwardly refactors the mixin-function based composition from above into a pure inheritance driven solution. And for this special pattern I'd like to coin the term "Interpositioned Dynamic Subclassing" which is supposed to replace the formerly used, less specific "dynamic subclassing/sub-typing".
(with the end of logging one gets presented an overview of the entire inheritance involved)
Conclusion
Though some might see it as a merely personal opinion, it seems that any mixin approach is better suited for solving any of the OP's problems.
The advantage comes with a clear dependency free implementation of specific functionality. In case an object needs to have specific behavior it gets applied/mixed-in ("has a" indicates composition).
In comparison, an inheritance driven solution, regardless whether sub-typing is involved or not, depends on a correctly build inheritance/prototype chain, thus it is less free when it comes to the implementation and especially application of behavior because it comes with implicitly made assumptions or to be met requirements which are not that obvious at first.
In addition, the reading of how one creates and names sub-typed classes appears clumsy, because one finds oneself in the need of having to more specifically to describe the class name, which actually should be a noun (a class or instance of it "is a" thing) like
Paginator, as soon as it acquires more specialized skills. All of a suddenPaginatororPaginationspecializes towardsLoadablePaginatedTracksbeing created by a process calledasLoadablePaginatedTracks.There is not such an overhead with any of the established mixin patterns that are true composing patterns.