So erstellen Sie die perfekte OOP-Anwendung [geschlossen]

98

Kürzlich habe ich versucht, eine Firma 'x'. Sie schickten mir einige Fragen und sagten mir, ich solle nur eine lösen.

Das Problem ist wie folgt:

Die Grundumsatzsteuer beträgt 10% für alle Waren, ausgenommen Bücher, Lebensmittel und Medizinprodukte, die davon ausgenommen sind.
Der Einfuhrzoll ist eine zusätzliche Umsatzsteuer, die auf alle eingeführten Waren in Höhe von 5% ohne Ausnahmen erhoben wird.

Wenn ich Artikel kaufe, erhalte ich eine Quittung, in der der Name aller Artikel und deren Preis (einschließlich Steuern) aufgeführt sind, die mit den Gesamtkosten der Artikel und den Gesamtbeträgen der gezahlten Umsatzsteuern endet.
Die Rundungsregeln für die Umsatzsteuer lauten, dass bei einem Steuersatz von n% ein Regalpreis von p (np / 100 auf die nächste 0,05 aufgerundet) Umsatzsteuerbetrag enthält.

"Sie sagten mir, sie interessieren sich für den Designaspekt Ihrer Lösung und möchten meine objektorientierten Programmierkenntnisse bewerten ."

Das sagten sie in ihren eigenen Worten

  • Für die Lösung möchten wir, dass Sie entweder Java, Ruby oder C # verwenden.
  • Wir interessieren uns für den DESIGN-ASPEKT Ihrer Lösung und möchten Ihre objektorientierten Programmierkenntnisse bewerten .
  • Sie können externe Bibliotheken oder Tools zum Erstellen oder Testen verwenden. Insbesondere können Sie Unit-Test-Bibliotheken verwenden oder Tools erstellen, die für die von Ihnen ausgewählte Sprache verfügbar sind (z. B. JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake usw.).
  • Optional können Sie Ihrem Code auch eine kurze Erläuterung Ihres Designs und Ihrer Annahmen hinzufügen.
  • Bitte beachten Sie, dass wir KEINE webbasierte Anwendung oder eine umfassende Benutzeroberfläche erwarten. Vielmehr erwarten wir eine einfache, konsolenbasierte Anwendung, die sich für Ihren Quellcode interessiert.

Also habe ich unten Code bereitgestellt - Sie können einfach Code einfügen und in VS ausführen.

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
     }
 }

Sie können die Eingabe trennen und für verschiedene Eingaben ausführen.

Ich lieferte die Lösung, wurde aber abgelehnt.

"Sie sagten, sie können mich nicht für unsere derzeit offenen Positionen in Betracht ziehen, weil die Codelösung nicht zufriedenstellend ist."

Bitte leiten Sie mich, was hier fehlt. Ist diese Lösung keine gute OOAD-Lösung?
Wie kann ich meine OOAD-Fähigkeiten verbessern?
Meine Senioren sagen auch, dass eine perfekte OOAD-Anwendung auch praktisch nicht funktioniert.

Vielen Dank

sunder
quelle
2
Vielleicht haben sie erwartet, dass Sie zwischen den Produkttypen anhand einer Vererbungshierarchie und nicht anhand einer Aufzählung unterscheiden? (Obwohl ich denke, dass dieser Ansatz für das gegebene Szenario ziemlich kompliziert wäre.)
Douglas
Ich vermute, dass sie Ihre Lösung msotly abgelehnt haben, weil Sie keine Schnittstellen definiert haben.
Chris Gessler
28
Als Faustregel gilt: Wenn Sie in einer Interview-Situation aufgefordert werden, OOP-Kenntnisse zu demonstrieren, sollten Sie versuchen, die Verwendung einer switch-Anweisung zu vermeiden. Verwenden Sie stattdessen eine Vererbungshierarchie.
Joe
4
Sollte in der Codeüberprüfung veröffentlicht werden.
Derek
Ich hatte dort auch gepostet, konnte dort aber keine gute Lösung finden. Aber jeder kann meine neue Lösung sehen, die ich nach Hilfe anderer erstellt habe. Codeproject.com/Questions/332077/… hier finden Sie auch meinen neuen Code.
Sunder

Antworten:

246

