Comment créer l'application POO parfaite [fermé]

98

Récemment, j'essayais une entreprise «x». Ils m'ont envoyé une série de questions et m'ont dit de n'en résoudre qu'une.

Le problème est comme ça -

La taxe de vente de base est applicable à un taux de 10% sur tous les produits, à l'exception des livres, de la nourriture et des produits médicaux qui sont exonérés.
Le droit d'importation est une taxe de vente supplémentaire applicable sur toutes les marchandises importées au taux de 5%, sans exonération.

Lorsque j'achète des articles, je reçois un reçu indiquant le nom de tous les articles et leur prix (taxes comprises), en terminant par le coût total des articles et le montant total des taxes de vente payées.
Les règles d'arrondi pour la taxe de vente sont que pour un taux de taxe de n%, un prix de vente de p contient (np / 100 arrondi au 0,05 le plus proche) le montant de la taxe de vente.

«Ils m'ont dit qu'ils étaient intéressés par l' aspect conception de votre solution et qu'ils aimeraient évaluer mes compétences en programmation orientée objet

C'est ce qu'ils ont dit dans leurs propres mots

  • Pour la solution, nous voudrions que vous utilisiez Java, Ruby ou C #.
  • Nous sommes intéressés par l'ASPECT DE CONCEPTION de votre solution et souhaitons évaluer vos compétences en programmation orientée objet .
  • Vous pouvez utiliser des bibliothèques ou des outils externes à des fins de création ou de test. Plus précisément, vous pouvez utiliser des bibliothèques de tests unitaires ou créer des outils disponibles pour la langue de votre choix (par exemple, JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake, etc.)
  • Si vous le souhaitez, vous pouvez également inclure une brève explication de votre conception et de vos hypothèses avec votre code.
  • Veuillez noter que nous n'attendons PAS une application Web ou une interface utilisateur complète. Nous attendons plutôt une application simple, basée sur une console et intéressé par votre code source.

J'ai donc fourni le code ci-dessous - vous pouvez simplement copier du code et l'exécuter dans 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
     }
 }

vous pouvez désactiver l'entrée et l'exécuter pour différentes entrées.

J'ai fourni la solution mais j'ai été rejeté.

"Ils ont dit qu'ils ne pouvaient pas me considérer pour nos positions ouvertes actuelles parce que la solution de code n'est pas satisfaisante."

Veuillez me guider ce qui manque ici. Cette solution n'est-elle pas une bonne solution OOAD.
Comment puis-je améliorer mes compétences OOAD.
Mes aînés disent également qu'une application OOAD parfaite ne fonctionnera pas non plus pratiquement.

Merci

séparer
la source
2
Peut-être s'attendaient-ils à ce que vous distinguiez les types de produits en utilisant une hiérarchie d'héritage plutôt qu'une énumération? (Bien que je pense que cette approche serait plutôt alambiquée pour le scénario donné.)
Douglas
Je suppose qu'ils ont rejeté votre solution complètement parce que vous n'avez défini aucune interface.
Chris Gessler
28
En règle générale, si quelqu'un vous demande dans une situation d'entrevue de démontrer vos compétences en POO, vous devriez essayer d'éviter d'utiliser une instruction de commutation - utilisez plutôt une hiérarchie d'héritage.
Joe
4
Doit être publié dans la revue de code.
Derek
J'avais posté là aussi mais je n'ai pas pu trouver de bonne solution là-bas. Mais tout le monde peut voir ma nouvelle solution que j'ai créée après l'aide d'autres personnes codeproject.com/Questions/332077/ ... ici vous pouvez également trouver mon nouveau code.
Sunder le

Réponses:

246

Tout d'abord bon Dieu, ne faites pas de calculs financiers en double . Faites des calculs financiers en décimal ; c'est à cela que ça sert. Utilisez double pour résoudre des problèmes de physique , pas des problèmes financiers .

