Como criar o aplicativo OOP perfeito [fechado]


98

Recentemente, estava tentando uma empresa 'x'. Eles me enviaram algumas perguntas e me disseram para resolver apenas uma.

O problema é assim -

O imposto básico sobre vendas é aplicável a uma alíquota de 10% sobre todas as mercadorias, exceto livros, alimentos e produtos médicos que estão isentos.
O direito de importação é um imposto adicional sobre vendas aplicável a todas as mercadorias importadas, à alíquota de 5%, sem isenções.

Quando compro itens, recebo um recibo que lista o nome de todos os itens e seu preço (incluindo impostos), finalizando com o custo total dos itens e os valores totais de impostos sobre vendas pagos.
As regras de arredondamento para imposto sobre vendas são que, para uma taxa de imposto de n%, um preço de prateleira de p contém (np / 100 arredondado para o 0,05 mais próximo) valor de imposto sobre vendas.

“Eles me disseram que estão interessados ​​no aspecto de design da sua solução e gostariam de avaliar minhas habilidades de programação orientada a objetos .”

Isso é o que eles disseram em suas próprias palavras

  • Para a solução, gostaríamos que você usasse Java, Ruby ou C #.
  • Estamos interessados ​​no ASPECTO DE DESIGN da sua solução e gostaríamos de avaliar suas habilidades de programação orientada a objetos .
  • Você pode usar bibliotecas ou ferramentas externas para fins de construção ou teste. Especificamente, você pode usar bibliotecas de teste de unidade ou ferramentas de construção disponíveis para o idioma escolhido (por exemplo, JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake etc.)
  • Opcionalmente, você também pode incluir uma breve explicação de seu design e suposições junto com seu código.
  • Observe que NÃO esperamos um aplicativo baseado na web ou uma interface do usuário abrangente. Em vez disso, esperamos um aplicativo simples baseado em console e interessado em seu código-fonte.

Então, eu forneci o código abaixo - você pode simplesmente copiar, colar o código e executar no 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
     }
 }

você pode descompactar a entrada e executar para entradas diferentes.

Eu forneci a solução, mas fui rejeitado.

"Eles disseram que não podem me considerar para nossas posições em aberto porque a solução de código não é satisfatória."

Por favor me guie o que está faltando aqui. Esta solução não é uma boa solução OOAD.
Como posso melhorar minhas habilidades OOAD.
Meus idosos também dizem que o aplicativo OOAD perfeito também não funcionará na prática.

obrigado


2
Talvez eles esperassem que você fizesse a distinção entre os tipos de produto usando uma hierarquia de herança, em vez de uma enumeração? (Embora eu ache que essa abordagem seria um tanto complicada para o cenário dado.)
Douglas

Meu palpite é que eles rejeitaram sua solução completamente porque você não definiu nenhuma interface.
Chris Gessler

28
Como regra geral, se alguém pedir a você em uma situação de entrevista para demonstrar habilidades OOP, você deve tentar evitar o uso de uma instrução switch - em vez disso, use uma hierarquia de herança.
Joe

4
Deve ser postado na revisão do código.
Derek

Eu tinha postado lá também, mas não consegui uma boa solução. Mas todos podem ver minha nova solução que criei após a ajuda de outros codeproject.com/Questions/332077/… aqui você pode encontrar meu novo código também.
encerrado em

Respostas:


246

Em primeiro lugar, bom Deus, não fazem cálculos financeiros em dobro . Faça cálculos financeiros em decimal ; É pra isso que isto serve. Use o dobro para resolver problemas de física , não problemas financeiros .

A principal falha de design em seu programa é que a política está no lugar errado . Quem é o responsável pelo cálculo dos impostos? Você colocou o produto como responsável pelo cálculo dos impostos, mas quando você compra uma maçã ou um livro ou uma máquina de lavar, a coisa que você está prestes a comprar não é responsável por dizer quanto de imposto você vai pagar isto. A política governamental é responsável por lhe dizer isso. Seu projeto viola maciçamente o princípio básico de projeto OO de que os objetos devem ser responsáveis ​​por seus próprios interesses , e não de outra pessoa. A preocupação da máquina de lavar é lavar a roupa, não cobrar o imposto de importação certo. Se as leis fiscais mudarem, você não vai querer mudarobjeto da máquina de lavar , você deseja alterar o objeto de política .

Então, como abordar esse tipo de problema no futuro?

Eu teria começado destacando todos os substantivos importantes na descrição do problema:

O imposto básico sobre vendas é aplicável a uma alíquota de 10% sobre todas as mercadorias , exceto livros , alimentos e produtos médicos que estão isentos. O direito de importação é um imposto adicional sobre vendas aplicável a todas as mercadorias importadas, à alíquota de 5%, sem isenções . Quando eu compro itens , recebo um recibo que lista o nome de todos os itens e seus preços (incluindo impostos ), terminando com o custo totaldos itens e os valores totais dos impostos sobre vendas pagos. As regras de arredondamento para imposto sobre vendas são que, para uma taxa de imposto de n%, um preço de prateleira de p contém (np / 100 arredondado para o 0,05 mais próximo) valor de imposto sobre vendas .

Agora, quais são as relações entre todos esses substantivos?

  • O imposto básico sobre vendas é um tipo de imposto sobre vendas
  • O imposto de importação é uma espécie de imposto sobre vendas
  • Um imposto sobre vendas tem uma taxa que é decimal
  • Os livros são um tipo de item
  • A comida é um tipo de item
  • Produtos médicos são um tipo de item
  • Os itens podem ser bens importados
  • Um item tem um nome que é uma string
  • Um item tem um preço de prateleira que é decimal. (Observação: um item realmente tem um preço? Duas máquinas de lavar idênticas podem estar à venda por preços diferentes em lojas diferentes ou na mesma loja em momentos diferentes. Um design melhor poderia ser dizer que uma Política de Preços relaciona um Item a Seu preço.)
  • Uma Política de Isenção de Imposto sobre Vendas descreve as condições sob as quais um Imposto sobre Vendas não é aplicável a um Item.
  • Um Recibo contém uma lista de Itens, seus preços e seus impostos.
  • Um recibo tem um total
  • Um recibo tem um imposto total

... e assim por diante. Depois de ter resolvido todas as relações entre todos os substantivos, você pode começar a projetar uma hierarquia de classes. Existe um Item de classe base abstrata. O livro herda disso. Existe uma classe abstrata SalesTax; BasicSalesTax herda dele. E assim por diante.


12
você precisa de mais do que o que acabou de ser fornecido? Parece que você precisa aprender mais sobre como a herança é implementada e o que é polimorfismo.
Induster

27
@sunder: Esta resposta é mais do que suficiente. Agora é sua responsabilidade desenvolver suas habilidades, talvez usando isso como um primeiro exemplo. Observe que seu exemplo é a definição de um exemplo da vida real. Você falhou em uma entrevista na vida real porque esse código da vida real precisava de um design da vida real que você não forneceu.
Greg D

9
@Narayan: doubleé ideal para situações em que estar dentro de 0,00000001% da resposta certa é mais do que suficiente. Se você quiser descobrir a velocidade com que um tijolo está caindo após meio segundo, faça as contas em duplas. Quando você faz aritêmica financeira em duplas, obtém respostas como o preço após os impostos é de 43,79999999999999 dólares e isso parece bobo, embora esteja extremamente próximo da resposta correta.
Eric Lippert

31
+1 Você destacou um exercício notável, que é examinar cada substantivo no problema declarado e então enumerar suas relações entre si. Boa ideia.
Chris Tonkinson

3
@ Jordão: Em decimal, somar 0,10 dez vezes dá 1,00. Mas somar 1,0 / 333,0 trezentas e trinta e três vezes não resulta necessariamente em um decimal ou duplo. Em decimal, as frações que têm potências de dez no denominador são representadas exatamente; em duplas, são frações com potências de dois. Qualquer outra coisa é representada aproximadamente.
Eric Lippert

38

Se a empresa contar algo sobre bibliotecas como NUnit, JUnit ou Test :: Unit, é mais do que provável que o TDD seja realmente importante para eles. Em sua amostra de código não há testes.

Eu tentaria demonstrar conhecimento prático de:

  • Testes de unidade (por exemplo, NUnit)
  • Zombaria (por exemplo, RhinoMocks)
  • Persistência (por exemplo, NHibernate)
  • Recipientes IoC (por exemplo, NSpring)
  • Padrões de design
  • Princípio SÓLIDO

Eu gostaria de recomendar o www.dimecasts.net como uma fonte impressionante de screencasts gratuitos e de boa qualidade que cobrem todos os tópicos mencionados acima.


19