Zunächst einmal macht der Himmel keine doppelten Finanzberechnungen . Führen Sie Finanzberechnungen in Dezimalzahlen durch . dafür ist es da. Verwenden Sie double, um physikalische Probleme zu lösen , nicht finanzielle Probleme.

Der größte Konstruktionsfehler in Ihrem Programm besteht darin, dass die Richtlinie am falschen Ort ist . Wer ist für die Berechnung der Steuern verantwortlich? Sie haben das Produkt mit der Berechnung der Steuern beauftragt, aber wenn Sie einen Apfel, ein Buch oder eine Waschmaschine kaufen, ist das, was Sie kaufen möchten, nicht dafür verantwortlich, Ihnen mitzuteilen, wie viel Steuern Sie bezahlen werden es. Die Regierungspolitik ist dafür verantwortlich, Ihnen dies mitzuteilen. Ihr Design verstößt massiv gegen das grundlegende OO-Designprinzip, wonach Objekte für ihre eigenen Anliegen verantwortlich sein sollten und nicht für die anderer. Das Anliegen einer Waschmaschine ist es, Ihre Kleidung zu waschen und nicht den richtigen Einfuhrzoll zu erheben. Wenn sich die Steuergesetze ändern, möchten Sie sich nicht ändernAls Waschmaschinenobjekt möchten Sie das Richtlinienobjekt ändern .

Wie können wir diese Art von Problemen in Zukunft angehen?

Ich hätte damit begonnen, jedes wichtige Substantiv in der Problembeschreibung hervorzuheben:

Grund Umsatzsteuer ist bei einer anwendbaren Rate von 10% auf alle Waren , außer Bücher , Lebensmittel und medizinische Produkte , die ausgenommen sind. Der Einfuhrzoll ist eine zusätzliche Umsatzsteuer, die auf alle eingeführten Waren in Höhe von 5% ohne Ausnahmen erhoben wird . Wenn ich Artikel kaufe, erhalte ich eine Quittung, in der der Name aller Artikel und deren Preis (einschließlich Steuern ) aufgeführt sind und die mit den Gesamtkosten endetder Artikel und die Gesamtbeträge der gezahlten Umsatzsteuer . Die Rundungsregeln für die Umsatzsteuer lauten, dass bei einem Steuersatz von n% ein Regalpreis von p (np / 100 auf die nächste 0,05 aufgerundet) Umsatzsteuerbetrag enthält .

Welche Beziehungen bestehen zwischen all diesen Substantiven?

  • Die Grundumsatzsteuer ist eine Art Umsatzsteuer
  • Einfuhrzoll ist eine Art Umsatzsteuer
  • Eine Umsatzsteuer hat einen Dezimalsatz
  • Bücher sind eine Art Gegenstand
  • Essen ist eine Art Gegenstand
  • Medizinische Produkte sind eine Art Gegenstand
  • Artikel können importierte Waren sein
  • Ein Element hat einen Namen, der eine Zeichenfolge ist
  • Ein Artikel hat einen Regalpreis, der dezimal ist. (Hinweis: Hat ein Artikel wirklich einen Preis? Zwei identische Waschmaschinen werden möglicherweise zu unterschiedlichen Preisen in verschiedenen Geschäften oder im selben Geschäft zu unterschiedlichen Zeiten zum Verkauf angeboten. Ein besseres Design könnte darin bestehen, zu sagen, dass sich eine Preisrichtlinie auf einen Artikel bezieht sein Preis.)
  • Eine Umsatzsteuerbefreiungsrichtlinie beschreibt die Bedingungen, unter denen eine Umsatzsteuer auf einen Artikel nicht anwendbar ist.
  • Eine Quittung enthält eine Liste der Artikel, ihrer Preise und ihrer Steuern.
  • Eine Quittung hat eine Gesamtsumme
  • Eine Quittung hat eine Gesamtsteuer

... und so weiter. Sobald Sie alle Beziehungen zwischen allen Substantiven ausgearbeitet haben, können Sie mit dem Entwerfen einer Klassenhierarchie beginnen. Es gibt eine abstrakte Basisklasse Item. Buch erbt davon. Es gibt eine abstrakte Klasse SalesTax; BasicSalesTax erbt davon. Und so weiter.