Le principal défaut de conception de votre programme est que la politique n'est pas au bon endroit . Qui est en charge du calcul des taxes? Vous avez chargé le produit de calculer les taxes, mais lorsque vous achetez une pomme, un livre ou une machine à laver, la chose que vous vous apprêtez à acheter n'est pas responsable de vous dire sur quel montant vous allez payer. il. La politique gouvernementale est chargée de vous le dire. Votre conception viole massivement le principe de base de conception OO selon lequel les objets doivent être responsables de leurs propres préoccupations , et non de celles de quiconque. Le souci d'une machine à laver est de laver vos vêtements, pas de facturer les droits d'importation appropriés. Si les lois fiscales changent, vous ne voulez pas changerl'objet machine à laver , vous souhaitez modifier l'objet de stratégie .

Alors, comment aborder ce genre de problèmes à l'avenir?

J'aurais commencé par mettre en évidence tous les noms importants dans la description du problème:

La taxe de vente de base est applicable à un taux de 10% sur tous les produits , à l'exception des livres , de la nourriture et des produits médicaux qui sont exonérés. Le droit d'importation est une taxe de vente supplémentaire applicable sur toutes les marchandises importées au taux de 5%, sans exonération . Lorsque j'achète des articles, je reçois un reçu qui répertorie le nom de tous les articles et leur prix ( taxes comprises ), en terminant par le coût totaldes articles et le montant total des taxes de vente payées. Les règles d'arrondi pour la taxe de vente sont que pour un taux de taxe de n%, un prix de vente de p contient (np / 100 arrondi au 0,05 le plus proche) le montant de la taxe de vente .

Maintenant, quelles sont les relations entre tous ces noms?

  • La taxe de vente de base est une sorte de taxe de vente
  • Les droits d'importation sont une sorte de taxe de vente
  • Une taxe de vente a un taux qui est un décimal
  • Les livres sont une sorte d'objet
  • La nourriture est une sorte d'article
  • Les produits médicaux sont une sorte d'article
  • Les articles peuvent être des marchandises importées
  • Un élément a un nom qui est une chaîne
  • Un article a un prix d'étagère qui est un décimal. (Remarque: un article a-t-il vraiment un prix? Deux machines à laver identiques peuvent être en vente à des prix différents dans différents magasins ou dans le même magasin à des moments différents. Une meilleure conception pourrait être de dire qu'une politique de tarification associe un article à son prix.)
  • Une politique d'exonération de la taxe de vente décrit les conditions dans lesquelles une taxe de vente est inapplicable sur un article.
  • Un reçu contient une liste d'articles, leurs prix et leurs taxes.
  • Un reçu a un total
  • Un reçu a une taxe totale

... etc. Une fois que toutes les relations entre tous les noms ont été élaborées, vous pouvez commencer à concevoir une hiérarchie de classes. Il existe un élément de classe de base abstrait. Le livre en hérite. Il existe une classe abstraite SalesTax; BasicSalesTax en hérite. Etc.

Eric Lippert
la source
12
vous avez besoin de plus que ce qui vient d'être fourni? On dirait que vous devez en savoir plus sur la façon dont l'héritage est implémenté et ce qu'est le polymorphisme.
Induster
27
@sunder: Cette réponse est plus que suffisante. Il est maintenant de votre responsabilité de développer vos compétences, peut-être en vous servant de cela comme premier exemple. Notez que votre exemple est la définition d'un exemple réel. Vous avez échoué à une interview réelle parce que ce code réel nécessitait une conception réelle que vous n'avez pas fournie.
Greg D
9
@Narayan: doubleest idéal pour les situations où être à 0,00000001% de la bonne réponse est plus que suffisant. Si vous voulez savoir à quelle vitesse tombe une brique après une demi-seconde, faites le calcul en double. Lorsque vous effectuez un calcul financier en double, vous vous retrouvez avec des réponses comme le prix après impôt est de 43,79999999999999 dollars et cela semble ridicule même si c'est extrêmement proche de la bonne réponse.
Eric Lippert
31
+1 Vous avez mis en évidence un exercice remarquable, qui consiste à examiner chaque nom dans le problème posé, puis à énumérer leurs relations les uns avec les autres. Bonne idée.
Chris Tonkinson
3
@ Jordão: En décimal, ajouter 0,10 fois dix fois donne 1,00. Mais ajouter 1,0 / 333,0 trois cent trente-trois fois ne donne pas nécessairement un en décimal ou en double. En décimal, les fractions qui ont des puissances de dix dans le dénominateur sont exactement représentées; en double, ce sont des fractions de puissances de deux. Tout le reste est représenté approximativement.
Eric Lippert
38

