Come creare l'applicazione OOP perfetta [chiuso]


98

Recentemente stavo cercando una società "x". Mi hanno inviato alcune serie di domande e mi hanno detto di risolverne solo una.

Il problema è così:

L'imposta di base sulle vendite è applicabile con un'aliquota del 10% su tutti i beni, ad eccezione di libri, cibo e prodotti medici che sono esenti.
Il dazio all'importazione è un'imposta aggiuntiva sulle vendite applicabile a tutte le merci importate con un'aliquota del 5%, senza esenzioni.

Quando acquisto gli articoli ricevo una ricevuta che elenca il nome di tutti gli articoli e il loro prezzo (tasse incluse), terminando con il costo totale degli articoli e gli importi totali delle imposte sulle vendite pagate.
Le regole di arrotondamento per l'imposta sulle vendite sono che per un'aliquota fiscale di n%, un prezzo di scaffale di p contiene (np / 100 arrotondato allo 0,05 più vicino) importo dell'imposta sulle vendite.

"Mi hanno detto che sono interessati all'aspetto progettuale della tua soluzione e vorrebbero valutare le mie capacità di programmazione orientata agli oggetti ."

Questo è ciò che hanno detto con le loro stesse parole

  • Per la soluzione, vorremmo utilizzare Java, Ruby o C #.
  • Siamo interessati all'ASPETTO PROGETTUALE della vostra soluzione e vorremmo valutare le vostre capacità di programmazione orientata agli oggetti .
  • È possibile utilizzare librerie o strumenti esterni per scopi di creazione o test. In particolare, puoi utilizzare le librerie di unit test o creare strumenti disponibili per la lingua scelta (ad esempio, JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake ecc.)
  • Facoltativamente, puoi anche includere una breve spiegazione del tuo design e dei tuoi presupposti insieme al tuo codice.
  • Si prega di notare che NON ci aspettiamo un'applicazione basata sul Web o un'interfaccia utente completa. Piuttosto, ci aspettiamo una semplice applicazione basata su console e interessati al tuo codice sorgente.

Quindi ho fornito il codice di seguito: puoi semplicemente copiare il codice incolla ed eseguire in 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
     }
 }

è possibile rimuovere l'input dalla rete ed eseguire per diversi input.

Ho fornito la soluzione ma sono stato rifiutato.

"Hanno detto che non possono considerarmi per le nostre attuali posizioni aperte perché la soluzione del codice non è soddisfacente".

Per favore, guidami cosa manca qui. Questa soluzione non è una buona soluzione OOAD.
Come posso migliorare le mie abilità OOAD.
I miei anziani dicono anche che una perfetta applicazione OOAD non funzionerà praticamente.

Grazie


2
Forse si aspettavano che tu distinguessi tra i tipi di prodotto utilizzando una gerarchia di ereditarietà, piuttosto che un'enumerazione? (Anche se penso che questo approccio sarebbe piuttosto complicato per lo scenario dato.)
Douglas

La mia ipotesi è che abbiano rifiutato la tua soluzione principalmente perché non hai definito alcuna interfaccia.
Chris Gessler

28
Come regola generale, se qualcuno ti chiede in una situazione di colloquio di dimostrare abilità OOP, dovresti cercare di evitare di usare un'istruzione switch, invece usa una gerarchia di ereditarietà.
Joe

4
Dovrebbe essere pubblicato nella revisione del codice.
Derek

Avevo postato anche lì ma non riuscivo a trovare una buona soluzione lì. Ma tutti possono vedere la mia nuova soluzione che ho creato dopo l'aiuto di altri codeproject.com/Questions/332077/… qui puoi trovare anche il mio nuovo codice.
sabato

Risposte:


246

Prima di tutto buon cielo non fate calcoli finanziari in doppio . Fare calcoli finanziari in decimale ; ecco a cosa serve. Usa il doppio per risolvere problemi di fisica , non problemi finanziari .