Eric Lippert
quelle
12
Sie brauchen mehr als das, was gerade bereitgestellt wurde? Klingt so, als müssten Sie mehr darüber erfahren, wie Vererbung implementiert wird und was Polymorphismus ist.
Induster
27
@sunder: Diese Antwort ist mehr als ausreichend. Es liegt nun in Ihrer Verantwortung, Ihre Fähigkeiten zu entwickeln, vielleicht als erstes Beispiel. Beachten Sie, dass Ihr Beispiel die Definition eines realen Beispiels ist. Sie haben ein reales Interview nicht bestanden, weil für diesen realen Code ein reales Design erforderlich war, das Sie nicht bereitgestellt haben.
Greg D
9
@Narayan: doubleIdeal für Situationen, in denen es mehr als ausreichend ist, innerhalb von 0,00000001% der richtigen Antwort zu liegen. Wenn Sie herausfinden möchten, wie schnell ein Stein nach einer halben Sekunde fällt, rechnen Sie im Doppel. Wenn Sie Finanz- Arithemik im Doppel durchführen, erhalten Sie Antworten wie den Preis nach Steuern von 43,79999999999999 US-Dollar, und das sieht einfach albern aus, obwohl es der richtigen Antwort sehr nahe kommt.
Eric Lippert
31
+1 Sie haben eine bemerkenswerte Übung hervorgehoben, bei der jedes Substantiv im angegebenen Problem untersucht und anschließend die Beziehungen untereinander aufgezählt werden. Großartige Idee.
Chris Tonkinson
3
@ Jordão: Dezimal ergibt das zehnmalige Hinzufügen von 0,10 1,00. Das dreimalige Hinzufügen von 1.0 / 333.0 dreihundertdreiunddreißig ergibt jedoch nicht unbedingt eine Dezimal- oder eine Doppelzahl. In Dezimalzahlen werden Brüche mit Zehnerpotenzen im Nenner genau dargestellt. im Doppel sind es Brüche mit Zweierpotenzen. Alles andere ist ungefähr vertreten.
Eric Lippert
38

Wenn das Unternehmen etwas über Bibliotheken wie NUnit, JUnit oder Test :: Unit erzählt, ist es mehr als wahrscheinlich, dass TDD für sie wirklich wichtig ist. In Ihrem Codebeispiel gibt es überhaupt keine Tests.

Ich würde versuchen, praktisches Wissen zu demonstrieren über:

  • Unit Tests (zB NUnit)
  • Verspottung (zB RhinoMocks)
  • Persistenz (zB NHibernate)
  • IoC-Container (z. B. NSpring)
  • Designmuster
  • FESTES Prinzip

Ich möchte das www.dimecasts.net als beeindruckende Quelle für kostenlose Screencasts von guter Qualität empfehlen, die alle oben genannten Themen abdecken.

Radek
quelle
19

Dies ist sehr subjektiv, aber hier sind einige Punkte, die ich zu Ihrem Code machen möchte:

  • Meiner Meinung nach haben Sie gemischt Productund ShoppingCartItem. Productsollte den Produktnamen, den Steuerstatus usw. haben, aber nicht die Menge. Die Menge ist keine Eigenschaft eines Produkts - sie ist für jeden Kunden des Unternehmens, der dieses bestimmte Produkt kauft, unterschiedlich.

  • ShoppingCartItemsollte eine Productund die Menge haben. Auf diese Weise kann der Kunde mehr oder weniger des gleichen Produkts frei kaufen. Mit Ihrem aktuellen Setup ist das nicht möglich.

  • Die Berechnung der endgültigen Steuer sollte auch nicht Teil von sein Product- es sollte Teil von so etwas sein, ShoppingCartda die endgültige Steuerberechnung möglicherweise die Kenntnis aller Produkte im Warenkorb beinhaltet.

