web-dev-qa-db-ja.com

継承よりも構成、なぜ両方ではないのですか?

私はこれを状況外シナリオで使用します(ここで良い習慣と思うものは私を状況に残しますインターフェイスの実装と、実装を行うための構成の使用の両方の。


以下を想像してみてください:

私はCharacterHealthおよびManaを持っています。

interface ICharacter
{
    Health Health { get; }
    Mana Mana { get; }
}

class Character : ICharacter
{
    public Health Health { get; private set; }
    public Mana Mana { get; private set; }

    public Character(Health health, Mana mana)
    {
        this.Health = health;
        this.Mana = mana;
    }
}

次に、HealthManaは一緒に属しているため、1つのオブジェクトに結合する必要があると判断します。

interface IResource
{
    Health Health { get; }
    Mana Mana { get; }
}

Character

interface ICharacter
{
    IResource Resource { get; }
}

class Character : ICharacter
{
    public IResource Resource { get; private set; }

    public Character(IResource resource)
    {
        this.Resource = resource;
    }
}

ただし、Characterのクライアントがcharacter.Health.Currentと言わずに最小知識の原則に違反せずにキャラクターの健康にアクセスできるようにするために欲しいCharacterこの情報を提供します。そして、カプセル化を改善するために、CharacterHealthManaをどのように格納するかは、次のように開示しません。

interface ICharacter : IResource
{
}

class Character : ICharacter
{
    private IResource resource;

    public Health Health
    {
        get
        {
            return resource.Health;
        }
    }

    public Mana Mana
    {
        get
        {
            return resource.Mana;
        }
    }

    public Character(IResource resource)
    {
        this.resource = resource;
    }
}

これにより、私のCharacterHealthManaに戻るようになりますが、Characterは、構成されているのと同じインターフェイスを実装しています。


実装では、これは Decorator Pattern によく似ていますが、意図的には共通点はありません。

質問

  1. これはpattern/antipatternであり、クラスが構成されているのと同じインターフェースを実装することを認識し、そうであれば、それは何と呼ばれますか?

  2. IResourceにプロパティを持たせるよりも、これは良いアイデアだと思いますか? -理由/理由がない

  3. これが決定的なノーゴーである、警戒すべき状況はありますか?

Sidenote-ICharacterには確かに多くのメソッドがありますが、短くして簡潔にするために、ここでは関連する部分のみを含めました。

2
Chris Wohlert

継承よりも構成、なぜ両方ではないのですか?

原則は「継承よりもprefer構成」として正しく引用されています。どちらもできないと言った人はいません。時々あなたは両方をしなければならない。しかし、選択肢を考えると、通常、構成はより良いものです。

あなたのIResourceは、あなたが言及しているものとは異なるパターンを思い出させます。 パラメータオブジェクト は、依存関係を一緒にバンドルすることにより、オブジェクトの構築を容易にします。

極端な場合、この service locator のスマックには注意してください。状況によってはこれが悪い場合があります。

デメテルの法則 を尊重することは、よく理解していれば良いことです。ここで問題が発生していますが、問題はありません。 Characterが集計以外のことをしていないことが明らかになるまで、ヘルスとマナへのアクセスを簡略化する必要があります。

不足しているのは抽象化です。確かにCharacterにはもっと多くのメソッドがあると約束しましたが、私がここで見る限り、Characterは不必要な間接指示以外は何も追加していません。

現時点では、Characterは、脳死値オブジェクト(文字列など)のように見えます。それがそうなるのであれば、これらの値の独自のコピーを与え、それを平坦化します。

Characterが動作オブジェクトになる場合、Healthはカプセル化によって非表示にされ、Wound(int)Die()などのメソッドを使用してやり取りされることを期待しますGetHealth()ではありません。これは Tell、do n't ask の後に続きます。キャラクターの健康状態は、他人の仕事ではありません。これは実際のカプセル化です。

どちらの場合でも、Characterクラスは、キャラクターの詳細を処理する途中で一時停止するべきではありません。これはそれらを解決するための場所でなければなりません。私にそれを掘り過ぎさせないでください。

9
candied_orange

あなたは健康とマナを一緒に束ねることからどのような利益を得ていますか?これらは、私にとっては値のように聞こえ、属性を分離しますが、値であるかオブジェクトであるかに関係なく、代わりにプロパティとして設定できます。これらは、キャラクターに対して簡単にチェックされることを意図しています。

はじめて良かったと思います。マナ/ヘルスに、単なる値以上の原因となる動作またはルールがある可能性がある場合、それらのインターフェイスに対してプログラムする必要があると思います(これにより、キャラクターの動作を単体テストするときにそれらを模擬できます)。

interface ICharacter
{
    IMana Mana { get; }
    IHealth Health { get; }
}

public class Character : ICharacter
{
    public IMana Mana { get; }
    public IHealth Health { get };

    public Character(IMana mana, IHealth health)
    {
        Mana = mana;
        Health = health;
    }
}

それらが単なる値である場合は、それらを値にするだけです。値アプローチを使用すると、コンストラクタにそれらを提供する必要さえない場合があります。妥当なデフォルト値をそのまま使用できます。カプセル化が役立つようになったところで、公開されたメソッド内でヒーリングとマナのロジックを焼き込むことができ、呼び出しコードは、公開されたメソッドを使用しないとヘルスとマナを変更できません。

interface ICharacter
{
    int Mana { get; }
    int Health { get; }
    void Heal(int numPotions);
    void RejuvenateMana(int numPotions);
}

public class Character : ICharacter
{
    public int Mana { get; private set; } // default is 0
    public int Health { get; private set; } = 100;

    public void Heal(int numPotions) 
    {
        Health += numPotions;//todo: add real logic
    }

    public void RejuvenateMana(int numPotions)
    {
        Mana += numPotions;//todo: add real logic
    }

}

異なる動作やルールがあることに気づいたら、それらのルールを依存関係として追加するか、文字タイプ間で動作を変える他の方法が必要になるでしょう。すべてがどのように連携するかに応じて、癒し、若返り、ダメージを受けるなどが別のオブジェクトによって処理される可能性があり、その場合、ICharacterが何かによって作用される場合は、ヘルス/マナをパブリックに割り当て可能なプロパティにしたいと思うでしょう。そうしないと。そうでなければ、依存関係としてヘルス/マナを計算する依存関係を提供することが賢明かもしれません。

IResourceにプロパティを持たせるよりも、これは良いアイデアだと思いますか? - なぜ?/何故なの?

これらは、キャラクターの2つの別個の属性のように思えます。それらを組み合わせると、組織化のためだけにそうなると思います。呼び出しコードは、すぐに利用できる情報を不必要にDigする必要があります。

このパターン/クラスを構成するのと同じインターフェイスを実装するアンチパターンは認識されていますか?そうであれば、それは何と呼ばれますか?

私はこれを過度にエンジニアリングし、過度に一般化していると言います。この場合、IResourceは非常に一般的なものになりましたが、実際には単一の意味がなく、一般化されすぎていて、十分に分解されていないため、インターフェース分離の原則に違反しています。インターフェースを実装し、同じインターフェースを実装する依存関係を取り込むことで行うことは一般的ですが、状況によっては、一部のプログラミング言語では、すべてのオブジェクトがObjectから継承するか、Objective-Cの場合すべて継承元のNSObjectクラスとNSObjectプロトコル(インターフェース)があります。このパターンは、以前はすべてのものを焼き付けるために使用されていましたが、通常、すべてが同じ正確なプロパティを持つ必要がある場合にのみ機能します(つまり、インターフェイスコンポーネントは常にx、y位置などを持ちます)。

これが決定的なノーゴーである、警戒すべき状況はありますか?

前に述べたように、これはISPを壊すような気がします。クライアントは、必要のないものに依存する必要はありません。文字がリソースの場合、ICharacterはIResourceインターフェイスのすべてを継承するため、IResourceから独立して文字を変更することはできません。そして、ICharacterは、IResourceがこのような一般的な方法で使用され、ICharacterに属さないより多くの特性が追加される場合、必要のないIResource特性を持つ可能性があります。


編集:Characterクラスが他のクラスとどのように相互作用するかを実際に見たいです。たとえば、2文字を受け取り、属性を調べて、Health、Agility、Weaponなどの属性に基づいて適用されるダメージの量を確認するBattleCalculatorクラスがあった場合、マナについて知る必要はありません(マナは他のキャラクター属性を変更するために使用されていると仮定します)。その場合、インターフェイスをさらに分割して、マナ関連のものを別のインターフェイスに置くことができます。これにより、戦闘計算機はそれについて知る必要がなくなります。これは、すべてのキャラクターがマナを持つことを前提としています。

public interface IManaBearer 
{ 
    IMana Mana { get; } 
}
public interface ICharacter { ... }
public class Character: IManaBearer, ICharacter {...}

public class CharacterBattleCalculator
{
    public CharacterBattleCalculator(ICharacter player1, ICharacter player2)
    {
        ...
    }
    ...
}
4
morbidhawk

このパターン/クラスを構成するのと同じインターフェイスを実装するアンチパターンは認識されていますか?そうであれば、それは何と呼ばれますか?

Proxy または Dectorator です。プロキシは通常、内部プロパティのように、メソッド呼び出しを別のクラスまたはインスタンスに転送するだけの WrappersまたはAdapters を参照します。そして、デコレータは基本的にadditionalメソッドまたは「オーバーライド」に付属するプロキシです。

IResourceにプロパティを持たせるよりも、これは良いアイデアだと思いますか? - なぜ?/何故なの?

それ自体は、neutralのアイデアです。それは命名に帰着します。 ICharacterIResourceである場合、それは良いアイデアです。それだけIResourceを持っている場合、それはひどい考えです。

これが決定的なノーゴーである、警戒すべき状況はありますか?

セマンティックフィットではない場合に加えて、ラッピングを維持するのが難しい場合も悪い考えです。これは、内部インターフェイスが揮発性の場合(まだ開発中)である可能性があります。 ...「知識が最小の原則」が私の唯一の正当な理由である場合は特に、プロキシやデコレータも個人的に避けます。特に、大きなインターフェースの場合はそうです。

継承よりも構成、なぜ両方ではないのですか?

あなたの場合では、あなたは実際には両方ではないので!

クラスは継承されます。 インターフェース実装されています。したがって、「構成と実装インターフェースの両方」を実行していることになります。そして、それは完全に正常な日常のことです。

これは、「構成」と継承の「両方」がどのようになるかです。

class A : B
{
  public C Prop;
}

クラスABを継承し、Cで構成されます。

だから、一般的に言えば、なぜ両方をしないのですか?

理由はまったくありません。 ABから継承し、その構成でCを使用することが理にかなっている場合は、それを行います。そうでない場合は、 しないでください。

意味のあることをしてください。


ボーナスアドバイスタイム!あなたとあなたの同僚のために、原則が存在する理由に注意してください。優先継承よりも構成には 利点と欠点の両方 があります。そして率直に言って、 最小の知識の原則の利点 は(私の意見では)せいぜい不安定です。ただし、主に、この例のCAのコンシューマに直接実行してほしくないことを実行できる場合、Cはプライベートにする必要があります。ただし、Cの各メソッドのラッパーを作成する場合は、Cをパブリックにする必要があります。

3
svidgen

続けるための2つの注意事項:

  1. 誰かがICharacterを知っていれば、それはIresourceを知っているので、リソースクラスの内部を公開しても、文字を使用するクラスの知識は減りませんでした。

  2. この2つのデータが一緒になり、各データに個別にアクセスできるようにしたために作成されたIResourceのインターフェイスを誰かが知っていると、さらに混乱したと思います。あなたは決定する必要があります-両方の部分が一緒に来る(そしてそれらは一緒に露出される)、またはそれらのそれぞれがもう一方なしであるのでそれが作る(そしてそれらにクラスを作らせない)のいずれか

0
Belgi