Il principale difetto di progettazione del tuo programma è che la politica si trova nel posto sbagliato . Chi è responsabile del calcolo delle tasse? Hai incaricato il prodotto di calcolare le tasse, ma quando acquisti una mela o un libro o una lavatrice, la cosa che stai per acquistare non è responsabile di dirti quante tasse pagherai esso. La politica del governo è responsabile di dirtelo. Il tuo progetto viola in maniera massiccia il principio di base del design OO secondo cui gli oggetti dovrebbero essere responsabili delle proprie preoccupazioni e non di quelle di chiunque altro. La preoccupazione di una lavatrice è lavare i vestiti, non addebitare il giusto dazio all'importazione. Se le leggi fiscali cambiano, tu non vuoi cambiarel'oggetto lavatrice , si desidera modificare l'oggetto criterio .

Quindi, come affrontare questo tipo di problemi in futuro?

Avrei iniziato evidenziando ogni nome importante nella descrizione del problema:

L'imposta di base sulle vendite è applicabile con un'aliquota del 10% su tutti i beni , ad eccezione di libri , cibo e prodotti medici che sono esenti. Il dazio all'importazione è un'imposta aggiuntiva sulle vendite applicabile a tutte le merci importate con un'aliquota del 5%, senza esenzioni . Quando acquisto gli articoli ricevo una ricevuta che elenca il nome di tutti gli articoli e il loro prezzo ( tasse incluse ), terminando con il costo totaledegli articoli e l'importo totale delle imposte sulle vendite pagate. Le regole di arrotondamento per l' imposta sulle vendite sono che per un'aliquota fiscale di n%, un prezzo di scaffale di p contiene (np / 100 arrotondato allo 0,05 più vicino) importo dell'imposta sulle vendite .

Ora, quali sono le relazioni tra tutti quei nomi?

  • L'imposta di base sulle vendite è una sorta di imposta sulle vendite
  • Il dazio all'importazione è una sorta di imposta sulle vendite
  • Un'imposta sulle vendite ha un'aliquota decimale
  • I libri sono una specie di oggetto
  • Il cibo è una specie di oggetto
  • I prodotti medici sono una sorta di oggetto
  • Gli articoli possono essere merci importate
  • Un oggetto ha un nome che è una stringa
  • Un articolo ha un prezzo di scaffale che è un decimale. (Nota: un articolo ha davvero un prezzo? Due lavatrici identiche potrebbero essere in vendita a prezzi diversi in negozi diversi o nello stesso negozio in momenti diversi. Un design migliore potrebbe essere quello di dire che una politica dei prezzi si riferisce a un articolo a il suo prezzo.)
  • Una politica di esenzione dall'imposta sulle vendite descrive le condizioni in cui l'imposta sulle vendite non è applicabile su un articolo.
  • Una ricevuta ha un elenco di articoli, i relativi prezzi e le relative imposte.
  • Una ricevuta ha un totale
  • Una ricevuta ha un'imposta totale

... e così via. Dopo aver elaborato tutte le relazioni tra tutti i nomi, puoi iniziare a progettare una gerarchia di classi. C'è una classe base astratta Item. Il libro eredita da esso. C'è una classe astratta SalesTax; BasicSalesTax eredita da esso. E così via.


12
hai bisogno di più di quanto è stato appena fornito? Sembra che tu abbia bisogno di saperne di più su come viene implementata l'ereditarietà e cos'è il polimorfismo.
Induster

27
@sunder: questa risposta è più che sufficiente. Ora è tua responsabilità sviluppare le tue capacità, magari usando questo come primo esempio. Nota che il tuo esempio è la definizione di un esempio di vita reale. Hai fallito un colloquio nella vita reale perché questo codice nella vita reale necessitava di un progetto reale che non hai fornito.
Greg D,

9
@Narayan: doubleè l'ideale per le situazioni in cui essere entro lo 0,00000001% della risposta giusta è più che sufficiente. Se vuoi capire quanto velocemente cade un mattone dopo mezzo secondo, fai i conti in doppio. Quando fai arithemtic finanziario in doppio, ti ritroverai con risposte come il prezzo al netto delle tasse 43,79999999999999 dollari e questo sembra sciocco anche se è estremamente vicino alla risposta corretta.
Eric Lippert,