xxbbcc
quelle
Das einzige Problem, das ich mit dieser Antwort habe, ist, dass sie beschreibt, wie ein besseres Produktzahlungssystem aufgebaut werden kann (was gültig ist), aber die OOP-Methoden nicht wirklich ausführlich beschreibt. Dies kann in jeder Sprache implementiert werden. Ohne irgendeine Art von Schnittstellen, Vererbung, Polymorphismus usw. zu zeigen, würde er den Test immer noch nicht bestehen.
Timeout
In Bezug auf den letzten Punkt: IMO bester Ort für die Steuerberechnung ist eine separate TaxCalculator-Klasse aufgrund des Prinzips der Einzelverantwortung.
Radek
Danke für die Antwort, aber wie praktisch es ist. arbeitet jedes Unternehmen in so umfangreichen und reinen OOPS-Modellen?
Sunder
@shyamsunder An meiner Antwort ist nichts wirklich Reines. Es werden keine Schnittstellen / Vererbungen verwendet, die wichtige Aspekte von OOD sind, aber es zeigt - meiner Meinung nach - das wichtigste Prinzip, und das bringt Verantwortlichkeiten dahin, wo sie hingehören. Wie andere Antworten hervorhoben, besteht das Hauptproblem bei Ihrem Design darin, dass Sie die Verantwortlichkeiten zwischen verschiedenen Akteuren vertauscht haben und dies zu Problemen beim Hinzufügen von Funktionen führt. Die meisten großen Programme können nur wachsen, wenn sie diesen Prinzipien folgen.
xxbbcc
Gute Antwort, aber ich stimme auch zu, dass die Steuerberechnung ein separates Objekt sein sollte.
14

Zunächst einmal ist dies eine sehr gute Interviewfrage. Es ist ein gutes Maß für viele Fähigkeiten.

Es gibt viele Dinge, die Sie verstehen müssen, um eine gute Antwort zu geben (es gibt keine perfekte Antwort), sowohl auf hoher als auch auf niedriger Ebene. Hier sind ein paar:

  • Domänenmodellierung -> Wie erstellen Sie ein gutes Modell der Lösung? Welche Objekte erstellen Sie? Wie werden sie die Anforderungen lösen? Die Suche nach den Substantiven ist ein guter Anfang, aber wie entscheiden Sie, ob Ihre Auswahl an Entitäten gut ist? Welche anderen Entitäten benötigen Sie? Welches Domain-Wissen benötigen Sie, um es zu lösen?
  • Trennung von Bedenken, lose Kopplung, hohe Kohäsion -> Wie trennen Sie die Teile des Designs, die unterschiedliche Bedenken oder Änderungsraten haben, und in welcher Beziehung stehen Sie zu ihnen? Wie halten Sie Ihr Design flexibel und aktuell?
  • Unit-Test, Refactoring, TDD -> Wie gehen Sie vor , um eine Lösung zu finden? Schreiben Sie Tests, verwenden Sie Scheinobjekte, refaktorieren Sie, iterieren Sie?
  • Sauberer Code, Sprachsprachen -> Verwenden Sie die Funktionen Ihrer Programmiersprache, um Ihnen zu helfen? Schreiben Sie verständlichen Code? Sind Ihre Abstraktionsebenen sinnvoll? Wie wartbar ist der Code?
  • Tools : Verwenden Sie die Quellcodeverwaltung? Werkzeuge bauen? IDEs?

Von dort aus können Sie viele interessante Diskussionen führen, die Entwurfsprinzipien (wie die SOLID-Prinzipien), Entwurfsmuster, Analysemuster, Domänenmodellierung, Technologieoptionen und zukünftige Entwicklungspfade umfassen (z. B. was ist, wenn ich eine Datenbank oder eine umfangreiche UI-Ebene hinzufüge). Was muss geändert werden?), Kompromisse, nicht funktionale Anforderungen (Leistung, Wartbarkeit, Sicherheit, ...), Abnahmetests usw.

Ich werde nicht kommentieren, wie Sie Ihre Lösung ändern sollten, nur dass Sie sich mehr auf diese Konzepte konzentrieren sollten.

Aber ich kann Ihnen nur als Beispiel (in Java) zeigen, wie ich dieses Problem (teilweise) gelöst habe . Schauen Sie in der ProgramKlasse nach, wie alles zusammenkommt, um diese Quittung auszudrucken:

------------------ DAS IST IHRE BESTELLUNG ------------------
(001) Domain Driven Design ----- 69,99 USD
(001) Wachsende objektorientierte Software ----- 49,99 USD
(001) House MD Staffel 1 ----- 29,99 USD
(001) House MD Staffel 7 ----- $ 34,50
(IMD) Wachsende objektorientierte Software ----- $ 2,50
(BST) House MD Staffel 1 ----- $ 3,00
(BST) House MD Staffel 7 ----- $ 3,45
(IMD) House MD Staffel 7 ----- $ 1,73
                                INSGESAMT ----- $ 184,47
                                STEUER GESAMT ----- $ 10,68
                                    GESAMT ----- $ 195.15