Isso é altamente subjetivo, mas aqui estão alguns pontos que eu faria sobre o seu código:

  • Na minha opinião, você misturou Producte ShoppingCartItem. Productdeve ter o nome do produto, situação fiscal, etc., mas não a quantidade. A quantidade não é uma propriedade de um produto - será diferente para cada cliente da empresa que comprar aquele produto específico.

  • ShoppingCartItemdeve ter um Producte a quantidade. Dessa forma, o cliente pode comprar livremente mais ou menos do mesmo produto. Com sua configuração atual, isso não é possível.

  • O cálculo do imposto final também não deve fazer parte do Product- deve ser parte de algo assim, ShoppingCartpois o cálculo do imposto final pode envolver o conhecimento de todos os produtos no carrinho.


O único problema que tenho com essa resposta é que ela descreve como construir um sistema de pagamento de produto melhor (o que é válido), mas não detalha realmente as metodologias de OOP. Isso pode ser implementado em qualquer idioma. Sem mostrar algum tipo de interface, herança, polimorfismo, etc. ele ainda falharia no teste.
Tempo limite

Em referência ao último ponto: o melhor lugar da IMO para o cálculo do imposto é a classe TaxCalculator separada devido ao princípio de responsabilidade única.
Radek

obrigado pela resposta, mas como é prático. todas as empresas trabalham em modelos OOPS tão extensos e puros.
encerrado em

@shyamsunder Não há nada realmente puro na minha resposta. Ele não usa interfaces / herança, que são aspectos importantes do OOD, mas mostra o princípio mais importante - na minha opinião - que é colocar as responsabilidades onde elas pertencem. Como outras respostas apontaram, o principal problema com seu design é que você confundiu responsabilidades entre vários atores e isso levará a problemas ao adicionar recursos. A maioria dos softwares grandes só pode crescer se seguir esses princípios.
xxbbcc

Boa resposta, mas também concordo que o cálculo do imposto deve ser um objeto separado.

14

Em primeiro lugar, esta é uma pergunta de entrevista muito boa. É um bom indicador de muitas habilidades.

Há muitas coisas que você precisa entender para fornecer uma boa resposta (não existe uma resposta perfeita), tanto de alto quanto de baixo nível. Aqui estão algumas:

  • Modelagem de Domínio -> como você cria um bom modelo da solução? Que objetos você cria? Como eles resolverão os requisitos? Procurar os substantivos é um bom começo, mas como você decide se sua escolha de entidades é boa? De que outras entidades você precisa? Qual domínio de conhecimento você precisa para resolvê-lo?
  • Separação de preocupações, acoplamento fraco, alta coesão -> Como você separa as partes do design que têm preocupações ou taxas de mudança diferentes e como você as relaciona? Como você mantém seu design flexível e atual?
  • Teste de unidade, refatoração, TDD -> Qual é o seu processo para encontrar uma solução? Você escreve testes, usa objetos simulados, refatorar, iterar?
  • Código limpo, expressões idiomáticas -> Você usa os recursos da sua linguagem de programação para ajudá-lo? Você escreve código compreensível? Seus níveis de abstração fazem sentido? Quão sustentável é o código?
  • Ferramentas : você usa controle de origem? Construir ferramentas? IDEs?

A partir daí, você pode ter muitas discussões interessantes, envolvendo princípios de design (como os princípios SOLID), padrões de design, padrões de análise, modelagem de domínio, opções de tecnologia, caminhos de evolução futura (por exemplo, e se eu adicionar um banco de dados ou uma camada de IU rica, o que precisa mudar?), compromissos, requisitos não funcionais (desempenho, capacidade de manutenção, segurança, ...), teste de aceitação, etc ...

Não vou comentar como você deve mudar sua solução, apenas que você deve focar mais nesses conceitos.

Mas posso mostrar como resolvi (parcialmente) esse problema , apenas como exemplo (em Java). Olhe na Programaula para ver como tudo se junta para imprimir este recibo:

------------------ ESTE É O SEU PEDIDO ------------------
(001) Design Orientado por Domínio ----- $ 69,99
(001) Software Orientado a Objetos em Crescimento ----- $ 49,99
(001) House MD Temporada 1 ----- $ 29,99
(001) House MD Temporada 7 ----- $ 34,50
(IMD) Growing Object Oriented Software ----- $ 2,50
(BST) House MD Temporada 1 ----- $ 3,00
(BST) House MD Temporada 7 ----- $ 3,45
(IMD) House MD Temporada 7 ----- $ 1,73
                                SUBTOTAL ----- $ 184,47
                                TAX TOTAL ----- $ 10,68
                                    TOTAL ----- $ 195,15
---------------- OBRIGADO POR NOS ESCOLHER ----------------

Você definitivamente deveria dar uma olhada nesses livros :-)

Apenas como uma advertência: minha solução ainda está muito incompleta, eu apenas me concentrei no cenário do caminho feliz para ter uma boa base para construir.