Si la société dit quelque chose à propos de bibliothèques comme NUnit, JUnit ou Test :: Unit, il est plus que probable que TDD est vraiment important pour elles. Dans votre exemple de code, il n'y a aucun test.

J'essaierais de démontrer une connaissance pratique de:

  • Tests unitaires (par exemple NUnit)
  • Mocking (par exemple RhinoMocks)
  • Persistance (par exemple NHibernate)
  • Conteneurs IoC (par exemple NSpring)
  • modèles de conception
  • Principe SOLID

Je voudrais recommander le www.dimecasts.net comme source impressionnante de screencasts gratuits et de bonne qualité qui couvre tous les sujets mentionnés ci-dessus.

Radek
la source
19

C'est très subjectif, mais voici quelques points que je ferais à propos de votre code:

  • À mon avis, vous avez mélangé Productet ShoppingCartItem. Productdoit avoir le nom du produit, le statut fiscal, etc. mais pas la quantité. La quantité n'est pas une propriété d'un produit - elle sera différente pour chaque client de l'entreprise qui achète ce produit particulier.

  • ShoppingCartItemdevrait avoir un Productet la quantité. De cette façon, le client peut acheter librement plus ou moins du même produit. Avec votre configuration actuelle, ce n'est pas possible.

  • Le calcul de la taxe finale ne devrait pas non plus faire partie du Product- cela devrait faire partie de quelque chose comme étant ShoppingCartdonné que le calcul final de la taxe peut impliquer la connaissance de tous les produits dans le panier.

xxbbcc
la source
Le seul problème que j'ai avec cette réponse est qu'elle décrit comment construire un meilleur système de paiement de produit (ce qui est valide) mais ne précise pas vraiment les méthodologies OOP. Cela pourrait être implémenté dans n'importe quelle langue. Sans montrer une sorte d'interface, d'héritage, de polymorphisme, etc., il échouerait quand même le test.
Timeout
En référence au dernier point: le meilleur endroit de l'OMI pour le calcul de la taxe est une classe TaxCalculator distincte en raison du principe de responsabilité unique.
Radek
merci pour la réponse, mais comme c'est pratique. chaque entreprise travaille-t-elle dans des modèles OOPS aussi étendus et purs.
Sunder le
@shyamsunder Il n'y a rien de vraiment pur dans ma réponse. Il n'utilise pas les interfaces / l'héritage qui sont des aspects importants de l'OOD, mais il montre le principe le plus important - à mon avis - et c'est de mettre les responsabilités à leur place. Comme d'autres réponses l'ont souligné, le principal problème avec votre conception est que vous confondez les responsabilités entre différents acteurs et cela entraînera des problèmes lors de l'ajout de fonctionnalités. La plupart des grands logiciels ne peuvent évoluer que s'ils suivent ces principes.
xxbbcc
Bonne réponse mais je suis également d'accord pour dire que le calcul de la taxe devrait être un objet distinct.
14

Tout d'abord, c'est une très bonne question d'entretien. C'est un bon indicateur de nombreuses compétences.

