完璧なOOPアプリケーションを作成する方法[終了]


98

最近、「x」という会社を探していました。彼らは私にいくつかの質問を送って、1つだけを解決するように私に言いました。

問題はこのようなものです-

基本消費税は、免除される本、食品、医療製品を除くすべての商品に10%の率で適用されます。
輸入関税とは、すべての輸入品に5%の税率で適用される追加の消費税で、免除はありません。

アイテムを購入すると、すべてのアイテムの名前とその価格(税込)が記載されたレシートが届きます。最後に、アイテムの合計費用と消費税の合計額が支払われます。
消費税の丸めルールは、税率がn%の場合、棚卸価格pに(np / 100を最も近い0.05に切り上げた)金額の消費税が含まれることです。

「彼らは私に言った、彼らはあなたのソリューションのデザインの側面に興味があり、私のオブジェクト指向プログラミングのスキルを評価したいと思っている。」

これは彼らが彼ら自身の言葉で言ったことです

  • 解決策としては、Java、Ruby、またはC#のいずれかを使用してください。
  • 私たちはあなたのソリューションの設計側面に興味があり、オブジェクト指向プログラミングのスキルを評価したいと思います。
  • ビルドまたはテストの目的で外部ライブラリまたはツールを使用できます。具体的には、選択した言語(JUnit、Ant、NUnit、NAnt、Test :: Unit、Rakeなど)で利用可能なユニットテストライブラリまたはビルドツールを使用できます。
  • 必要に応じて、コードのほかに、設計と仮定の簡単な説明を含めることもできます。
  • 親切に、私たちはWebベースのアプリケーションや包括的なUIを期待していないことに注意してください。むしろ、シンプルなコンソールベースのアプリケーションを想定しており、ソースコードに関心があります。

だから私は以下のコードを提供しました-あなたは貼り付けコードをコピーしてVSで実行することができます。

class Program
 {
     static void Main(string[] args)
     {
         try
         {
             double totalBill = 0, salesTax = 0;
             List<Product> productList = getProductList();
             foreach (Product prod in productList)
             {
                 double tax = prod.ComputeSalesTax();
                 salesTax += tax;
                 totalBill += tax + (prod.Quantity * prod.ProductPrice);
                 Console.WriteLine(string.Format("Item = {0} : Quantity = {1} : Price = {2} : Tax = {3}", prod.ProductName, prod.Quantity, prod.ProductPrice + tax, tax));
             }
             Console.WriteLine("Total Tax : " + salesTax);
             Console.WriteLine("Total Bill : " + totalBill);                
        }
         catch (Exception ex)
         {
             Console.WriteLine(ex.Message);
         }
         Console.ReadLine();
     }

    private static List<Product> getProductList()
     {
         List<Product> lstProducts = new List<Product>();
         //input 1
         lstProducts.Add(new Product("Book", 12.49, 1, ProductType.ExemptedProduct, false));
         lstProducts.Add(new Product("Music CD", 14.99, 1, ProductType.TaxPaidProduct, false));
         lstProducts.Add(new Product("Chocolate Bar", .85, 1, ProductType.ExemptedProduct, false));

        //input 2
         //lstProducts.Add(new Product("Imported Chocolate", 10, 1, ProductType.ExemptedProduct,true));
         //lstProducts.Add(new Product("Imported Perfume", 47.50, 1, ProductType.TaxPaidProduct,true));

        //input 3
         //lstProducts.Add(new Product("Imported Perfume", 27.99, 1, ProductType.TaxPaidProduct,true));
         //lstProducts.Add(new Product("Perfume", 18.99, 1, ProductType.TaxPaidProduct,false));
         //lstProducts.Add(new Product("Headache Pills", 9.75, 1, ProductType.ExemptedProduct,false));
         //lstProducts.Add(new Product("Imported Chocolate", 11.25, 1, ProductType.ExemptedProduct,true));
         return lstProducts;
     }
 }

public enum ProductType
 {
     ExemptedProduct=1,
     TaxPaidProduct=2,
     //ImportedProduct=3
 }