31
+1 Hai evidenziato un esercizio straordinario, che consiste nell'esaminare ogni sostantivo nel problema dichiarato, e poi enumerare le loro relazioni tra loro. Grande idea.
Chris Tonkinson

3
@ Jordão: In decimale, aggiungendo 0,10 dieci volte si ottiene 1,00. Ma aggiungendo 1.0 / 333.0 trecentotrentatre volte non si ottiene necessariamente uno in decimale o doppio. In decimale, le frazioni che hanno potenze di dieci al denominatore sono rappresentate esattamente; in doppio, è frazioni con potenze di due. Qualsiasi altra cosa è rappresentata approssimativamente.
Eric Lippert

38

Se l'azienda dice qualcosa su librerie come NUnit, JUnit o Test :: Unit è più che probabile che TDD sia davvero importante per loro. Nel tuo esempio di codice non sono presenti test.

Proverei a dimostrare una conoscenza pratica di:

  • Test unitari (es. NUnit)
  • Mocking (es. RhinoMocks)
  • Persistenza (es. NHibernate)
  • Contenitori IoC (ad es. NSpring)
  • modelli di progettazione
  • Principio SOLIDO

Vorrei raccomandare www.dimecasts.net come fonte impressionante di screencast gratuiti e di buona qualità che coprono tutti gli argomenti sopra menzionati.


19

Questo è altamente soggettivo, ma qui ci sono alcuni punti che vorrei fare sul tuo codice:

  • Secondo me hai mescolato Producte ShoppingCartItem. Productdovrebbe avere il nome del prodotto, lo status fiscale, ecc. ma non la quantità. La quantità non è una proprietà di un prodotto: sarà diversa per ogni cliente dell'azienda che acquista quel particolare prodotto.

  • ShoppingCartItemdovrebbe avere un Producte la quantità. In questo modo il cliente può acquistare liberamente più o meno lo stesso prodotto. Con la tua configurazione attuale non è possibile.

  • Anche il calcolo dell'imposta finale non dovrebbe far parte del Product- dovrebbe far parte di qualcosa di simile ShoppingCartpoiché il calcolo dell'imposta finale potrebbe comportare la conoscenza di tutti i prodotti nel carrello.


L'unico problema che ho con questa risposta è che descrive come costruire un sistema di pagamento del prodotto migliore (che è valido) ma non elabora realmente le metodologie OOP. Questo potrebbe essere implementato in qualsiasi lingua. Senza mostrare una sorta di interfaccia, ereditarietà, polimorfismo, ecc. Fallirebbe comunque il test.
Timeout

In riferimento all'ultimo punto: il posto migliore IMO per il calcolo delle imposte è la classe TaxCalculator separata a causa del principio di responsabilità unico.
Radek

grazie per la risposta ma quanto è pratico. ogni azienda lavora in modelli OOPS così estesi e puri.
sunder

@shyamsunder Non c'è niente di veramente puro nella mia risposta. Non usa interfacce / ereditarietà che sono aspetti importanti dell'OOD, ma mostra il principio più importante - a mio parere - e cioè mettere le responsabilità al loro posto. Come sottolineato da altre risposte, il problema principale con il tuo design è che hai confuso le responsabilità tra i vari attori e questo porterà a problemi quando aggiungi le funzionalità. La maggior parte dei software di grandi dimensioni può crescere solo se seguono questi principi.
xxbbcc

Buona risposta, ma sono anche d'accordo sul fatto che il calcolo delle tasse dovrebbe essere un oggetto separato.

14

Prima di tutto, questa è un'ottima domanda per l'intervista. È un buon indicatore di molte abilità.