Analisei sua solução e achei muito interessante. Embora eu ache que a classe Order não deva ser responsável por imprimir um Recibo. Da mesma forma, a classe TaxMethod não deve ser responsável pelo cálculo do imposto. Além disso, TaxMethodPractice não deve conter uma lista de TaxMethod. Em vez disso, uma classe chamada SalesPolicy deve conter essa lista. Uma classe chamada SalesEngine deve receber uma SalesPolicy, um Order e um TaxCalculator. SalesEngine aplicará a SalesPolicy nos itens do pedido e calculará o imposto usando o TaxCalculator
CKing

@bot: observações interessantes .... Neste momento, Orderimprime o recibo, mas Receiptconhece a sua própria formatação. Além disso, TaxMethodPractice é uma espécie de política tributária, detém todos os impostos que se aplicam a um determinado cenário. TaxMethods são calculadoras de impostos. Eu sinto que você está perdendo apenas uma classe de ligação de nível superior , como seu SalesEngine proposto. É uma ideia interessante.
Jordão

Eu simplesmente sinto que cada classe deve ter uma única responsabilidade bem definida e as classes que representam objetos do mundo real devem se comportar de uma forma que esteja alinhada com o mundo real. Nesse caso, um TaxMethod pode ser dividido em duas classes. Um TaxCriteria e um TaxCalculator. Da mesma forma, um pedido não deve imprimir um recibo. Um ReceiptGenerator deve receber um Recibo para gerar um recibo.
CKing

@bot: Concordo totalmente! Bons designs são SÓLIDOS ! Um TaxMethod é uma calculadora de impostos e um TaxEligibilityCheck é um critério de impostos. Eles são entidades separadas. Quanto ao recebimento, sim, dividir a parte geradora melhoraria ainda mais o projeto.
Jordão

1
Essa ideia vem do padrão de especificação , dê uma olhada!
Jordão

12

Exceto o fato de que você está usando uma classe chamada produto, você não demonstrou que sabe o que é herança, você não criou várias classes herdadas de Produto, nenhum polimorfismo. O problema poderia ter sido resolvido usando vários conceitos OOP (mesmo apenas para mostrar que você os conhece). Este é um problema de entrevista, então você quer mostrar o quanto sabe.

No entanto, eu não entraria em depressão agora. O fato de não os ter demonstrado aqui não significa que ainda não os conheça ou não seja capaz de os aprender.

Você só precisa de um pouco mais de experiência com OOP ou entrevistas.

Boa sorte!


Na verdade, este foi meu primeiro design, criei outro, mas não posso mostrar porque o limite de caracteres está excedendo.
encerrado em

você pode demonstrá-lo com a ajuda de qualquer exemplo.
encerrado em

@sunder: Você pode simplesmente atualizar a questão com seu novo design.
Bjarke Freund-Hansen

10

Pessoas que começaram a aprender programação com OOP não têm grandes problemas para entender o que isso significa, porque é exatamente como na vida real . Se você tiver habilidades com outra família de programação que não OO, pode ser mais difícil de entender.

Em primeiro lugar, desligue a tela ou saia do seu IDE favorito. Pegue um papel e um lápis e faça uma lista de entidades , relações , pessoas , máquinas , processos , coisas , etc. tudo que poderia ser encontrado em seu programa final.

Em segundo lugar, tente obter as diferentes entidades básicas . Você vai entender que alguns podem compartilhar propriedades ou habilidades , você tem que colocá-los em objetos abstratos . Você deve começar a desenhar um bom esquema do seu programa.

Em seguida, você deve colocar funcionalidades (métodos, funções, sub-rotinas, chame como quiser): por exemplo, um objeto de produto não deve ser capaz de calcular o imposto sobre vendas . Um objeto de mecanismo de vendas deveria.

Não sinta problemas com todas as palavras grandes ( interfaces , propriedades , polimorfismo , herança , etc.) e padrões de design em uma primeira vez, nem mesmo tente fazer um código bonito ou o que quer que seja ... Basta pensar em objetos simples e interações entre ele como na vida real .

Depois, tente ler alguma literatura séria e concisa sobre isso. Acho que a Wikipedia e o Wikilivros são uma ótima maneira de começar e depois apenas ler coisas sobre GoF e Design Patterns e UML .


3
+1 para "Em primeiro lugar, desligue a tela". Acho que o poder de pensar muitas vezes é confundido com o poder da computação.
kontur

1
1 para usar a abordagem mais simples de usar lápis e papel. Muitas vezes as pessoas ficam confusas quando sentam na frente do IDE :)
Neeraj Gulia