class Product
 {
     private ProductType _typeOfProduct = ProductType.TaxPaidProduct;
     private string _productName = string.Empty;
     private double _productPrice;
     private int _quantity;
     private bool _isImportedProduct = false;

    public string ProductName { get { return _productName; } }
     public double ProductPrice { get { return _productPrice; } }
     public int Quantity { get { return _quantity; } }

    public Product(string productName, double productPrice,int quantity, ProductType type, bool isImportedProduct)
     {
         _productName = productName;
         _productPrice = productPrice;
         _quantity = quantity;
         _typeOfProduct = type;
         _isImportedProduct = isImportedProduct;
     }

    public double ComputeSalesTax()
     {
         double tax = 0;
         if(_isImportedProduct) //charge 5% tax directly
             tax+=_productPrice*.05;
         switch (_typeOfProduct)
         {
             case ProductType.ExemptedProduct: break;
             case ProductType.TaxPaidProduct:
                 tax += _productPrice * .10;
                 break;
         }
         return Math.Round(tax, 2);
         //round result before returning
     }
 }

入力をuncommnetして、さまざまな入力に対して実行できます。

解決策を提供しましたが、拒否されました。

「彼らは言った、彼らは私たちの現在のオープンポジションについて私を考慮することができない。なぜならコードソリューションは満足のいくものではないからだ。」

ここで不足しているものを教えてください。このソリューションは良いOOADソリューションではありませんか?
OOADスキルを向上させるにはどうすればよいですか。
私の先輩はまた、完璧なOOADアプリケーションも実際には機能しないと言っています。

ありがとう


2
おそらく、列挙ではなく継承階層を使用して製品タイプを区別することを期待していましたか?(ただし、このアプローチは、与えられたシナリオではかなり複雑になります。)
ダグラス

私の推測では、インターフェースを定義しなかったため、ソリューションが即座に拒否されました。
Chris Gessler、2012

28
経験則として、誰かがインタビューの状況でOOPスキルを実証するように依頼した場合は、switchステートメントの使用は避けてください。代わりに、継承階層を使用してください。
Joe

4
コードレビューに投稿する必要があります。
デレク

私もそこに投稿しましたが、良い解決策を得ることができませんでした。しかし、誰もが他の人の助けを得て作成した私の新しいソリューションを見る ことができますcodeproject.com/Questions/332077/…ここに私の新しいコードも見つけることができます。
2012

回答:


246

まず最初に、良い天国は二重に財務計算をしません10進数で財務計算を行います。それが目的です。金銭ではなく物理問題を解決するためにdoubleを使用します問題ます。

プログラムの主な設計上の欠陥は、ポリシーが間違った場所にあることです。誰が税金の計算を担当していますか?あなたが置いた製品に税金の計算を任せましたが、リンゴ、本、または洗濯機を購入するとき、購入しようとしているものは、あなたが支払う予定の税額を通知する責任はありませんそれ。 政府の方針はあなたにそれを伝える責任があります。あなたのデザインは、オブジェクトが他人の責任ではなく自分の懸念に責任を負うべきであるというOOの基本設計原則に大幅に違反しています。洗濯機の懸念は、適切な輸入関税を課すことなく衣服を洗うことです。税法が変更されても、変更したくない洗濯機オブジェクトポリシーオブジェクトを変更する

では、このような問題に今後どのように取り組むべきでしょうか?

問題の説明で重要なすべての名詞を強調表示することから始めます。

基本消費税は、全体に対して10%ので適用されます、免除される食品医療製品を除き、商品されます。輸入関税は、追加で消費税のすべてに該当する輸入品割合なしで、5%の免除アイテムを購入すると、すべてのアイテムの名前とその価格税込)が記載されたレシートが届きます。アイテムの合計、および支払われた消費税の総額。消費税の丸め規則では、税率がn%の場合、棚卸価格 pに(np / 100を最も近い0.05に切り上げた)金額の消費税が含まれます。

さて、それらすべての名詞間の関係は何ですか?

  • 基本消費税は一種の消費税です
  • 輸入税は一種の消費税です
  • 消費税には10進数のレートがあります
  • 本は一種のアイテム
  • 食べ物は一種のアイテム
  • 医療製品は一種のアイテムです
  • 輸入品の場合があります
  • アイテムは文字列である名前を持っています
  • アイテムには、10進数のシェルフ価格があります。(注:アイテムには本当に価格がありますか?2つの同一の洗濯機が異なる店舗で、または同じ店舗で異なる時間に異なる価格で販売されている場合があります。より良い設計は、価格設定ポリシーがアイテムを次のように関連付けていると言うことです。その価格。)
  • 売上税免除ポリシーは、売上税がアイテムに適用されない条件を記述します。
  • 領収書には、アイテム、その価格、および税金のリストがあります。
  • 領収書には合計があります
  • 領収書には合計税があります

