22

最近、クラスでのデータと行動の分離について話し合いました。データと動作の分離の概念は、ドメインモデルとその動作を別々のクラスに配置することによって実装されます。
しかし、私はこのアプローチの想定される利点を確信していません。それは「偉大な」人によって造られたかもしれませんが(私にはわかりませんが、マーティン・ファウラーだと思います)。ここに簡単な例を示します。Personとそのメソッド(動作)のデータを含むPersonクラスがあるとします。

class Person
{
    string Name;
    DateTime BirthDate;

    //constructor
    Person(string Name, DateTime BirthDate)
    {
        this.Name = Name;
        this.BirthDate = BirthDate;
    }

    int GetAge()
    {
        return Today - BirthDate; //for illustration only
    }

}

ここで、動作とデータを別々のクラスに分けます。

class Person
{
    string Name;
    DateTime BirthDate;

    //constructor
    Person(string Name, DateTime BirthDate)
    {
        this.Name = Name;
        this.BirthDate = BirthDate;
    }
}

class PersonService
{
    Person personObject;

    //constructor
    PersonService(string Name, DateTime BirthDate)
    {
        this.personObject = new Person(Name, BirthDate);
    }

    //overloaded constructor
    PersonService(Person personObject)
    {
        this.personObject = personObject;
    }

    int GetAge()
    {
        return personObject.Today - personObject.BirthDate; //for illustration only
    }
}

これは有益であり、柔軟性を向上させ、緩い結合を提供すると思われます。方法がわかりません。私によると、これは余分なコーディングとパフォーマンスのペナルティをもたらし、毎回2つのクラスオブジェクトを初期化する必要があります。そして、私はこのコードを拡張することでより多くの問題を見ます。上記の場合に継承を導入するとどうなるか考えてみてください。両方のクラスを継承する必要があります

class Employee: Person
{
    Double Salary;

    Employee(string Name, DateTime BirthDate, Double Salary): base(Name, BirthDate)
    {
        this.Salary = Salary;       
    }

}

class EmployeeService: PersonService
{
    Employee employeeObject;

    //constructor
    EmployeeService(string Name, DateTime BirthDate, Double Salary)
    {
        this.employeeObject = new Employee(Name, BirthDate, Salary);
    }

    //overloaded constructor
    EmployeeService(Employee employeeObject)
    {
        this.employeeObject = employeeObject;
    }
}

別のクラスで動作を分離した場合でも、Behaviorクラスメソッドが機能するには、Dataクラスのオブジェクトが必要であることに注意してください。したがって、最終的には、モデルオブジェクトの形式のデータがありますが、Behaviorクラスにはデータと動作の両方が含まれます。
いくつかのインターフェースをミックスに追加できると言うかもしれません。そうすれば、IPersonServiceとIEmployeeServiceを使用できます。しかし、すべてのクラスにインターフェースを導入し、インターフェースから継承することは問題ないように思われます。

では、同じクラスに入れることでは達成できなかった、上記のデータと動作を分離することで何を達成したかを教えてください。

4

7 に答える 7

14

実際、Martin Fowlerは、ドメインモデルでは、データと動作を組み合わせる必要があると述べています。AnemicDomainModelを見てください。

于 2012-07-09T06:30:57.233 に答える
12

私は同意します、あなたが実装したときの分離は面倒です。しかし、他のオプションがあります。getAge(person p)メソッドを持つageCalculatorオブジェクトはどうですか?またはperson.getAge(IAgeCalculator calc)。または、さらに良いcalc.getAge(IAgeble a)

これらの懸念を分離することから生じるいくつかの利点があります。あなたがあなたの実装が何年も戻ることを意図したと仮定すると、人/赤ちゃんがたった3ヶ月であるとしたらどうでしょうか?0を返しますか?.25?例外をスローしますか?犬の年齢が必要な場合はどうなりますか?数十年または数時間の年齢?特定の日付の年齢が必要な場合はどうなりますか?その人が死んだ場合はどうなりますか?火星の軌道を1年間使用したい場合はどうなりますか?またはヘブライ語のカレンダー?