Alguns cientistas disseram que nosso cérebro fica desatento quando olha para uma tela. Quando estudo design de arquitetura de software, nosso professor nos faz trabalhar no papel. Ele não se importa com softwares UML poderosos. O importante é entender as coisas primeiro.
smonff

4

Primeiro, não misture Productclasse com classe Receipt ( ShoppingCart), o quantitydeve ser parte de ReceipItem( ShoppingCartItem), bem como Tax& Cost. O TotalTax& TotalCostdeve fazer parte ShoppingCart.

Minha Productclasse tem apenas Name& Price& algumas propriedades somente leitura como 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); } }
}

Sua parte de cálculo de impostos está associada Product. Um produto não define políticas fiscais, é classes de impostos. Com base na descrição do problema, existem dois tipos de impostos sobre vendas: Basice Dutyimpostos. Você pode usar Template Method Design Patternpara alcançá-lo:

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, finalmente, uma aula para aplicar impostos:

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

    }
}

Você pode experimentá-los em MyFiddle .


2

Um bom ponto de partida sobre as regras de projeto são os princípios SOLID .

Por exemplo, o princípio Aberto e Fechado afirma que se você deseja adicionar uma nova funcionalidade, você não precisa adicionar código à classe existente, mas sim adicionar uma nova classe.

Para seu aplicativo de exemplo, isso significaria que adicionar um novo imposto sobre vendas exigiria adicionar uma nova classe. O mesmo vale para diferentes produtos que são exceções à regra.

A regra de arredondamento obviamente está em classes separadas - o princípio de Responsabilidade Única afirma que cada classe tem uma única responsabilidade.

Acho que tentar escrever o código sozinho traria muito mais benefícios do que simplesmente escrever uma boa solução e colá-la aqui.

Um algoritmo simples para escrever o programa projetado perfeito seria:

  1. Escreva algum código que resolva o problema
  2. Verifique se o código está em conformidade com os princípios SOLID
  3. Se houver violações de regra, vá para 1.

2

Uma implementação OOP perfeita é completamente discutível. Pelo que vejo em sua pergunta, você pode modularizar o código com base na função que eles desempenham para calcular o preço final como Produto, Imposto, Banco de Produto e assim por diante.

  1. Productpode ser uma classe abstrata e os tipos derivados, como Livros, Comida, podem ser herdados dela. A aplicabilidade fiscal pode ser decidida pelos tipos derivados. O produto diria se o imposto é aplicável ou não com base na classe derivada.

  2. TaxCriteria pode ser um enum e pode ser especificado durante a compra (importado, aplicabilidade do imposto sobre vendas).

  3. Taxclasse calculará o imposto com base em TaxCriteria.

  4. Ter um ShoppingCartItemcomo sugerido por XXBBCC pode encapsular instâncias de produtos e impostos e é uma ótima maneira de segregar detalhes do produto com quantidade, preço total com impostos etc.

Boa sorte.


1

De uma perspectiva estritamente OOA / D, um grande problema que vejo é que a maioria dos seus atributos de classe tem o nome redundante da classe no nome do atributo. por exemplo , preço do produto , tipo do produto . Nesse caso, em todos os lugares em que você usar esta classe, você terá um código muito detalhado e um tanto confuso, por exemplo, product.productName. Remova o prefixo / sufixo do nome da classe redundante de seus atributos.

Além disso, não vi nenhuma classe relacionada com a compra e criação de um recibo, como foi perguntado na pergunta.


1

Aqui está um ótimo exemplo de um padrão OO para Produtos, Impostos, etc ... Observe o uso de Interfaces, que é essencial no design OO.

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


3
Eu prefiro fazer do produto uma classe (abstrata) a torná-lo uma interface. Eu também não faria de cada produto uma classe separada. No máximo, criaria uma classe por categoria.
CodesInChaos

@CodeInChaos - Na maioria das vezes você precisa de ambos, mas se estiver tentando conseguir um emprego como arquiteto, eu escolheria implementar Interfaces em vez de uma classe Abstract.
Chris Gessler

1
As interfaces neste exemplo não têm nenhum sentido. Eles apenas levam à duplicação de código em cada classe que os implementa. Cada classe o implementa da mesma maneira.
Piotr Perak

0

Atacou o problema de Custo com Imposto usando um padrão de Visitante.

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

Bem-vindo ao stackoverflow. Certifique-se de explicar sua resposta em resposta à pergunta. O OP não está apenas buscando uma solução, mas porque uma solução é melhor / pior.
Simon.SA
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.