... 等々。すべての名詞間の関係がすべて完成したら、クラス階層の設計を開始できます。抽象基本クラスのアイテムがあります。本はそれから継承します。抽象クラスSalesTaxがあります。BasicSalesTaxはそれを継承します。等々。


12
提供された以上のものが必要ですか?継承の実装方法とポリモーフィズムとは何かについてもっと学ぶ必要があるようです。
インダストラー

27
@サンダー:この答えは十分すぎるほどです。おそらくこれを最初の例として使用して、スキルを磨くのはあなたの責任です。あなたの例は実際の例の定義であることに注意してください。この実際のコードには、提供していない実際のデザインが必要だったため、実際のインタビューに失敗しました。
Greg D

9
@Narayan:double正解の0.00000001%以内で十分な場合に最適です。0.5秒後にレンガが落下する速さを把握したい場合は、計算を倍精度で実行します。あなたがダブルで財政的調教を行うとき、あなたは税後の価格のような答えに終わり、43.79999999999999ドルであり、それはそれが正解に非常に近いにもかかわらず、ばかげて見えます。
Eric Lippert、2012

31
+1これまでに述べた問題の各名詞を調べ、次にそれらの間の関係を列挙するという、注目すべき課題を強調しました。いい案。
Chris Tonkinson、2012

3
@Jordão:10進数では、0.10を10回追加すると1.00になります。ただし、1.0 / 333.0を333倍すると、必ずしも10進数でも2倍でもかまいません。10進数では、分母が10のべき乗である分数が正確に表されます。doubleでは、2の累乗の分数です。それ以外のものはおおよそ表示されます。
Eric Lippert、2012

38

会社がNUnit、JUnit、またはTest :: Unitなどのライブラリについて何か言っている場合、TDDが実際にそれらにインポートされている可能性が高いです。あなたのコードサンプルにはテストはまったくありません。

私は次の実践的な知識を示すように努めます:

  • ユニットテスト(例:NUnit)
  • モッキング(RhinoMocksなど)
  • 持続性(例:NHibernate)
  • IoCコンテナ(例:NSpring)
  • デザインパターン
  • SOLIDの原則

上記のすべてのトピックをカバーする無料の高品質スクリーンキャストの印象的なソースとして、www.dimecasts.netをお勧めします。


19

これは非常に主観的ですが、ここで私はあなたのコードについていくつか指摘しておきます。

  • 私の意見では、あなたは混合ProductしましたShoppingCartItemProduct商品名、税ステータスなどが含まれている必要がありますが、数量は含まれていません。数量は製品の特性ではありません-その特定の製品を購入する会社の顧客ごとに異なります。

  • ShoppingCartItem持つべきProductと量を。そうすれば、顧客は同じ製品を多かれ少なかれ自由に購入できます。現在のセットアップでは不可能です。

  • また、最終的な税額を計算するの一部であってはならないProduct-それはのようなものの一部である必要がありShoppingCart、最終的な税計算がカート内のすべての製品を知っ伴う可能性があるため。


この回答で私が抱えている唯一の問題は、より優れた製品支払いシステム(これは有効です)を構築する方法を説明していますが、OOP手法については詳しく説明していません。これは任意の言語で実装できます。なんらかのインターフェース、継承、ポリモーフィズムなどを示さなければ、彼はまだテストに失敗するでしょう。
タイムアウト

最後のポイントを参照してください。IMOの税計算に最適な場所は、単一の責任の原則により、別個のTaxCalculatorクラスです。
Radek

返信ありがとうございます。すべての会社がこのような大規模で純粋なOOPSモデルで作業していますか。
サンダー

@shyamsunder私の答えについて本当に純粋なものは何もありません。それはOODの重要な側面であるインターフェース/継承を使用していませんが、私の意見では、最も重要な原則を示しています。他の回答が指摘したように、設計の主な問題は、さまざまなアクター間で責任を混同し、機能を追加するときに問題が発生することです。ほとんどの大規模なソフトウェアは、これらの原則に従う場合にのみ成長できます。
xxbbcc

正解ですが、税計算は別のオブジェクトである必要があることにも同意します。

14