Il y a beaucoup de choses que vous devez comprendre pour fournir une bonne réponse (il n'y a pas de réponse parfaite), à ​​la fois de haut niveau et de bas niveau. En voici quelques-uns:

  • Modélisation de domaine -> comment créer un bon modèle de solution? Quels objets créez-vous? Comment résoudront-ils les exigences? La recherche des noms est un bon début, mais comment décidez-vous si votre choix d'entités est bon? De quelles autres entités avez-vous besoin? De quelles connaissances de domaine avez-vous besoin pour le résoudre?
  • Séparation des préoccupations, couplage lâche, cohésion élevée -> Comment séparez-vous les parties de la conception qui ont des préoccupations ou des taux de changement différents et comment les reliez-vous? Comment gardez-vous votre conception flexible et actuelle?
  • Tests unitaires, refactoring, TDD -> Quel est votre processus pour trouver une solution? Ecrivez-vous des tests, utilisez-vous des objets fictifs, refactorisez, itérez?
  • Code propre, idiomes de langage -> Utilisez-vous les fonctionnalités de votre langage de programmation pour vous aider? Écrivez-vous du code compréhensible? Vos niveaux d'abstraction ont-ils un sens? Dans quelle mesure le code est-il maintenable?
  • Outils : utilisez-vous le contrôle de code source? Créer des outils? IDE?

À partir de là, vous pouvez avoir de nombreuses discussions intéressantes, impliquant des principes de conception (comme les principes SOLID), des modèles de conception, des modèles d'analyse, la modélisation de domaine, des choix technologiques, des chemins d'évolution futurs (par exemple, si j'ajoute une base de données ou une couche d'interface utilisateur riche, que faut-il changer?), compromis, exigences non fonctionnelles (performances, maintenabilité, sécurité, ...), tests d'acceptation, etc ...

Je ne commenterai pas comment vous devriez changer votre solution, juste que vous devriez vous concentrer davantage sur ces concepts.

Mais, je peux vous montrer comment j'ai (partiellement) résolu ce problème , juste à titre d'exemple (en Java). Regardez dans la Programclasse pour voir comment tout est réuni pour imprimer ce reçu:

------------------ CECI EST VOTRE COMMANDE ------------------
(001) Conception basée sur le domaine ----- 69,99 $
(001) Logiciel orienté objet en croissance ----- 49,99 $
(001) House MD Saison 1 ----- 29,99 $
(001) House MD Saison 7 ----- 34,50 $
(IMD) Logiciel orienté objet en croissance ----- $ 2,50
(BST) House MD Saison 1 ----- 3,00 $
(BST) House MD Saison 7 ----- 3,45 $
(IMD) House MD Saison 7 ----- 1,73 $
                                SOUS-TOTAL ----- 184,47 $
                                TOTAL FISCAL ----- 10,68 $
                                    TOTAL ----- 195,15 $
---------------- MERCI DE NOUS CHOISIR ----------------

Vous devriez certainement jeter un œil à ces livres :-)

Juste une mise en garde: ma solution est encore très incomplète, je me suis juste concentré sur le scénario du chemin heureux afin d'avoir une bonne base sur laquelle bâtir.

Jordão
la source
J'ai parcouru votre solution et l'ai trouvée assez intéressante. Bien que je pense que la classe Order ne devrait pas être responsable de l'impression d'un reçu. De même, la classe TaxMethod ne devrait pas être responsable du calcul de la taxe. En outre, TaxMethodPractice ne doit pas contenir une liste de TaxMethod. Au lieu de cela, une classe appelée SalesPolicy doit contenir cette liste. Une classe appelée SalesEngine doit recevoir une SalesPolicy, une Order et un TaxCalculator. SalesEngine appliquera la SalesPolicy sur les articles de la commande et calculera la taxe à l'aide du calculateur de taxes
CKing
@bot: observations intéressantes .... En ce moment, Orderimprime le reçu, mais Receiptconnaît sa propre mise en forme. En outre, TaxMethodPractice est une sorte de politique fiscale, elle contient toutes les taxes qui s'appliquent à un certain scénario. TaxMethods sont des calculateurs d'impôts. J'ai l'impression qu'il ne vous manque qu'une classe de liaison de niveau supérieur, comme votre SalesEngine proposée. C'est une idée intéressante.
Jordão
Je pense juste que chaque classe doit avoir une seule responsabilité bien définie et que les classes qui représentent des objets du monde réel devraient se comporter d'une manière conforme au monde réel. Pour cette question, une TaxMethod peut être divisée en deux classes. Un TaxCriteria et un TaxCalculator. De même, une commande ne doit pas imprimer de reçu. Un ReceiptGenerator doit recevoir un reçu pour générer un reçu.
CKing
@bot: Je suis tout à fait d'accord! Les bons designs sont SOLIDES ! Un TaxMethod est un calculateur de taxes et un TaxEligibleCheck est un critère fiscal. Ce sont des entités distinctes. En ce qui concerne le reçu, oui, le fractionnement de la partie génératrice améliorerait encore la conception.
Jordão
1
Cette idée vient du modèle de spécification , jetez un œil!
Jordão
12