---------------- DANKE FÜR DIE AUSWAHL UNS ----------------

Sie sollten sich unbedingt diese Bücher ansehen :-)

Nur als Einschränkung: Meine Lösung ist immer noch sehr unvollständig. Ich habe mich nur auf das Happy-Path-Szenario konzentriert, um eine gute Grundlage zu haben, auf der ich aufbauen kann.

Jordão
quelle
Ich habe Ihre Lösung durchgesehen und fand sie ziemlich interessant. Obwohl ich der Meinung bin, dass die Order-Klasse nicht für das Drucken eines Empfangs verantwortlich sein sollte. Ebenso sollte die TaxMethod-Klasse nicht für die Berechnung der Steuer verantwortlich sein. Außerdem sollte TaxMethodPractice keine Liste von TaxMethod enthalten. Stattdessen sollte eine Klasse namens SalesPolicy diese Liste enthalten. Einer Klasse namens SalesEngine sollten eine SalesPolicy, eine Order und ein TaxCalculator übergeben werden. SalesEngine wendet die SalesPolicy auf die Artikel in der Bestellung an und berechnet die Steuer mit dem TaxCalculator
CKing
@bot: interessante Beobachtungen .... Druckt gerade Orderdie Quittung aus, Receiptkennt aber die eigene Formatierung. Außerdem ist TaxMethodPractice eine Art Steuerrichtlinie, die alle Steuern enthält, die für ein bestimmtes Szenario gelten. TaxMethods sind Steuerrechner. Ich habe das Gefühl, dass Ihnen nur eine höhere Bindungsklasse fehlt , wie Ihre vorgeschlagene SalesEngine. Das ist eine interessante Idee.
Jordão
Ich bin einfach der Meinung, dass jede Klasse eine einzige klar definierte Verantwortung haben muss und Klassen, die Objekte der realen Welt darstellen, sich in einer Weise verhalten sollten, die mit der realen Welt übereinstimmt. In diesem Fall kann eine TaxMethod in zwei Klassen aufgeteilt werden. Eine TaxCriteria und ein TaxCalculator. Ebenso darf eine Bestellung keine Quittung drucken. Einem ReceiptGenerator sollte eine Quittung zum Generieren einer Quittung übergeben werden.
CKing
@bot: Ich stimme vollkommen zu! Gute Designs sind fest ! Eine TaxMethod ist ein Steuerrechner und ein TaxEligibilityCheck ist ein Steuerkriterium. Sie sind getrennte Einheiten. Was die Quittung betrifft, würde eine Aufteilung des Erzeugungsteils das Design weiter verbessern.
Jordão
1
Diese Idee stammt aus dem Spezifikationsmuster , werfen Sie einen Blick darauf!
Jordão
12

Abgesehen von der Tatsache, dass Sie eine Klasse namens Produkt verwenden, haben Sie nicht nachgewiesen, dass Sie wissen, was Vererbung ist. Sie haben keine mehrfach klassifizierte Vererbung von Produkt erstellt, keinen Polymorphismus. Das Problem hätte mit mehreren OOP-Konzepten gelöst werden können (auch um zu zeigen, dass Sie sie kennen). Dies ist ein Interviewproblem, daher möchten Sie zeigen, wie viel Sie wissen.

Ich würde mich jetzt jedoch nicht in eine Depression verwandeln. Die Tatsache, dass Sie sie hier nicht demonstriert haben, bedeutet nicht, dass Sie sie noch nicht kennen oder nicht lernen können.

Sie brauchen nur etwas mehr Erfahrung mit OOP oder Interviews.

Viel Glück!

Andrei G.
quelle
Eigentlich war dies mein erstes Design. Ich habe ein anderes erstellt, kann es Ihnen aber nicht zeigen, da die Zeichenbeschränkung überschritten wird.
Sunder
Können Sie es anhand eines Beispiels demonstrieren?
Sunder
@sunder: Sie können die Frage einfach mit Ihrem neuen Design aktualisieren.
Bjarke Freund-Hansen
10