まず第一に、これは非常に良いインタビューの質問です。それは多くの良いゲージですスキルの。

高水準と低水準の両方で、良い答えを提供するために理解する必要のある多くの事項があります(完全な答えはありません)。ここにカップルがあります:

  • ドメインモデリング ->ソリューションの優れたモデルをどのように作成しますか?どのようなオブジェクトを作成しますか?彼らはどのように要件を解決しますか?名詞を探すことは良い出発点ですが、エンティティの選択が良いかどうかはどうやって決めるのですか?他にどのようなエンティティが必要ですか?どのようなドメインの知識それを解決するためにが必要ですか?
  • 懸念の分離、疎結合、高い凝集性 ->懸念や変化率の異なる設計の部分をどのように分離し、それらをどのように関連付けるのですか?どのようにして設計を柔軟かつ最新に保つのですか?
  • 単体テスト、リファクタリング、TDD- > ソリューションを作成するためのプロセスは何ですか?テストを作成し、モックオブジェクトを使用し、リファクタリングし、反復しますか?
  • クリーンなコード、言語イディオム ->プログラミング言語の機能を使用して支援していますか?わかりやすいコードを書いていますか?抽象化のレベルは理にかなっていますか?コードはどの程度保守可能ですか?
  • ツール:ソース管理を使用していますか?ツールを構築しますか?IDE?

そこから、設計原則(SOLID原則など)、設計パターン、分析パターン、ドメインモデリング、テクノロジーの選択、将来の進化パス(たとえば、データベースやリッチUIレイヤーを追加するとどうなるか)を含む多くの興味深いディスカッションを行うことができます。何を変更する必要がありますか?)、トレードオフ、非機能要件(パフォーマンス、保守性、セキュリティなど)、受け入れテストなど...

ソリューションをどのように変更するべきかについてはコメントしません。これらの概念にさらに焦点を当てるべきです。

しかし、例として(Javaで)この問題を(部分的に)解決した方法示すことができます。見てProgramクラスそれはすべて、この領収書を印刷するために一緒に来る方法を確認します:

------------------これはあなたの注文です------------------
(001)ドメイン駆動設計----- $ 69.99
(001)成長するオブジェクト指向ソフトウェア----- $ 49.99
(001)ハウスMDシーズン1 ----- $ 29.99
(001)ハウスMDシーズン7 ----- $ 34.50
(IMD)成長するオブジェクト指向ソフトウェア----- $ 2.50
(BST)ハウスMDシーズン1 ----- $ 3.00
(BST)ハウスMDシーズン7 ----- $ 3.45
(IMD)ハウスMDシーズン7 ----- $ 1.73
                                小計----- $ 184.47
                                税総額----- $ 10.68
                                    合計----- $ 195.15
----------------米国をお選びいただきありがとうございます----------------

あなたは間違いなくそれらの本を見るべきです:-)

ちょうど注意:私のソリューションはまだ非常に不完全です、私は上に構築するための良い基盤を持つためにハッピーパスシナリオに焦点を当てました。


私はあなたの解決策を調べましたが、それはかなり興味深いものでした。OrderクラスはRecieptの印刷を担当すべきではないと思いますが。同様に、TaxMethodクラスは税の計算を担当すべきではありません。また、TaxMethodPracticeにはTaxMethodのリストを含めないでください。代わりに、SalesPolicyというクラスにこのリストを含める必要があります。SalesEngineというクラスには、SalesPolicy、Order、およびTaxCalculatorを渡す必要があります。SalesEngineはOrderのアイテムにSalesPolicyを適用し、TaxCalculatorを使用して税を計算します
CKing

@bot:興味深い観察....現時点でOrderは、領収書を印刷しますがReceipt、独自のフォーマットについて知っています。また、TaxMethodPracticeは一種の税制であり、特定のシナリオに適用されるすべての税を保持します。TaxMethods 税計算機です。あなたが提案しているSalesEngineのようないくつかのより高いレベルのバインディングクラスしか見当たらないと感じています。面白いアイデアですね。
ジョルダン

すべてのクラスには明確に定義された単一の責任が必要であり、現実世界のオブジェクトを表すクラスは現実世界に沿った方法で動作する必要があると思います。さらに言えば、TaxMethodは2つのクラスに分割できます。TaxCriteriaおよびTaxCalculator。同様に、注文は領収書を印刷してはなりません。レシートを生成するには、ReceiptGeneratorにReceiptを渡す必要があります。
12