Ci sono molte cose che devi capire per fornire una buona risposta (non esiste una risposta perfetta), sia di alto livello che di basso livello. Eccone un paio:

  • Domain Modeling -> come si crea un buon modello della soluzione? Quali oggetti crei? Come risolveranno i requisiti? Cercare i nomi è un buon inizio, ma come decidi se la tua scelta di entità è buona? Di quali altre entità hai bisogno? Di quali conoscenze di dominio hai bisogno per risolverlo?
  • Separazione delle preoccupazioni, accoppiamento lento, alta coesione -> Come separare le parti del progetto che hanno preoccupazioni o tassi di cambiamento diversi e come le relazionate? Come mantieni il tuo design flessibile e attuale?
  • Unit testing, refactoring, TDD -> Qual è il tuo processo per trovare una soluzione? Scrivete test, usate oggetti fittizi, refactoring, iterate?
  • Codice pulito, idiomi linguistici -> Utilizzi le funzionalità del tuo linguaggio di programmazione per aiutarti? Scrivi codice comprensibile? I tuoi livelli di astrazione hanno un senso? Quanto è manutenibile il codice?
  • Strumenti : usi il controllo del codice sorgente? Costruire strumenti? IDE?

Da lì, puoi avere molte discussioni interessanti, che coinvolgono principi di progettazione (come i principi SOLID), modelli di progettazione, modelli di analisi, modelli di dominio, scelte tecnologiche, percorsi di evoluzione futuri (ad esempio, cosa succede se aggiungo un database o un ricco livello di interfaccia utente, cosa deve cambiare?), compromessi, requisiti non funzionali (prestazioni, manutenibilità, sicurezza, ...), test di accettazione, ecc ...

Non commenterò come dovresti cambiare la tua soluzione, ma solo che dovresti concentrarti maggiormente su questi concetti.

Ma posso mostrarti come ho (parzialmente) risolto questo problema , solo come esempio (in Java). Guarda in Programclasse per vedere come funziona tutto per stampare questa ricevuta:

------------------ QUESTO È IL TUO ORDINE ------------------
(001) Domain Driven Design ----- $ 69,99
(001) Software orientato agli oggetti in crescita ----- $ 49,99
(001) House MD Stagione 1 ----- $ 29,99
(001) House MD Stagione 7 ----- $ 34,50
(IMD) Software orientato agli oggetti in crescita ----- $ 2,50
(BST) House MD Stagione 1 ----- $ 3,00
(BST) House MD Stagione 7 ----- $ 3,45
(IMD) House MD Stagione 7 ----- $ 1,73
                                SUB-TOTALE ----- $ 184,47
                                TOTALE FISCALE ----- $ 10,68
                                    TOTALE ----- $ 195,15
---------------- GRAZIE PER AVER SCELTO NOI ----------------

Dovresti assolutamente dare un'occhiata a quei libri :-)

Un avvertimento: la mia soluzione è ancora molto incompleta, mi sono solo concentrato sullo scenario del percorso felice per avere una buona base su cui costruire.


Ho esaminato la tua soluzione e l'ho trovata piuttosto interessante. Anche se ritengo che la classe Order non dovrebbe essere responsabile della stampa di una ricevuta. Allo stesso modo, la classe TaxMethod non dovrebbe essere responsabile del calcolo delle tasse. Inoltre, TaxMethodPractice non deve contenere un elenco di TaxMethod. Invece, una classe chiamata SalesPolicy dovrebbe contenere questo elenco. A una classe denominata SalesEngine dovrebbe essere passata una SalesPolicy, un Order e un TaxCalculator. SalesEngine applicherà la politica di vendita sugli articoli nell'ordine e calcolerà l'imposta utilizzando il calcolatore delle imposte
CKing

@bot: osservazioni interessanti .... In questo momento, Orderstampa la ricevuta, ma Receiptconosce la propria formattazione. Inoltre, TaxMethodPractice è una sorta di politica fiscale, contiene tutte le tasse che si applicano a un determinato scenario. TaxMethods sono calcolatori delle tasse. Sento che ti manca solo una classe di associazione di livello superiore , come il tuo SalesEngine proposto. È un'idea interessante.
Jordão

Sento solo che ogni classe deve avere una singola responsabilità ben definita e le classi che rappresentano oggetti del mondo reale dovrebbero comportarsi in un modo che sia in linea con il mondo reale. Per questo motivo un TaxMethod può essere suddiviso in due classi. Un TaxCriteria e un TaxCalculator. Allo stesso modo un Ordine non deve stampare una ricevuta. Un ReceiptGenerator deve essere passato a Receipt per generare una ricevuta.
CKing

