16

私は OOP の用語で考えるのは比較的初心者であり、それを行う正しい方法についての私の「本能」をまだ見つけていません。演習として、机の上の飲み物を例に、さまざまな種類のオブジェクト間の境界線を作成する場所を見つけようとしています。

Drinkと のような属性volumeと とtemperatureのようなメソッドを持つオブジェクト を作成すると仮定するとpour()drink()特定の飲み物の「タイプ」がどこに来るかを確認するのに苦労しています。

TeaCoffeeまたはの飲み物の種類があるとします。私の最初の本能は、属性とメソッドが共通しているJuiceため、サブクラス化することです。Drink

問題は両方になり、 と のTeaようCoffeeな属性がsugarsありますmilkが、Juiceそうではありませんが、3 つすべてにvariant(それぞれアールグレイ、デカフェ、オレンジ) があります。

同様にTea、メソッドがありますが、それはオブジェクトには意味がありませCoffeeん。addSugar()Juice

それは、すべてのサブクラスがそれらを必要としない場合でも、スーパークラスがこれらの属性とメソッドを持つ必要があることを意味しますか、それともサブクラスで、特にvariant各サブクラスが持っているような属性について、それらを定義しますかそれは有効な値の独自のリストですか?

しかし、最終的にはとサブクラスの2 つのaddSugar()メソッドになります。TeaCoffee

または、すべての属性とメソッドをスーパークラスに配置することになり、ほとんどが少なくともいくつかの飲み物の種類間で共有されるため、サブクラス化のポイントは一体何だったのだろうか?

抽象化しすぎているのではないかと心配していますが、新しいタイプを追加したい場合に、追い詰められたくはありませWatervariant

4

10 に答える 10

21

問題の一部は、現実世界のオブジェクトがきちんと階層化されていないことです。すべてのオブジェクトには、さまざまな親があります。グラス 1 杯のジュースは確かに飲み物ですが、栄養源でもありますが、すべての飲み物がそうであるとは限りません。コップ一杯の水に栄養はほとんどありません。同様に、飲み物以外の栄養源もたくさんあります。しかし、ほとんどの言語では、1 つのクラスからしか継承できません。1 杯のお茶は技術的にはエネルギー源ですが (熱く、熱エネルギーを含んでいます)、バッテリーもそうです。それらは共通の基本クラスを持っていますか?

そしてもちろん、おまけの質問ですが、私がジュースに砂糖を入れたくない場合、それを防ぐにはどうすればよいですか? 物理的に、それは可能です。しかし、それは従来行われていません。どれをモデルにしたいですか?

最終的に、「世界」をモデル化しようとしないでください。代わりに問題をモデル化してください。アプリケーションでお茶をどのように処理しますか? アプリケーションでのお茶の役割は何ですか? あなたの場合、多くの可能な親のうち、どれが理にかなっていますか?

アプリケーションで「砂糖を加えてもよい飲み物」と「そのまましか飲めない飲み物」を区別する必要がある場合は、おそらくそれらを 2 つの異なる基本クラスにすることになります。一般的な「ドリンク」クラスも必要ですか? おそらく、そうではないかもしれません。バーは、それが飲み物であること、飲めることを気にしないかもしれません。それは販売可能な製品であり、顧客に砂糖が必要かどうかを尋ねなければならない場合もあれば、必要ない場合もあります。しかし、「drink」基本クラスは必要ないかもしれません。

しかし、飲み物を飲む人にとっては、Consume() メソッドを持つ "Drink" 基本クラスが必要になるでしょう。これが重要な側面だからです。

最終的に、あなたの目標は機能するプログラムを書くことであり、完全な OOP クラス図を書くことではないことを忘れないでください。問題は、「OOP でさまざまな種類の飲み物を表現するにはどうすればよいか」ではなく、「プログラムでさまざまな種類の飲み物を扱えるようにするにはどうすればよいか」です。答えは、共通の Drink ベース クラスを持つクラス階層にそれらを配置することかもしれません。しかし、それはまったく別のものかもしれません。「すべての飲み物を同じように扱い、名前を入れ替えるだけ」の場合もあり、その場合は 1 つのクラスで十分です。または、実際に「飲む」プロパティをモデル化せずに、飲み物を単なる別の種類の消耗品または別の種類の液体として扱うことになるかもしれません。

