2016-05-10 14 views
1

私は、インスタンス化するクラスタイプだけでなく、クラスのプロパティのデフォルト値のセットを渡すファクトリメソッドをいくつか用意しています。クラス。クラス型にインスタンス化されていないプロパティを渡そうとするとコンパイラが教えてくれるような型付けをしたいと思います。私はこれを行う方法を理解することはできません。ファクトリメソッドのクラスプロパティのインターフェイスタイプを取得する方法

ここに私がしたいことを示す簡単な例があります。

私が試してみると、「インターフェイスはクラスまたは別のインターフェイスだけを拡張するかもしれない」というコンパイラエラーが発生します。インターフェイスClassPropsの場合、Tから拡張できないためです。

class Base { 
    myId: number; 
} 

class Klass1 extends Base { 
    myString: string; 
    myNumber: number; 
} 

interface IBaseCtr<T> { 
    new(): T; 
} 

// Get an interface that is the properties of the class passed 
interface ClassProps<T extends Base> extends T {} 

// This method compiles, but doesn't check the prop names 
//function factory<T extends Base>(ctrFunc: IBaseCtr<T>, 
//         initialData?: Object): T 

function factory<T extends Base>(ctrFunc: IBaseCtr<T>, 
           initialData?: ClassProps<T>): T 
{ 
    let new_obj = new ctrFunc(); 
    Object.assign(new_obj, initialData); 
    return new_obj; 
} 


let v1 = factory(Klass1, {myId: 10, myString: 'foo'}); 

let v2 = factory(Klass1, {badVar: 10}); 

それはフラグbadVarとして、この2回目の呼び出しでは許可されないようにinitialDataを入力取得する方法任意のアイデア?

+0

興味深い。過去に私はクラスデータを表現するためのインターフェースを構築し、そのクラスを拡張しましたが、ここで十分ではないと思いますか? – Paarth

+0

@Paarth私は他の場所でもそれを行っています。このケースでは、他の開発者がクラスとその他の関連する制約をどのように組み立てているかによって、そのメソッドを使用することはできません。 – Allen

答えて

1

これはすばらしい質問だと思います。

ここに私のアプローチがあります。そのインターフェイスを完全に削除します。

function factory<T extends V, V extends Base>(ctrFunc: IBaseCtr<T>, 
           initialData?: V): T 
{ 
    let new_obj = new ctrFunc(); 
    Object.assign(new_obj, initialData); 
    return new_obj; 
} 

これは階層を設定し、塩基はT(最終的なタイプ)のプロパティのサブセット自体であり、Vのプロパティのサブセットです。 v1はエラーなしで割り当てられます。 v2の代入文では、コンパイル時にエラーが発生します。

extends Baseの部分を実際に削除して、この機能をより多くの場所で適用することができます。それでも同じ方法で型チェックを行います。笑いのために

、正しくチェックを入力し、完全に独立したファクトリ関数のシグネチャ:

function factory<T extends V, V>(ctrFunc: new() => T, 
           initialData?: V): T 
+0

その解決策は素晴らしいです。それは私が欲しいものを正確に行いますが、最初のデータが実際にはソートの「親」であるように、拡張の順序を反転することは決して考えませんでした。よく働く。私はあなたの2番目のフォームを使用するつもりです。ありがとう。 – Allen

+0

近くを見ても、私はまだ2番目を使用しますが、TがBaseを拡張するタイプであることを保証しないという問題があります。(注意:上記の最初のオプションは、Baseからのプロパティを持つinitialDataを設定しているだけなので、実際には機能しません) – Allen

+0

この関数に渡されたものはBaseから拡張する必要があります。 *仕事。何がうんざりしているのか説明できますか? – Paarth

0

私は少し違った、あなたがやったよりも物事をしましたが、最初のコード:

interface BaseProperties { 
    myId: number; 
} 

class Base<T extends BaseProperties> { 
    protected myId: number; 

    init(props: T): void { 
     this.myId = props.myId; 
    } 
} 

interface Klass1Properties extends BaseProperties { 
    myString: string; 
    myNumber: number 
} 

class Klass1 extends Base<Klass1Properties> { 
    private myString: string; 
    private myNumber: number; 

    init(props: Klass1Properties): void { 
     super.init(props); 

     this.myString = props.myString; 
     this.myNumber = props.myNumber; 
    } 
} 

interface IBaseCtr<T> { 
    new(): T; 
} 

function factory<P extends BaseProperties, T extends Base<P>>(ctrFunc: IBaseCtr<T>, initialData?: P): T { 
    let new_obj = new ctrFunc(); 
    new_obj.init(initialData); 
    return new_obj; 
} 


let v1 = factory(Klass1, {myId: 10, myString: 'foo'}); 

let v2 = factory(Klass1, {badVar: 10}); 

これは、あなたにマッチしますfactoryへの2回目の呼び出しでコンパイルエラーが発生するため、

クラスメンバーをprivate/protectedに変更し、Object.assignの代わりにinitメソッドを使用して値を割り当てます。
なぜですか?それはオブジェクト指向のことだからです。この方法では、super.initを使用して値を割り当てるときにクラスの階層を使用できます(または、特定の状況にないと判断した場合は呼び出さない)。
基本的には、このように多くのコントロールがあります。

The code in playground

+0

これは私が頻繁にやっていることですが、Allenのゴールと思われる一般的なファクトリ関数では機能しません。 – Paarth

+0

@Paarthこれがうまくいくことが明らかな遊び場のコードへのリンクを追加しました私が何かを逃していない限り、。 –

+0

私は彼の意図にかかっていると思います。問題の私の解釈は、提示されたクラスは純粋に背景のためであり、必ずしも望ましい階層ではないということでした。私があなたの解決策を正しく理解しているなら、これはあなたが定義した階層ではうまくいくが、他のクラスに対してはもう一度それを設定しなければならない。 – Paarth

関連する問題