Sauf le fait que vous utilisez une classe appelée product, vous n'avez pas démontré que vous savez ce qu'est l'héritage, vous n'avez pas créé d'héritage à classes multiples de Product, pas de polymorphisme. Le problème aurait pu être résolu en utilisant plusieurs concepts de POO (même juste pour montrer que vous les connaissez). C'est un problème d'entretien, vous voulez donc montrer ce que vous en savez.

Je ne me transformerais cependant pas en dépression maintenant. Le fait que vous ne les ayez pas montrés ici ne signifie pas que vous ne les connaissez pas déjà ou que vous ne pouvez pas les apprendre.

Vous avez juste besoin d'un peu plus d'expérience avec la POO ou les entretiens.

Bonne chance!

Andrei G
la source
en fait, c'était mon premier design, j'en ai créé un autre mais je ne peux pas vous montrer que la limite de caractères dépasse.
Sunder le
pouvez-vous le démontrer à l'aide de n'importe quel exemple.
Sunder le
@sunder: Vous pouvez simplement mettre à jour la question avec votre nouveau design.
Bjarke Freund-Hansen
10

Les gens qui ont commencé à apprendre la programmation avec la POO n'ont pas de grands problèmes pour comprendre ce que cela signifie, car c'est comme dans la vraie vie . Si vous avez des compétences avec une autre famille de programmation que OO, cela pourrait être plus difficile à comprendre.

Tout d'abord, éteignez votre écran ou quittez votre IDE préféré. Prenez un papier et un crayon et faites une liste d' entités , de relations , de personnes , de machines , de processus , de trucs , etc. tout ce qui pourrait être rencontré dans votre programme final.

Deuxièmement, essayez d'obtenir les différentes entités de base . Vous comprendrez que certains peuvent partager des propriétés ou des capacités , il faut les mettre dans des objets abstraits . Vous devriez commencer à dessiner un joli schéma de votre programme.

Ensuite, vous devez mettre des fonctionnalités (méthodes, fonctions, sous-programmes, appelez-les comme vous le souhaitez): par exemple, un objet produit ne doit pas être capable de calculer la taxe de vente . Un objet de moteur de vente devrait.

Ne vous sentez pas mal avec tous les grands mots ( interfaces , propriétés , polymorphisme , héritage , etc.) et les modèles de conception dans un premier temps, n'essayez même pas de faire du beau code ou autre ... Pensez simplement à des objets simples et interractions entre elle comme dans la vraie vie .

Après, essayez de lire une littérature sérieuse et concise à ce sujet. Je pense que Wikipedia et Wikibooks sont un très bon moyen de commencer et de lire ensuite des informations sur GoF, les modèles de conception et UML .

smonff
la source
3
+1 pour "Tout d'abord, éteignez votre écran". Je pense que le pouvoir de penser trop souvent confondu avec le pouvoir de l'informatique.
kontur le
1
+1 pour avoir adopté l'approche la plus simple d'utilisation du crayon et du papier. Plusieurs fois, les gens sont confus lorsqu'ils sont assis devant l'IDE :)
Neeraj Gulia
Certains scientifiques ont dit que notre cerveau est inattentif lorsqu'il regarde un écran. Quand j'étudie la conception d'architecture logicielle, notre professeur nous fait travailler sur papier. Il ne se soucie pas des puissants logiciels UML. Ce qui est important, c'est d'abord de comprendre les choses.
smonff
4

Premièrement, ne mélangez pas la Productclasse avec la classe Receipt ( ShoppingCart), le quantitydevrait faire partie de ReceipItem( ShoppingCartItem), ainsi que de Tax& Cost. Le TotalTax& TotalCostdevrait faire partie de ShoppingCart.

Ma Productclasse a seulement Name& Price& quelques propriétés en lecture seule comme 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); } }
}

