コンストラクタをどのように分解できますか?


21

私にはEnemyクラスがあり、コンストラクタは次のようになります。

public Enemy(String name, float width, float height, Vector2 position, 
             float speed, int maxHp, int attackDamage, int defense... etc.){}

コンストラクターには非常に多くのパラメーターがあるため、これは悪いように見えますが、Enemyインスタンスを作成するときは、これらすべてを指定する必要があります。また、これらの属性をEnemyクラスに含めて、それらのリストを反復処理し、これらのパラメーターを取得/設定できるようにします。EnemyをEnemyB、EnemyAにサブクラス化して、maxHpなどの特定の属性をハードコーディングすることを考えていましたが、Enemyのリスト(EnemyA、EnemyB、およびEnemyC's)。

私はただきれいにコーディングする方法を学ぼうとしています。違いがあれば、Java / C ++ / C#で作業しています。正しい方向の任意のポイントが高く評価されています。


5
すべての属性をバインドする1つのコンストラクターを作成しても何も悪いことはありません。実際、一部の永続化環境では必要です。おそらく、ピース単位の構築を行った後に呼び出される有効性チェックメソッドを使用して、複数のコンストラクターを作成できないということはありません。
BobDalgleish

1
リテラルを使用してコード内にEnemyオブジェクトを構築するつもりがあるかどうかを質問する必要があります。そうでない場合、そしてなぜそうなるのかわからない場合は、データベースインターフェイス、シリアル化文字列、または...からデータをプルするコンストラクターを構築します。
Zan Lynx 14年


回答:


58

解決策は、パラメーターを複合型にバンドルすることです。幅と高さは概念的に関連しています-それらは敵の寸法を指定し、通常一緒に必要になります。これらは、Dimensionsタイプ、またはRectangle位置を含むタイプに置き換えることができます。一方、特に加速度が後で画像に入る場合は、グループpositionspeedしてMovementDataタイプに分類する方が理にかなっている場合があります。私は仮定コンテキストからmaxHpattackDamagedefense、なども一緒に属しているStatsタイプ。したがって、改訂された署名は次のようになります。

public Enemy(String name, Dimensions dimensions, MovementData movementData, Stats stats)

線を描画する場所の詳細は、コードの残りの部分と、通常一緒に使用されるデータによって異なります。


21
また、非常に多くの値を持つことは、単一責任原則の違反を示している可能性があると付け加えます。また、特定のオブジェクトに値をグループ化することは、これらの責任を分離するための最初のステップです。
陶酔14年

2
値のリストがSRPの問題だとは思わない。それらのほとんどは、おそらく基本クラスのコンストラクタを対象としています。階層内の各クラスは、単一の責任を持つことができます。EnemyはをターゲットとするクラスですPlayerが、共通の基本クラスCombatantには戦闘の統計情報が必要です。
MSalters

@MSalters必ずしもSRPの問題を示しているわけではありませんが、可能性はあります。彼が十分な数の計算を行う必要がある場合、それらの関数は静的/自由関数(単純な古いデータコンテナとしてDimensions/ を使用する場合MovementData)またはメソッド(彼がそれらを抽象データに変換する場合)タイプ/オブジェクト)。例として、彼がまだVector2型を作成していなかった場合、彼はでベクトル演算を行うことになったかもしれませんEnemy
ドーバル14年

24

Builderパターンを確認してください。リンクから(パターンと代替案の例を示します):

[コンストラクタ] Builderパターンは、コンストラクターまたは静的ファクトリーに少数のパラメーターが含まれるクラスを設計する場合、特にそれらのパラメーターのほとんどがオプションである場合に適しています。クライアントコードは、従来のテレスコープコンストラクターパターンよりもビルダーを使用して読み書きする方がはるかに簡単であり、ビルダーはJavaBeansよりもはるかに安全です。


4
短いコードスニペットが役立ちます。これは、さまざまな入力を持つ複雑なオブジェクトまたは構造を構築するための優れたパターンです。EnemyABuilder、EnemyBBuilderなど、さまざまな共有プロパティをカプセル化するビルダーを専門にすることもできます。これはFactoryパターンの裏返しのようなものですが(以下で回答します)、個人的な好みはBuilderです。
ロブ14年

1
おかげで、BuilderパターンとFactoryパターンの両方が、私が全体的にやろうとしていることでうまく機能するように見えます。Builder / FactoryとDovalの提案の組み合わせが私が探しているものだと思います。編集:答えは1つしかマークできないと思います。トピックの質問に答えているのでDovalに渡しますが、他の質問も同様に私の特定の問題に役立ちます。皆さん、ありがとうございました。
トラビス14年

ご使用の言語がファントムタイプをサポートしている場合、SetX関数の一部またはすべてが呼び出されることを強制するビルダーパターンを作成できることに注意する価値があると思います。また、1回だけ呼び出されるようにすることもできます(必要な場合)。
トーマスエディング14年

1
@ Mark16リンクで述べたように、> Builderパターンは、AdaおよびPythonにある名前付きオプションパラメーターをシミュレートします。質問でC#も使用していると述べましたが、その言語は名前付き/オプションの引数(C#4.0以降)をサポートしているため、別のオプションになる可能性があります。
ボブ14年

5

サブクラスを使用して一部の値を事前設定することは望ましくありません。新しいタイプの敵の行動や属性が異なる場合にのみサブクラスを作成します。

工場出荷時のパターンは、通常使用される正確なクラスの上に抽象化するために使用されているが、また、オブジェクトの作成のためのテンプレートを提供するために使用することができます。

class EnemyFactory {

    // each of these methods is essentially a template for a kind of enemy

    Enemy enemyA(String name, ...) {
        return new Enemy(name, ..., presetValue, ...);
    }

    Enemy enemyB(String name, ...) {
        return new Enemy(name, ..., otherValue, ...);
    }

    Enemy enemyC(String name, ...) {
        return new EnemySubclass(name, ..., otherValue, ...);
    }

    ...
}

EnemyFactory factory = new EnemyFactory();
Enemy a = factory.enemyA("fred", ...);
Enemy b = factory.enemyB("willy", ...);

0

独立して使用したいオブジェクトを表すクラスへのサブクラスを予約します。たとえば、敵だけでなく、すべてのキャラクターが名前、速度、maxHpを持つキャラクタークラス、または画面上に幅のある存在を持つスプライトを表すクラス、高さ、位置。

多くの入力パラメーターを持つコンストラクターに本質的に問題はありませんが、少し分割したい場合は、ほとんどのパラメーターを設定する1つのコンストラクターと、使用可能な別の(オーバーロードされた)コンストラクターがあります特定の値を設定し、他の値をデフォルト値に設定します。

使用する言語に応じて、コンストラクターの入力パラメーターに次のようなデフォルト値を設定できるものがあります。

Enemy(float height = 42, float width = 42);

0

Rory Hunterの回答に追加するコード例(Java):

public class Enemy{
   private String name;
   private float width;
   ...

   public static class Builder{
       private Enemy instance;

       public Builder(){
           this.instance = new Enemy();
       }


       public Builder withName(String name){
           instance.name = name;
           return this;
       }

       ...

       public Enemy build(){
           return instance;
       }
   }
}

これで、次のようにEnemyの新しいインスタンスを作成できます。

Enemy myEnemy = new Enemy.Builder().withName("John").withX(x).build();

1
プログラマーは概念的な質問をツアーし、答えは物事説明すること期待されています。説明の代わりにコードダンプをスローすることは、コードをIDEからホワイトボードにコピーするようなものです。ホワイトボードには、コンパイラを持っていません
ブヨ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.