Menschen, die mit OOP angefangen haben, Programmieren zu lernen, haben keine großen Probleme zu verstehen, was es bedeutet, weil es genauso ist wie im wirklichen Leben . Wenn Sie mit anderen Programmierkenntnissen als OO vertraut sind, ist dies möglicherweise schwieriger zu verstehen.

Schalten Sie zunächst Ihren Bildschirm aus oder beenden Sie Ihre Lieblings-IDE. Nehmen Sie ein Papier und einen Bleistift und machen Sie eine Liste von Entitäten , Beziehungen , Menschen , Maschinen , Prozesse , Material usw. alles , die in Ihr endgültiges Programm begegnet werden könnten.

Zweitens versuchen Sie, die verschiedenen grundlegenden Entitäten zu erhalten. Sie werden verstehen, dass einige Eigenschaften oder Fähigkeiten gemeinsam nutzen können. Sie müssen sie in abstrakte Objekte einfügen . Sie sollten beginnen, ein schönes Schema Ihres Programms zu zeichnen.

Als nächstes müssen Sie Funktionen (Methoden, Funktionen, Unterprogramme, wie gewünscht aufrufen) eingeben: Beispielsweise sollte ein Produktobjekt nicht in der Lage sein, die Umsatzsteuer zu berechnen . Ein Sales Engine Objekt sollte.

Machen Sie beim ersten Mal keine Probleme mit all den großen Wörtern ( Schnittstellen , Eigenschaften , Polymorphismus , Erbe usw.) und Designmustern. Versuchen Sie nicht einmal, schönen Code oder was auch immer zu erstellen. Denken Sie nur an einfache Objekte und Wechselwirkungen zwischen ihm wie im wirklichen Leben .

Versuchen Sie anschließend, eine ernsthafte, prägnante Literatur darüber zu lesen. Ich denke, Wikipedia und Wikibooks sind ein wirklich guter Weg, um zu beginnen und dann einfach etwas über GoF und Design Patterns und UML zu lesen .

smonff
quelle
3
+1 für "Schalten Sie zuerst Ihren Bildschirm aus". Ich denke, die Kraft des Denkens wird zu oft mit der Kraft des Rechnens verwechselt.
Kontur
1
+1 für den einfachsten Ansatz mit Bleistift und Papier. Oft sind die Leute verwirrt, wenn sie vor der IDE sitzen :)
Neeraj Gulia
Einige Wissenschaftler sagten, dass unser Gehirn beim Betrachten eines Bildschirms unaufmerksam ist. Wenn ich Software-Architekturdesign studiere, bringt uns unser Lehrer dazu, auf Papier zu arbeiten. Er hat nichts gegen leistungsstarke UML-Software. Wichtig ist, zuerst die Dinge zu verstehen.
Smonff
4

Mischen Sie zuerst keine ProductKlasse mit der Receipt ( ShoppingCart) - Klasse, die quantitysollte Teil von ReceipItem( ShoppingCartItem) sein, sowie Tax& Cost. Das TotalTax& TotalCostsollte ein Teil von sein ShoppingCart.

Meine ProductKlasse hat nur Name& Price& einige schreibgeschützte Eigenschaften wie 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); } }
}

Ihr Steuerberechnungsteil ist gekoppelt mit Product. Ein Produkt definiert keine Steuerrichtlinien, sondern Steuerklassen. Basierend auf der Beschreibung des Problems gibt es zwei Arten von Umsatzsteuern: Basicund DutySteuern. Sie können verwenden Template Method Design Pattern, um es zu erreichen:

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; } }
}

Und schließlich eine Klasse, um Steuern anzuwenden:

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));
        }

    }
}

Sie können sie bei MyFiddle ausprobieren .

Daniel B.
quelle
2

Ein sehr guter Ausgangspunkt für Entwurfsregeln sind die SOLID- Prinzipien.

Das Open Closed-Prinzip besagt beispielsweise, dass Sie, wenn Sie neue Funktionen hinzufügen möchten, der vorhandenen Klasse keinen Code hinzufügen müssen, sondern eine neue Klasse hinzufügen müssen.