個人インターフェースを使用するが、誕生日や年齢を使用しないクラスには影響しません。年齢の計算を消費するデータから切り離すことで、柔軟性が高まり、再利用の可能性が高まります。(たぶん、チーズと同じコードの人の年齢を計算することさえできます!)

いつものように、最適な設計は状況によって大きく異なります。ただし、この種の問題でパフォーマンスが私の決定に影響を与えることはまれな状況です。システムの他の部分は、ブラウザとサーバー間の光速、データベースの取得またはシリアル化など、数桁大きい要因である可能性があります。時間/ドルは、理論上のパフォーマンスの懸念よりも、単純さと保守性に向けたリファクタリングに費やす方が適切です。そのためには、ドメインモデルのデータと動作を分離することが役立つと思います。結局のところ、これらは個別の関心事ですよね?

そのような優先順位があっても、物事は混乱しています。これで、人の年齢を求めるクラスには、別の依存関係、calcクラスがあります。理想的には、クラスの依存関係を少なくすることが望ましいです。また、calcのインスタンス化を担当するのは誰ですか?注入しますか?calcFactoryを作成しますか?それとも静的メソッドである必要がありますか?決定はテスト容易性にどのように影響しますか?シンプルさへの意欲は実際に複雑さを増していますか?

行動とデータの組み合わせに関するOOの事例と、単一責任の原則との間には隔たりがあるようです。他のすべてが失敗した場合は、両方の方法で記述してから、同僚に「どちらが簡単ですか」と尋ねます。

于 2013-02-19T04:16:29.123 に答える
4

おかしなことに、OOPはデータと動作を組み合わせたものとして説明されることがよくあります。

ここで示しているのは、私がアンチパターンと見なしているもの、つまり「貧血ドメインモデル」です。それはあなたが言及したすべての問題に苦しんでいるので、避けるべきです。

アプリケーションのレベルが異なれば、手続きがより複雑になる可能性があります。これは、これまでに示したようなサービスモデルに適していますが、通常はシステムの端にしかありません。それでも、それは従来のオブジェクト設計(データ+動作)によって内部的に実装されます。通常、これは単なる頭痛の種です。

于 2012-07-09T06:31:21.290 に答える
4

返信が1年ほど遅れていることに気づきましたが、とにかく…笑

私は以前に行動を分離しましたが、あなたが示した方法ではありません。

ビヘイビアーを分離することが理にかなっているのは、共通のインターフェースを持ちながら、さまざまなオブジェクトに対してさまざまな(固有の)実装を可能にするビヘイビアーがある場合です。

たとえば、私がゲームを作成している場合、オブジェクトで使用できる動作には、歩く、飛ぶ、ジャンプするなどの機能があります。

IWalkable、IFlyable、IJumpableなどのインターフェイスを定義し、これらのインターフェイスに基づいて具体的なクラスを作成することで、優れた柔軟性とコードの再利用が可能になります。

IWalkableの場合は...

CouldWalk:IWalkableBehavior

LimitedWalking:IWalkableBehavior

UnlimitedWalking:IWalkableBehavior

IFlyableBehaviorとIJumpableBehaviorの同様のパターン。

これらの具象クラスは、CannotWalk、LimitedWalking、およびUnlimitedWalkingの動作を実装します。

オブジェクト(敵など)の具体的なクラスには、これらのビヘイビアーのローカルインスタンスがあります。例えば:

IWalkableBehavior _walking = new CouldWalk();

他の人は新しいLimitedWalking()または新しいUnlimitedWalking()を使用するかもしれません。

敵の行動を処理するときが来たら、AIがプレーヤーが敵の特定の範囲内にいることを検出したとすると(これは、IReactsToPlayerProximityと言う行動である可能性もあります)、自然に敵を「」に近づけようとします。敵と交戦する」。

