typescriptは、extendsによる多重相続を提供しないです。
その代わり、mixin という手法を用いて、多重継承を実現させることができます。
mixinもクラスを利用して継承を実現するが、クラスのextendsを使用しません。
クラスをまるで関数のように、あるいは単独のオブジェクトのように眺めながら、
必要な場合ごとにクラスを合成して新しい機能に拡張していく技法です。
そのため、extends を使用するときのように、クラスでのクラスの上下関係は不明です。
まず、extendsを通じたクラス、NewsFeedApiとNewsDetailApiはApiのgetRequestを呼び出しています。
extends
class Api {
ajax: XMLHttpRequest;
url: string;
constructor(url: string) {
this.ajax = new XMLHttpRequest();
this.url = url;
}
protected getRequest<AjaxResponse>(): AjaxResponse {
this.ajax.open('GET', url, false);
this.ajax.send();
return JSON.parse(this.ajax.response);
}
}
class NewsFeedApi extends Api{
getData(): NewsFeed[] {
return this.getRequest<NewsFeed[]>();
}
}
class NewsDetailApi extends Api{
getData(): NewsDetail {
return this.getRequest<NewsDetail>();
}
}
//呼び元
NewsFeedApi(URL);
NewsDetailApi(URL);
下はmixin技法。
mixinは、extendsのように機能を直接的に提供せず、コードテクニックで展開される手法です。
extendsを使わないのでNewsFeedApiとNewsDetailApiがapiとどのように関係を結ぶかを
mixinを通じて具現します。
クラスを関数あるいは単独オブジェクトとして眺めるため、constructorやextends を削除します。
mixin
class Api {
getRequest<AjaxResponse>(url: string): AjaxResponse {
const ajax: XMLHttpRequest= new XMLHttpRequest();
ajax.open('GET', url, false);
ajax.send();
return JSON.parse(ajax.response) as AjaxResponse;
}
}
class NewsFeedApi {
getData(): NewsFeed[] {
return this.getRequest<NewsFeed[]>(NEWS_URL);
}
}
class NewsDetailApi {
getData(): NewsDetail {
return this.getRequest<NewsDetail>(NEWS_URL);
}
}
//呼び元
NewsFeedApi();
NewsDetailApi();
applyApiMixinsはtargetClassがN個baseClassesをextendsする関数です。
そのため、baseClassesは配列型になります。
applyApiMixinsはtypescriptが提供するコードなので、安全に使えます。
mixin
// Apiを使いますとの定義
interface NewsFeedApi extends Api {};
interface NewsDetailApi extends Api {};
applyApiMixins(NewsFeedApi, [Api]);
applyApiMixins(NewsDetailApi, [Api]);
/**
* @param targetClass
* @param baseClasses
*/
function applyApiMixins(targetClass: any, baseClasses: any[]): void {
baseClasses.forEach(baseClass => {
Object.getOwnPropertyNames(baseClass.prototype).forEach(name => {
const descriptor = Object.getOwnPropertyDescriptor(baseClass.prototype, name);
if (descriptor) {
Object.defineProperty(targetClass.prototype, name, descriptor);
}
});
});
}