Votre partie de calcul de taxe est couplée avec Product. Un produit ne définit pas les politiques fiscales, c'est des classes fiscales. Selon la description du problème, il existe deux types de taxes de vente: Basicet les Dutytaxes. Vous pouvez utiliser Template Method Design Patternpour y parvenir:

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

Et enfin une classe pour appliquer les taxes:

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

    }
}

Vous pouvez les essayer sur MyFiddle .

Daniel B
la source
2

Les principes SOLID constituent un très bon point de départ pour les règles de conception .

Par exemple, le principe Open Closed stipule que si vous souhaitez ajouter de nouvelles fonctionnalités, vous n'avez pas besoin d'ajouter du code à une classe existante, mais plutôt d'ajouter une nouvelle classe.

Pour votre exemple d'application, cela signifierait que l'ajout d'une nouvelle taxe de vente nécessiterait l'ajout d'une nouvelle classe. Il en va de même pour différents produits qui font exception à la règle.

La règle d'arrondi va évidemment dans une classe distincte - le principe de responsabilité unique stipule que chaque classe a une seule responsabilité.

Je pense qu'essayer d'écrire le code vous-même apporterait beaucoup plus d'avantages que simplement écrire une bonne solution et la coller ici.

Un algorithme simple pour écrire le programme parfaitement conçu serait:

  1. Écrivez du code qui résout le problème
  2. Vérifiez si le code est conforme aux principes SOLID
  3. S'il y a des violations de règle que goto 1.
devdimi
la source
2

Une mise en œuvre parfaite de la POO est complètement discutable. D'après ce que je vois dans votre question, vous pouvez modulariser le code en fonction du rôle qu'ils jouent pour calculer le prix final comme Product, Tax, ProductDB, etc.

  1. Productpourrait être une classe abstraite et les types dérivés comme Books, Food pourraient en être hérités. L'applicabilité fiscale peut être décidée par les types dérivés. Le produit indiquerait si la taxe est applicable ou non en fonction de la classe dérivée.

  2. TaxCriteria peut être une énumération et cela peut être spécifié lors de l'achat (importé, applicabilité de la taxe de vente).

  3. Taxclass calculera la taxe en fonction de TaxCriteria.

  4. Avoir un ShoppingCartItemcomme suggéré par XXBBCC peut encapsuler les instances de produit et de taxe et c'est un excellent moyen de séparer les détails du produit avec la quantité, le prix total avec la taxe, etc.

Bonne chance.

Karthik
la source
1

D'un point de vue strictement OOA / D, un problème majeur que je vois est que la plupart de vos attributs de classe ont le nom redondant de la classe dans le nom d'attribut. Par exemple , le prix du produit , le type de produit . Dans ce cas, partout où vous utilisez cette classe, vous aurez un code trop verbeux et quelque peu déroutant, par exemple product.productName. Supprimez les préfixes / suffixes de nom de classe redondants de vos attributs.

De plus, je n'ai vu aucune classe concernée par l'achat et la création d'un reçu comme cela a été demandé dans la question.

Peter Cetinski
la source
1

Voici un excellent exemple de modèle OO pour les produits, la taxe, etc. Remarquez l'utilisation d'interfaces, qui est essentielle dans la conception OO.

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

Chris Gessler
la source
3
Je préfère faire du produit une classe (abstraite) plutôt que d'en faire une interface. Je ne ferais pas non plus de chaque produit une classe distincte. Au plus je créerais une classe par catégorie.
CodesInChaos
@CodeInChaos - La plupart du temps, vous avez besoin des deux, mais si vous essayez de décrocher un emploi en tant qu'architecte, je choisirais d'implémenter des interfaces sur une classe abstraite.
Chris Gessler
1
Les interfaces de cet exemple n'ont aucun sens. Ils ne conduisent qu'à une duplication de code dans chaque classe les implémentant. Chaque classe l'implémente de la même manière.
Piotr Perak
0

Attaque du problème de coût avec taxe en utilisant un modèle de visiteur.

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
la source
Bienvenue dans stackoverflow. Veuillez vous assurer d'expliquer votre réponse en réponse à la question. Le PO ne cherche pas seulement une solution, mais pourquoi une solution est meilleure / pire.
Simon.SA