@bot:私は完全に同意します!良いデザインはしっかりしています!TaxMethod 税計算機であり、TaxEligibilityCheck 税基準です。それらは別のエンティティです。レシートに関しては、はい。生成部分を分割すると、デザインがさらに改善されます。
ジョルダン

1
そのアイディアは仕様パターンから来ています、見てください!
ジョルダン

12

productと呼ばれるクラスを使用しているという事実を除いて、継承が何であるかを知っていることを証明したことはありません。この問題は、複数のOOPの概念を使用して解決できた可能性があります(あなたがそれらを知っていることを示すだけでも)。これは面接の問題なので、どれだけ知っているかを示したいと思います。

しかし、今はうつ病にはなりません。ここでそれらを実証しなかったという事実は、それらをまだ知らない、またはそれらを学ぶことができないことを意味するのではありません。

OOPまたはインタビューのどちらかをもう少し経験する必要があるだけです。

幸運を!


実はこれが私の最初のデザインでした。別のデザインを作成しましたが、文字数制限を超えているため表示できません。
サンダー

あなたはどんな例の助けを借りてそれを実証できますか?
サンダー

@サンダー:新しいデザインで質問を更新できます。
Bjarke Freund-Hansen、2012

10

それがあるので、OOPでプログラミングを学習を開始している人々は、それが何を意味するのか理解するために偉大な問題を持っていないだけで、実際の生活のように。OOよりも他のプログラミングに精通しているスキルを持っていると、理解するのが難しい場合があります。

まず、画面をオフにするか、お気に入りのIDEを終了します。鉛筆を取り、エンティティ関係のリストを作成します最終的なプログラムで遭遇する可能性のあるすべて機械プロセスものなどのします。

次に、さまざまな基本エンティティを取得してみます。いくつかはプロパティ能力を共有できることを理解するでしょう 。それを抽象オブジェクトに入れなければなりません。プログラムの素敵なスキーマを描き始める必要があります。

次に、機能(メソッド、関数、サブルーチン、必要に応じて呼び出す)を配置する必要があります。たとえば、製品オブジェクトは売上税計算できません。販売エンジンオブジェクト必要があります。

すべての大きな単語(インターフェースプロパティポリモーフィズム)に問題を感じないでください。遺産)になど)とデザインパターンに初めてないでください。美しいコードなどを作ろうとしないでください...単純なオブジェクトを考えて、実生活のようにそれの間の相互作用

その後、これに関する深刻な簡潔な文献を読んでみてください。ウィキペディアウィキブックスは、最初にGoFとデザインパターンについて読むための本当に良い方法だと思います UML


3
「まず、画面をオフにする」の+1。思考の力は計算の力と間違われることが多いと思います。
kontur

1
+1は、鉛筆と紙を使用する最も簡単な方法です。多くの場合、IDEの前に座っていると混乱します:)
Neeraj Gulia

一部の科学者は、画面を見ているときに私たちの脳は無関心であると述べました。私がソフトウェアアーキテクチャの設計を研究しているとき、私たちの先生は私たちに紙での作業をさせてくれます。彼は強力なUMLソフトウェアを気にしません。重要なことは、最初に物事を理解することです。
smonff 2012年

4

まず混在していないProduct領収書(とクラスShoppingCart)クラス、quantityの一部であるべきReceipItemShoppingCartItem)だけでなく、TaxCostTotalTaxTotalCostの一部である必要がありますShoppingCart

私のProductクラスでは、唯一持っているNamePrice&のようないくつかの読み取り専用のプロパティをIsImported

class Product
{
    static readonly IDictionary<ProductType, string[]> productType_Identifiers = 
        new Dictionary<ProductType, string[]>
        {
            {ProductType.Food, new[]{ "chocolate", "chocolates" }},
            {ProductType.Medical, new[]{ "pills" }},
            {ProductType.Book, new[]{ "book" }}
        };

    public decimal ShelfPrice { get; set; }

    public string Name { get; set; }

    public bool IsImported { get { return Name.Contains("imported "); } }

    public bool IsOf(ProductType productType)
    {
        return productType_Identifiers.ContainsKey(productType) &&
            productType_Identifiers[productType].Any(x => Name.Contains(x));
    }
}

class ShoppringCart
{
    public IList<ShoppringCartItem> CartItems { get; set; }