Für Ihre Beispielanwendung würde dies bedeuten, dass für das Hinzufügen einer neuen Umsatzsteuer eine neue Klasse hinzugefügt werden muss. Gleiches gilt für verschiedene Produkte, die Ausnahmen von der Regel darstellen.

Die Rundungsregel gilt offensichtlich für eine separate Klasse - das Prinzip der Einzelverantwortung besagt, dass jede Klasse eine Einzelverantwortung hat.

Ich denke, der Versuch, den Code selbst zu schreiben, würde weitaus mehr Nutzen bringen, als einfach eine gute Lösung zu schreiben und sie hier einzufügen.

Ein einfacher Algorithmus zum Schreiben des perfekt gestalteten Programms wäre:

  1. Schreiben Sie einen Code, der das Problem löst
  2. Überprüfen Sie, ob der Code den SOLID-Prinzipien entspricht
  3. Wenn es Regelverstöße gibt, gehe zu 1.
devdimi
quelle
2

Eine perfekte OOP-Implementierung ist völlig umstritten. Nach dem, was ich in Ihrer Frage sehe, können Sie den Code basierend auf der Rolle, die sie für die Berechnung des Endpreises wie Produkt, Steuer, ProduktDB usw. spielen, modularisieren.

  1. Productkönnte eine abstrakte Klasse sein und die abgeleiteten Typen wie Bücher, Lebensmittel könnten davon geerbt werden. Die steuerliche Anwendbarkeit kann durch die abgeleiteten Typen entschieden werden. Das Produkt würde anhand der abgeleiteten Klasse feststellen, ob die Steuer anwendbar ist oder nicht.

  2. TaxCriteria kann eine Aufzählung sein und diese kann während des Kaufs angegeben werden (importiert, Anwendbarkeit der Umsatzsteuer).

  3. TaxKlasse berechnet Steuern basierend auf TaxCriteria.

  4. Mit einem ShoppingCartItemvon XXBBCC vorgeschlagenen Wert können Produkt- und Steuerinstanzen gekapselt werden. Dies ist eine hervorragende Möglichkeit, Produktdetails nach Menge, Gesamtpreis nach Steuern usw. zu trennen.

Viel Glück.

Karthik
quelle
1

Aus einer rein OOA / D-Perspektive ist ein Hauptproblem, das ich sehe, dass die meisten Ihrer Klassenattribute den redundanten Namen der Klasse im Attributnamen haben. zB Produkt Preis, typeOf Produkt . In diesem Fall haben Sie überall dort, wo Sie diese Klasse verwenden, übermäßig ausführlichen und etwas verwirrenden Code, z. B. product.productName. Entfernen Sie das redundante Klassennamenpräfix / -suffix aus Ihren Attributen.

Außerdem habe ich keine Klassen gesehen, die sich mit dem Kauf und der Erstellung einer Quittung befassten, wie in der Frage gestellt.

Peter Cetinski
quelle
1

Hier ist ein großartiges Beispiel für ein OO-Muster für Produkte, Steuern usw. Beachten Sie die Verwendung von Schnittstellen, die für das OO-Design unerlässlich sind.

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

Chris Gessler
quelle
3
Ich würde es vorziehen, das Produkt zu einer (abstrakten) Klasse zu machen, anstatt es zu einer Schnittstelle zu machen. Ich würde auch nicht jedes Produkt zu einer eigenen Klasse machen. Ich würde höchstens eine Klasse pro Kategorie erstellen.
CodesInChaos
@CodeInChaos - Meistens benötigen Sie beides, aber wenn Sie versuchen, einen Job als Architekt zu bekommen, würde ich Interfaces über eine Abstract-Klasse implementieren.
Chris Gessler
1
Schnittstellen in diesem Beispiel haben überhaupt keinen Sinn. Sie führen nur zu einer Codeduplizierung in jeder Klasse, die sie implementiert. Jede Klasse implementiert es auf die gleiche Weise.
Piotr Perak
0

Das Problem mit den Kosten mit Steuern wurde mithilfe eines Besuchermusters angegriffen.

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);
    }
LucidCoder
quelle
Willkommen bei stackoverflow. Bitte stellen Sie sicher, dass Sie Ihre Antwort als Antwort auf die Frage erklären. Das OP sucht nicht nur nach einer Lösung, sondern warum eine Lösung besser / schlechter ist.
Simon.SA