必要なのは_walking.Walk(int xdist)メソッドを呼び出すことだけで、自動的にソートされます。オブジェクトがCannotWalkを使用している場合、Walk()メソッドは単に戻り、何もしないと定義されるため、何も起こりません。LimitedWalkingを使用している場合、敵はプレーヤーに向かって非常に短い距離を移動する可能性があり、UnlimitedWalkingを使用している場合、敵はプレーヤーのすぐ上に移動する可能性があります。

私はこれをあまり明確に説明していないかもしれませんが、基本的に私が意味するのはそれを反対の見方で見ることです。オブジェクト(ここではデータと呼んでいるもの)をBehaviorクラスにカプセル化する代わりに、インターフェイスを使用してBehaviorをオブジェクトにカプセル化します。これにより、動作を洗練し、各「動作ベース」を簡単に拡張できる「緩い結合」が得られます。 (ウォーキング、フライング、ジャンプなど)新しい実装では、オブジェクト自体に違いはありません。歩行動作がCannotWalkとして定義されている場合でも、歩行動作があります。

于 2013-08-03T05:58:35.590 に答える
3

人(任意の人)に興味をそそる年齢。したがって、Personオブジェクトの一部である必要があります。

hasExperienceWithThe40mmRocketLauncher()は、個人に固有のものではありませんが、Personオブジェクトを拡張または集約できるMilitaryServiceインターフェイスに固有のものである可能性があります。したがって、Personオブジェクトの一部であってはなりません。

一般に、目標は、通常のPersonの動作に例外を導入するため、最も簡単な方法であるという理由だけで、ベースオブジェクト( "Person")にメソッドを追加しないようにすることです。

基本的に、「hasServedInMilitary」のようなものをベースオブジェクトに追加しているのを見ると、問題が発生します。次に、if(p.hasServedInMilitary())blablablaなどのステートメントを大量に実行します。これは、instanceOf()チェックを常に実行することと論理的に同じであり、Personと「兵役を見たPerson」は実際には2つの異なるものであり、何らかの方法で切断する必要があることを示しています。

一歩後退して、OOPは、ifステートメントとswitchステートメントの数を減らし、代わりに、さまざまなオブジェクトが抽象メソッド/インターフェースの特定の実装に従って処理できるようにすることを目的としています。データと動作を分離することでこれが促進されますが、極端にデータを分離してすべての動作からすべてのデータを分離する理由はありません。

于 2012-07-10T12:57:19.197 に答える
1

答えは本当にそれが正しい状況で良いということです。開発者としてのあなたの仕事の一部は、提示された問題の最善の解決策を決定し、将来のニーズに対応できるように解決策を位置付けることを試みることです。

私はこれを頻繁にこのパターンに従って行いませんが、コンパイラまたは環境がデータと動作の分離をサポートするように特別に設計されている場合、プラットフォームがスクリプトを処理および編成する方法で達成できる多くの最適化があります。

ソリューション全体を毎回カスタムビルドするのではなく、できるだけ多くのデザインパターンに精通することが最善の利益であり、パターンがすぐには意味をなさないため、あまり判断力を持たないでください。多くの場合、既存のデザインパターンを使用して、コード全体で柔軟で堅牢なソリューションを実現できます。これらはすべて開始点として意図されているため、遭遇する個々のシナリオに対応するように常にカスタマイズする準備をしておく必要があることを覚えておいてください。

于 2018-11-14T18:20:03.050 に答える
0

あなたが説明したアプローチは、戦略パターンと一致しています。これにより、次の設計原則が容易になります。

オープン/クローズド原則

クラスは拡張のために開いている必要がありますが、変更のために閉じている必要があります

継承をめぐる構成

動作は、個別のインターフェイスと、これらのインターフェイスを実装する特定のクラスとして定義されます。これにより、ビヘイビアーとビヘイビアーを使用するクラスとの間のデカップリングが向上します。動作は、それを使用するクラスを壊すことなく変更でき、クラスは、コードを大幅に変更することなく、使用される特定の実装を変更することで動作を切り替えることができます。

于 2020-07-13T21:23:21.383 に答える