@bot: sono completamente d'accordo! I buoni design sono SOLIDI ! Un TaxMethod è un calcolatore delle imposte e un TaxEligibilityCheck è un criterio fiscale. Sono entità separate. Per quanto riguarda la ricevuta, sì, dividere la parte generatrice migliorerebbe ulteriormente il design.
Jordão

1
Questa idea viene dal modello di specifica , dai un'occhiata!
Jordão

12

Tranne il fatto che stai usando una classe chiamata prodotto, non hai dimostrato di sapere cosa sia l'ereditarietà, non hai creato eredità di classi multiple da Product, nessun polimorfismo. Il problema avrebbe potuto essere risolto utilizzando più concetti OOP (anche solo per dimostrare che li conosci). Questo è un problema di intervista, quindi vuoi mostrare quanto sai.

Tuttavia non mi trasformerei in depressione adesso. Il fatto che non li hai dimostrati qui non significa che non li conosci già o non sei in grado di impararli.

Hai solo bisogno di un po 'più di esperienza con OOP o interviste.

In bocca al lupo!


in realtà questo è stato il mio primo design, ne ho creato un altro ma non posso mostrarti come il limite di caratteri sta superando.
sunder

puoi dimostrarlo con l'aiuto di qualsiasi esempio.
sunder

@sunder: puoi semplicemente aggiornare la domanda con il tuo nuovo design.
Bjarke Freund-Hansen

10

Le persone che hanno iniziato ad imparare la programmazione con OOP non hanno grandi problemi a capire cosa significa, perché è proprio come nella vita reale . Se hai competenze con altre famiglie di programmazione oltre a OO, potrebbe essere più difficile da capire.

Prima di tutto, spegni lo schermo o esci dal tuo IDE preferito. Prendi un foglio e una matita e fai un elenco di entità , relazioni , persone , macchine , processi , cose , ecc. Tutto ciò che potrebbe essere incontrato nel tuo programma finale.

Secondo, prova a ottenere le diverse entità di base . Capirai che alcuni possono condividere proprietà o abilità , devi metterlo in oggetti astratti . Dovresti iniziare a disegnare un bel schema del tuo programma.

Successivamente devi inserire le funzionalità (metodi, funzioni, subroutine, chiamalo come vuoi): ad esempio, un oggetto prodotto non dovrebbe essere in grado di calcolare l'imposta sulle vendite . Un oggetto motore di vendita dovrebbe.

Non avere problemi con tutte le parole grosse ( interfacce , proprietà , polimorfismo , eredità , ecc.) E i modelli di progettazione in una prima volta, non provare nemmeno a creare un bel codice o altro ... Pensa solo a oggetti semplici e interruzioni tra di esso come nella vita reale .

Dopo, prova a leggere alcune letterature concise serie su questo. Penso che Wikipedia e Wikibooks siano un ottimo modo per iniziare e poi leggere solo cose su GoF, Design Patterns e UML .


3
+1 per "Prima di tutto, spegni lo schermo". Penso che il potere del pensiero sia troppo spesso scambiato per il potere dell'informatica.
kontur

1
+1 per aver adottato l'approccio più semplice di usare carta e matita. Molte volte le persone si confondono quando si siedono davanti all'IDE :)
Neeraj Gulia

Alcuni scienziati hanno affermato che il nostro cervello è disattento quando guarda uno schermo. Quando studio progettazione dell'architettura del software, il nostro insegnante ci fa lavorare sulla carta. Non gli importa dei potenti software UML. L'importante è capire prima le cose.
smonff

4

Per prima cosa non mescolare la Productclasse con la classe Receipt ( ShoppingCart), quantitydovrebbe far parte di ReceipItem( ShoppingCartItem), così come Tax& Cost. Il TotalTax& TotalCostdovrebbe far parte di ShoppingCart.

La mia Productclasse, ha solo Name& Price& alcune proprietà di sola lettura come 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); } }
}