    public decimal TotalTax { get { return CartItems.Sum(x => x.Tax); } }

    public decimal TotalCost { get { return CartItems.Sum(x => x.Cost); } }
}

class ShoppringCartItem
{
    public Product Product { get; set; }

    public int Quantity { get; set; }

    public decimal Tax { get; set; }

    public decimal Cost { get { return Quantity * (Tax + Product.ShelfPrice); } }
}

税計算部分はと連動していProductます。製品は、税クラスである税ポリシーを定義しません。:問題の説明に基づいて、売上税の2種類があるBasicDuty税。あなたはTemplate Method Design Patternそれを達成するために使うことができます:

abstract class SalesTax
{
    abstract public bool IsApplicable(Product item);
    abstract public decimal Rate { get; }

    public decimal Calculate(Product item)
    {
        if (IsApplicable(item))
        {
            //sales tax are that for a tax rate of n%, a shelf price of p contains (np/100)
            var tax = (item.ShelfPrice * Rate) / 100;

            //The rounding rules: rounded up to the nearest 0.05
            tax = Math.Ceiling(tax / 0.05m) * 0.05m;

            return tax;
        }

        return 0;
    }
}

class BasicSalesTax : SalesTax
{
    private ProductType[] _taxExcemptions = new[] 
    { 
        ProductType.Food, ProductType.Medical, ProductType.Book 
    };

    public override bool IsApplicable(Product item)
    {
        return !(_taxExcemptions.Any(x => item.IsOf(x)));
    }

    public override decimal Rate { get { return 10.00M; } }
}

class ImportedDutySalesTax : SalesTax
{
    public override bool IsApplicable(Product item)
    {
        return item.IsImported;
    }

    public override decimal Rate { get { return 5.00M; } }
}

そして最後に税を適用するクラス:

class TaxCalculator
{
    private SalesTax[] _Taxes = new SalesTax[] { new BasicSalesTax(), new ImportedDutySalesTax() };

    public void Calculate(ShoppringCart shoppringCart)
    {
        foreach (var cartItem in shoppringCart.CartItems)
        {
            cartItem.Tax = _Taxes.Sum(x => x.Calculate(cartItem.Product));
        }

    }
}

MyFiddleで試してみることができます。


2

設計ルールに関する非常に優れた出発点は、SOLIDの原則です。

たとえば、オープンクローズの原則では、新しい機能を追加する場合、既存のクラスにコードを追加する必要はなく、新しいクラスを追加する必要があると述べています。

サンプルアプリケーションの場合、これは、新しい消費税を追加するには、新しいクラスを追加する必要があることを意味します。ルールの例外である異なる製品についても同じことが言えます。

丸め規則は明らかに別のクラスに分類されます。単一責任の原則では、すべてのクラスが単一の責任を負うと規定されています。

自分でコードを書こうとすることは、単に良いソリューションを書いてここに貼り付けることよりもはるかに多くの利益をもたらすと思います。

完璧に設計されたプログラムを書くための簡単なアルゴリズムは次のようになります:

  1. 問題を解決するコードを書いてください
  2. コードがSOLIDの原則に準拠しているかどうかを確認する
  3. goto 1よりもルール違反がある場合。

2

完全なOOP実装は完全に議論の余地があります。あなたの質問で私が見たことから、Product、Tax、ProductDBなどの最終的な価格を計算するためにコードが実行する役割に基づいてコードをモジュール化できます。

  1. Product抽象クラスである可能性があり、Books、Foodのような派生型はそれから継承される可能性があります。税の適用可能性は、派生タイプによって決定できます。製品は、税が適用されるかどうかを派生クラスに基づいて通知します。

  2. TaxCriteria 列挙型にすることができ、これは購入時に指定できます(輸入、消費税の適用)。

  3. Taxクラスはに基づいて税金を計算しTaxCriteriaます。

  4. XXBBCCでShoppingCartItem提案されているように、製品と税金のインスタンスをカプセル化できます。これは、製品の詳細を数量で、合計価格を税金で分離するのに最適な方法です。

幸運を。


1

厳密にOOA / Dの観点から見ると、主な問題の1つは、ほとんどのクラス属性の属性名にクラスの冗長名があることです。例:製品の価格、typeOf 製品。この場合、このクラスを使用するすべての場所で、product.productNameなどの過度に冗長でやや混乱するコードが存在します。属性から冗長なクラス名の接頭辞を削除します。