物理シミュレーターを作成している場合、おそらくバッテリーと一杯のお茶は同じ基本クラスから派生する必要があります。これは、どちらもエネルギーを蓄えることができるという特性に関係があるためですそして今、ジュースとお茶は突然共通点がなくなりました。どちらも別世界の飲み物かもしれませんが、あなたのアプリケーションでは?それらは完全に異なります。(そして、ジュースにもエネルギーがどのように含まれているかについては、つまらないことはやめてください。私はコップ一杯の水と言ったでしょうが、おそらく誰かが融合力か何かを持ち出すでしょう;))

目的を見失うことはありません。プログラムで飲み物をモデル化する必要があるのはなぜですか?

于 2009-07-03T11:45:34.343 に答える
20

オブジェクト指向設計は、単独では行われません。あなたが尋ねなければならない質問は、私の特定のアプリケーションがDrinkクラスで何をする必要があるかということです. 答え、つまり設計は、アプリケーションごとに異なります。OO で失敗する一番の方法は、クラスの完全なプラトン階層を構築しようとすることです。そのようなものは、完全なプラトン世界にのみ存在し、私たちが住む現実の世界では役に立ちません。

于 2009-07-03T11:04:04.333 に答える
14

シュガート問題には 2 つの解決策があります。1 つ目は、次のような addSugar と addMilk を含む HotDrinks のようなサブクラスを追加することです。

                 Drink
                  /  \
          HotDrink    Juice
           /   \
        Tea     Coffee

問題は、これらがたくさんある場合、これが乱雑になることです。

もう 1 つの解決策は、インターフェイスを追加することです。各クラスは、0 個以上のインターフェイスを実装できます。したがって、ISweetened インターフェイスと ICowPowerEnabled インターフェイスがあります。Tea と Coffee の両方が ISweetened および ICowPowerEnabled インターフェイスを実装する場所。

于 2009-07-03T11:03:18.097 に答える
7

他の回答に加えて、インターフェイスはコードを分離することで柔軟性を高める傾向があります。

機能を基本クラスに関連付けないように注意する必要があります。これは、すべての派生クラスに共通であるためです。この機能を抽出して、他の無関係なオブジェクトに適用できるかどうかを考えてみてください。実際には、「現実世界」の基本クラスの多くのメソッドが異なるインターフェイスに属している可能性があることがわかります。

たとえば、今日飲み物に「砂糖を加える」場合、後でパンケーキに加えることにするかもしれません。そして、AddSugar やその他のさまざまな無関係なメソッドを使用して、多くの場所で Drink を要求するコードになる可能性があります。リファクタリングが難しい場合があります。

クラスが何かを行う方法を知っている場合、クラスが実際に何であるかに関係なく、インターフェイスを実装できます。例えば:

interface IAcceptSugar
{
    void AddSugar();
}

public class Tea : IAcceptSugar { ... }
public class Coffee : IAcceptSugar { ... }
public class Pancake : IAcceptSugar { ... }
public class SugarAddict : IAcceptSugar { ... }

インターフェイスを使用して、実際にそれを実装するオブジェクトから特定の機能を要求すると、高度に独立したコードを作成するのに役立ちます。

public void Sweeten(List<IAcceptSugar> items)
{
   if (this.MustGetRidOfAllThisSugar)
   {
       // I want to get rid of my sugar, and
       // frankly, I don't care what you guys
       // do with it

       foreach(IAcceptSugar item in items)
          item.AddSugar();
   }
}

コードのテストも容易になります。インターフェイスが単純であるほど、そのインターフェイスのコンシューマを簡単にテストできます。

[編集]

多分私は完全に明確ではなかったので、明確にします: AddSugar() が派生オブジェクトのグループに対して同じことを行う場合、もちろんそれらの基本クラスに実装します。しかし、この例では、Drink は常に IAcceptSugar を実装する必要はないと述べました。

したがって、共通のクラスになる可能性があります。

 public class DrinkWithSugar : Drink, IAcceptSugar { ... }

AddSugar() を一連の飲み物に対して同じ方法で実装します。

しかし、ある日、これがメソッドにとって最適な場所ではないことに気付いた場合、インターフェイスを介してメソッドを使用しているだけであれば、コードの他の部分を変更する必要はありません。

教訓は次のとおりです。インターフェースが同じままである限り、階層は変更される可能性があります。

于 2009-07-03T12:01:13.463 に答える
5

このpdfはあなたを大いに助けるかもしれません、そしてそれはあなたの例に完全に適合します:

デコレータ パターン

于 2009-07-03T11:05:00.900 に答える
3

関連する可能性のある2つのこと:

1) ニール・バターワースは、ある文脈における実世界のモデルと実世界そのものとの間の非常に重要な違いを提起しています。オブジェクト指向設計は、理由によりオブジェクト指向モデリングと呼ばれることもあります。モデルは、現実世界の「興味深い」側面のみに関係しています。何が興味深いかは、アプリケーションのコンテキストによって異なります。

2)継承よりも構成を優先します。飲み物には、量と温度のプロパティがあり (モデリング コンテキストでは、人間の知覚、つまり極寒、冷、温、熱に関連している可能性があります)、材料 (水、紅茶、コーヒー、牛乳、砂糖、調味料) で構成されています。成分から継承するのではなく、成分で作られていることに注意してください。材料を飲み物に構成する良い方法に関する限り、デコレータパターンを調べてみてください。

于 2009-07-03T11:31:10.493 に答える
2

新しい OO プログラマーによくある間違いの 1 つは、継承がポリモーフィズムと再利用という 2 つの異なる目的に使用されることを認識していないことです。単一継承言語では、通常、再利用部分を犠牲にして、ポリモーフィズムのみを実行する必要があります。

もう 1 つの間違いは、継承を使いすぎることです。美しい継承階層のレイアウトを開始するのは良い考えのように思えるかもしれませんが、ポリモーフィックの問題が発生するまで待ってから継承を作成することをお勧めします。

4 つの異なる種類の飲み物のクラスを作成する際に問題が発生した場合は、継承なしで 4 つの異なる種類を作成します。「よし、今度はこれらの飲み物のいずれかを人に与えて、何を飲んだかを出力したい」という問題が与えられた場合、それはポリモーフィズムの完璧な使用法です。function を使用してインターフェイスまたは抽象基本クラスを追加しますdrink()。コンパイルをプッシュすると、5 つのクラスすべてで関数が実装されていないことがわかります。5 つの関数すべてを実装し、コンパイルをプッシュすれば完了です。

牛乳や砂糖の追加に関するあなたの質問に答えるために、あなたが遭遇していると私が想定している問題は、オブジェクトDrinkableを aに渡したいが、 orを持ってPersonDrinkableないということです。この問題は 2 つ以上の方法で解決できますが、1 つの方法は非常に悪い方法です。addMilkaddSugar

悪い方法: aDrinkableを a に渡しPersonて型検査を行い、オブジェクトをキャストして aTeaまたはを取得しますJuice

void prepare (Drinkable drink)
{
  if (drink is Juice)
  {
    Juice juice_drink = (Juick) drink;
    juice_drink.thing();
  }
}

1 つの方法: 1 つの方法は、drink をPerson具象クラスに渡してから、drink に渡すことです。

class Person
{
  void prepare_juice (Juice drink)
  {
    drink.thing1();
  }
  void prepare_coffee (Coffee drink)
  {
    drink.addSugar ();
    drink.addCream ();
  }
}

その人に飲み物を用意するように頼んだ後、それを飲むように頼みます。

于 2009-07-03T16:13:29.343 に答える
2

Decorator パターンを使用します。

于 2009-07-03T11:54:26.040 に答える
1

あなたが初心者なら、あなたのデザインパターンが完璧にならないことを心配する必要はありません. 実際、多くの人は完璧なデザインパターンはないと主張するでしょう:P

あなたの例については、おそらくお茶やコーヒーなどの飲み物の抽象クラスを作成します(それらは同様の属性を共有しているため)。そして、drink クラスにほとんど属性とメソッドを持たないようにしてください。水とジュースは、抽象的な飲み物クラスからサブクラス化できます。

于 2009-07-03T11:07:01.660 に答える
1

それはすべて状況に依存します。つまり、最も単純な設計から始めて、必要に応じてリファクタリングするということです。あまり派手な OOP には見​​えないかもしれませんが、1 つのクラスとプロパティ 'CanAddSugar' および 'SugarAmount' から始めます (後で、述語 CanAddIngredient(IngredientName) に一般化して、人々が飲み物にウィスキーを追加できるようにすることができます)。 )

そのすべてに深い疑問があります - 2 つのオブジェクトのどのような違いがあれば、それらに別のクラスが必要になるのでしょうか? 私はそれに対する良い答えを見たことがありません。

于 2010-01-01T10:10:54.407 に答える