La parte relativa al calcolo delle imposte è abbinata a Product. Un prodotto non definisce le politiche fiscali, ma le classi fiscali. In base alla descrizione del problema, esistono due tipi di imposte sulle vendite: Basice le Dutytasse. Puoi usare Template Method Design Patternper ottenerlo:

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

E infine una classe per applicare le tasse:

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

    }
}

Puoi provarli su MyFiddle .


2

Un ottimo punto di partenza sulle regole di progettazione sono i principi SOLID .

Ad esempio, il principio Open Closed afferma che se si desidera aggiungere nuove funzionalità non è necessario aggiungere codice alla classe esistente, ma piuttosto aggiungere una nuova classe.

Per la tua applicazione di esempio ciò significherebbe che l'aggiunta di una nuova imposta sulle vendite richiederebbe l'aggiunta di una nuova classe. Lo stesso vale per prodotti diversi che sono eccezioni alla regola.

La regola dell'arrotondamento va ovviamente in classi separate: il principio di responsabilità unica afferma che ogni classe ha un'unica responsabilità.

Penso che provare a scrivere il codice da soli porterebbe di gran lunga più vantaggi che semplicemente scrivere una buona soluzione e incollarla qui.

Un semplice algoritmo per scrivere il perfetto programma progettato sarebbe:

  1. Scrivi un codice che risolva il problema
  2. Verificare se il codice è conforme ai principi SOLID
  3. Se ci sono violazioni delle regole rispetto a goto 1.

2

Una perfetta implementazione OOP è completamente discutibile. Da quello che vedo nella tua domanda, potresti modulare il codice in base al ruolo che svolgono per calcolare il prezzo finale come Product, Tax, ProductDB e così via.

  1. Productpotrebbe essere una classe astratta e i tipi derivati ​​come Libri, Cibo potrebbero essere ereditati da essa. L'applicabilità fiscale può essere decisa dai tipi derivati. Il prodotto dirà se l'imposta è applicabile o meno in base alla classe derivata.

  2. TaxCriteria può essere un enum e può essere specificato durante l'acquisto (importato, applicabilità dell'imposta sulle vendite).

  3. Taxclass calcolerà le tasse in base a TaxCriteria.

  4. Avere un ShoppingCartItemcome suggerito da XXBBCC può incapsulare istanze di prodotti e tasse ed è un ottimo modo per separare i dettagli del prodotto con la quantità, il prezzo totale con le tasse ecc.

In bocca al lupo.


1

Da una prospettiva strettamente OOA / D, uno dei problemi principali che vedo è che la maggior parte degli attributi della tua classe ha il nome ridondante della classe nel nome dell'attributo. es. prezzo del prodotto , tipo del prodotto . In questo caso, ovunque si utilizzi questa classe si avrà un codice eccessivamente dettagliato e un po 'confuso, ad esempio product.productName. Rimuovere il prefisso / i suffissi del nome della classe ridondante dagli attributi.

Inoltre, non ho visto nessuna classe interessata all'acquisto e alla creazione di una ricevuta come richiesto nella domanda.


1

Ecco un ottimo esempio di un pattern OO per Prodotti, Tasse, ecc ... Notare l'uso delle interfacce, che è essenziale nella progettazione OO.

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


3
Preferirei rendere il prodotto una classe (astratta) piuttosto che un'interfaccia. Non renderei nemmeno ogni prodotto una classe separata. Al massimo creerei una classe per categoria.
CodesInChaos

@CodeInChaos - La maggior parte delle volte ti servono entrambi, ma se stai cercando di ottenere un lavoro come architetto, sceglierei di implementare le interfacce su una classe astratta.
Chris Gessler,

1
Le interfacce in questo esempio non hanno alcun senso. Portano solo alla duplicazione del codice in ogni classe che li implementa. Ogni classe lo implementa allo stesso modo.
Piotr Perak

0

Ha attaccato il problema del costo con le tasse utilizzando un modello di visitatore.

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

Benvenuto in stackoverflow. Assicurati di spiegare la tua risposta in risposta alla domanda. L'OP non sta solo cercando una soluzione, ma perché una soluzione è migliore / peggiore.
Simon.SA
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.