また、質問で尋ねられたように、領収書の購入と作成に関連するクラスはありませんでした。


1

これは、製品、税金などのOOパターンの優れた例です。OO設計に不可欠なインターフェイスの使用に注意してください。

http://www.dreamincode.net/forums/topic/185426-design-patterns-strategy/


3
私は、製品をインターフェースにするよりも(抽象的な)クラスにすることを好みます。各製品を別々のクラスにすることもしません。多くても、カテゴリごとに1つのクラスを作成します。
CodesInChaos

@CodeInChaos-ほとんどの場合、両方が必要ですが、アーキテクトとして仕事を着陸させる場合は、抽象クラスではなくインターフェイスを実装することを選択します。
Chris Gessler、2012

1
この例のインターフェースはまったく意味がありません。それらは、それらを実装する各クラスでコードの重複につながるだけです。各クラスは同じ方法で実装します。
Piotr Perak

0

ビジターパターンを使用して、税問題のコストを攻撃しました。

public class Tests
    {
        [SetUp]
        public void Setup()
        {
        }

        [Test]
        public void Input1Test()
        {
            var items = new List<IItem> {
                new Book("Book", 12.49M, 1, false),
                new Other("Music CD", 14.99M, 1, false),
                new Food("Chocolate Bar", 0.85M, 1, false)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(12.49, items[0].Accept(visitor));
            Assert.AreEqual(16.49, items[1].Accept(visitor));
            Assert.AreEqual(0.85, items[2].Accept(visitor));
        }

        [Test]
        public void Input2Test()
        {
            var items = new List<IItem> {
                new Food("Bottle of Chocolates", 10.00M, 1, true),
                new Other("Bottle of Perfume", 47.50M, 1, true)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(10.50, items[0].Accept(visitor));
            Assert.AreEqual(54.65, items[1].Accept(visitor));
        }

        [Test]
        public void Input3Test()
        {
            var items = new List<IItem> {
                new Other("Bottle of Perfume", 27.99M, 1, true),
                new Other("Bottle of Perfume", 18.99M, 1, false),
                new Medicine("Packet of headache pills", 9.75M, 1, false),
                new Food("Box of Chocolate", 11.25M, 1, true)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(32.19, items[0].Accept(visitor));
            Assert.AreEqual(20.89, items[1].Accept(visitor));
            Assert.AreEqual(9.75, items[2].Accept(visitor));
            Assert.AreEqual(11.80, items[3].Accept(visitor));
        }
    }

    public abstract class IItem : IItemVisitable
    { 
        public IItem(string name,
            decimal price,
            int quantity,
            bool isImported)
            {
                Name = name;
                Price = price;
                Quantity = quantity;
                IsImported = isImported;
            }

        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Quantity { get; set; }
        public bool IsImported { get; set; }

        public abstract decimal Accept(IItemVisitor visitor);
    }

    public class Other : IItem, IItemVisitable
    {
        public Other(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public class Book : IItem, IItemVisitable
    {
        public Book(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this),2);
    }

    public class Food : IItem, IItemVisitable
    {
        public Food(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public class Medicine : IItem, IItemVisitable
    {
        public Medicine(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public interface IItemVisitable
    {
        decimal Accept(IItemVisitor visitor);
    }

    public class ItemCostWithTaxVisitor : IItemVisitor
    {
        public decimal Visit(Food item) => CalculateCostWithTax(item);

        public decimal Visit(Book item) => CalculateCostWithTax(item);

        public decimal Visit(Medicine item) => CalculateCostWithTax(item);

        public decimal CalculateCostWithTax(IItem item) => item.IsImported ?
            Math.Round(item.Price * item.Quantity * .05M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity)
            : item.Price * item.Quantity;

        public decimal Visit(Other item) => item.IsImported ?
            Math.Round(item.Price * item.Quantity * .15M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity)
            : Math.Round(item.Price * item.Quantity * .10M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity);
    }

    public interface IItemVisitor
    {
        decimal Visit(Food item);
        decimal Visit(Book item);
        decimal Visit(Medicine item);
        decimal Visit(Other item);
    }

Stackoverflowへようこそ。質問に対する回答を必ず説明してください。OPは単にソリューションを求めているだけでなく、なぜソリューションの方が良い/悪いのかを追求しています。
Simon.SA 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.