Cómo crear la aplicación OOP perfecta [cerrado]

98

Recientemente estuve intentando para una empresa 'x'. Me enviaron una serie de preguntas y me dijeron que resolviera solo una.

El problema es así:

El impuesto básico sobre las ventas se aplica a una tasa del 10% sobre todos los bienes, excepto libros, alimentos y productos médicos que están exentos.
Los derechos de importación son un impuesto sobre las ventas adicional que se aplica a todos los productos importados a una tasa del 5%, sin exenciones.

Cuando compro artículos, recibo un recibo que enumera el nombre de todos los artículos y su precio (impuestos incluidos), terminando con el costo total de los artículos y las cantidades totales de impuestos sobre las ventas pagados.
Las reglas de redondeo para el impuesto a las ventas son que para una tasa impositiva de n%, un precio de venta libre de p contiene (np / 100 redondeado al 0.05 más cercano) la cantidad de impuesto a las ventas.

"Me dijeron que están interesados ​​en el aspecto de diseño de su solución y les gustaría evaluar mis habilidades de programación orientada a objetos ".

Esto es lo que dijeron con sus propias palabras

  • Para la solución, queremos que use Java, Ruby o C #.
  • Estamos interesados ​​en el ASPECTO DE DISEÑO de su solución y nos gustaría evaluar sus habilidades de programación orientada a objetos .
  • Puede utilizar bibliotecas o herramientas externas con fines de creación o prueba. Específicamente, puede usar bibliotecas de pruebas unitarias o herramientas de compilación disponibles para su idioma elegido (por ejemplo, JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake, etc.)
  • Opcionalmente, también puede incluir una breve explicación de su diseño y suposiciones junto con su código.
  • Tenga en cuenta que NO esperamos una aplicación basada en web o una interfaz de usuario completa. Más bien, estamos esperando una aplicación simple basada en consola e interesada en su código fuente.

Así que proporcioné el siguiente código: puede copiar, pegar el código y ejecutarlo en 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
     }
 }

puede descomprimir la entrada y ejecutarla para diferentes entradas.

Di la solución, pero me rechazaron.

"Dijeron que no pueden considerarme para nuestras posiciones abiertas actuales porque la solución del código no es satisfactoria".

Por favor, guíame lo que falta aquí. ¿Esta solución no es una buena solución OOAD?
¿Cómo puedo mejorar mis habilidades OOAD?
Mis personas mayores también dicen que la aplicación OOAD perfecta tampoco funcionará de manera práctica.

Gracias

romper
fuente
2
¿Quizás esperaban que distinguiera entre los tipos de productos utilizando una jerarquía de herencia, en lugar de una enumeración? (Aunque creo que el enfoque sería bastante enrevesado para el escenario dado.)
Douglas
Supongo que rechazaron su solución principalmente porque no definió ninguna interfaz.
Chris Gessler
28
Como regla general, si alguien le pide en una situación de entrevista que demuestre sus habilidades de programación orientada a objetos, debe intentar evitar el uso de una declaración de cambio; en su lugar, use una jerarquía de herencia.
Joe
4
Debe publicarse en la revisión del código.
Derek
También había publicado allí, pero no pude obtener una buena solución allí. Pero todos pueden ver mi nueva solución que creé después de la ayuda de otros codeproject.com/Questions/332077/ ... aquí también puede encontrar mi nuevo código.
salida

Respuestas:

246

En primer lugar, Dios mío, no haga cálculos financieros al doble . Haga cálculos financieros en decimal ; Esto es para lo que sirve. Utilice el doble para resolver problemas de física , no problemas financieros .

El principal defecto de diseño de su programa es que la política está en el lugar equivocado . ¿Quién se encarga de calcular los impuestos? Has puesto el producto a cargo de calcular los impuestos, pero cuando compras una manzana o un libro o una lavadora, lo que estás a punto de comprar no es responsable de decirte cuánto impuesto vas a pagar. eso. La política del gobierno es responsable de decirle eso. Su diseño viola masivamente el principio básico de diseño de OO de que los objetos deben ser responsables de sus propias preocupaciones y no de las de nadie más. La preocupación de una lavadora es lavar la ropa, no cobrar los derechos de importación adecuados. Si cambian las leyes fiscales, no quiere cambiarel objeto de la lavadora , desea cambiar el objeto de política .

Entonces, ¿cómo abordar este tipo de problemas en el futuro?

Habría comenzado resaltando cada sustantivo importante en la descripción del problema:

El impuesto básico sobre las ventas se aplica a una tasa del 10% sobre todos los bienes , excepto libros , alimentos y productos médicos que están exentos. Los derechos de importación son un impuesto sobre las ventas adicional que se aplica a todos los productos importados a una tasa del 5%, sin exenciones . Cuando compro artículos , recibo un recibo que enumera el nombre de todos los artículos y su precio ( impuestos incluidos ), terminando con el costo total.de los artículos y los montos totales de impuestos sobre las ventas pagados. Las reglas de redondeo para el impuesto sobre las ventas son que para una tasa impositiva de n%, un precio de venta libre de p contiene (np / 100 redondeado al 0.05 más cercano) la cantidad de impuesto sobre las ventas .

Ahora bien, ¿cuáles son las relaciones entre todos esos sustantivos?

  • El impuesto básico sobre las ventas es un tipo de impuesto sobre las ventas.
  • Los derechos de importación son un tipo de impuesto a las ventas
  • Un impuesto sobre las ventas tiene una tasa que es decimal
  • Los libros son una especie de artículo
  • La comida es una especie de artículo
  • Los productos médicos son una especie de artículo
  • Los artículos pueden ser bienes importados
  • Un artículo tiene un nombre que es una cadena
  • Un artículo tiene un precio de estante que es un decimal. (Nota: ¿un artículo realmente tiene un precio? Dos lavadoras idénticas pueden estar a la venta por diferentes precios en diferentes tiendas o en la misma tienda en diferentes momentos. Un mejor diseño podría ser decir que una Política de precios relaciona un artículo con su precio.)
  • Una Política de exención de impuestos sobre las ventas describe las condiciones bajo las cuales un impuesto sobre las ventas no es aplicable a un artículo.
  • Un recibo tiene una lista de artículos, sus precios y sus impuestos.
  • Un recibo tiene un total
  • Un recibo tiene un impuesto total

... y así. Una vez que haya resuelto todas las relaciones entre todos los sustantivos, puede comenzar a diseñar una jerarquía de clases. Hay un elemento de clase base abstracto. Libro hereda de él. Hay una clase abstracta SalesTax; BasicSalesTax hereda de él. Y así.

Eric Lippert
fuente
12
¿Necesita más de lo que se acaba de proporcionar? Parece que necesita aprender más sobre cómo se implementa la herencia y qué es el polimorfismo.
Induster
27
@sunder: Esta respuesta es más que suficiente. Ahora es su responsabilidad desarrollar sus habilidades, quizás usando esto como un primer ejemplo. Tenga en cuenta que su ejemplo es la definición de un ejemplo de la vida real. Falló una entrevista de la vida real porque este código de la vida real necesitaba un diseño de la vida real que usted no proporcionó.
Greg D
9
@Narayan: doublees ideal para situaciones en las que estar dentro del 0.00000001% de la respuesta correcta es más que suficiente. Si quieres saber qué tan rápido cae un ladrillo después de medio segundo, haz los cálculos en dobles. Cuando haces aritemética financiera en dobles, terminas con respuestas como el precio después de impuestos es 43.79999999999999 dólares y eso parece una tontería a pesar de que está muy cerca de la respuesta correcta.
Eric Lippert
31
+1 Ha resaltado un ejercicio notable, que consiste en examinar cada sustantivo en el problema planteado y luego enumerar sus relaciones entre sí. Gran idea.
Chris Tonkinson
3
@ Jordão: En decimal, sumar 0,10 diez veces da 1,00. Pero sumar 1.0 / 333.0 trescientas treinta y tres veces no necesariamente da uno en decimal o doble. En decimal, las fracciones que tienen potencias de diez en el denominador se representan exactamente; en dobles, son fracciones con potencias de dos. Todo lo demás se representa aproximadamente.
Eric Lippert
38

Si la empresa dice algo sobre bibliotecas como NUnit, JUnit o Test :: Unit, es más que probable que TDD sea realmente importante para ellos. En su ejemplo de código no hay pruebas en absoluto.

Intentaría demostrar un conocimiento práctico de:

  • Pruebas unitarias (por ejemplo, NUnit)
  • Burlarse (por ejemplo, RhinoMocks)
  • Persistencia (por ejemplo, NHibernate)
  • Contenedores de IoC (por ejemplo, NSpring)
  • patrones de diseño
  • Principio SOLIDO

Me gustaría recomendar www.dimecasts.net como una fuente impresionante de screencasts gratuitos y de buena calidad que cubren todos los temas mencionados anteriormente.

Radek
fuente
19

Esto es muy subjetivo, pero aquí hay algunos puntos que haría sobre su código:

  • En mi opinión, mezcló Producty ShoppingCartItem. Productdebe tener el nombre del producto, el estado fiscal, etc. pero no la cantidad. La cantidad no es una propiedad de un producto, será diferente para cada cliente de la empresa que compre ese producto en particular.

  • ShoppingCartItemdebe tener una Producty la cantidad. De esa forma, el cliente puede comprar libremente más o menos del mismo producto. Con su configuración actual eso no es posible.

  • El cálculo del impuesto final tampoco debería ser parte del Product- debería ser parte de algo así, ShoppingCartya que el cálculo final del impuesto puede implicar conocer todos los productos en el carrito.

xxbbcc
fuente
El único problema que tengo con esta respuesta es que describe cómo construir un mejor sistema de pago de productos (que es válido), pero no explica realmente las metodologías de programación orientada a objetos. Esto se puede implementar en cualquier idioma. Sin mostrar algún tipo de interfaces, herencia, polimorfismo, etc., aún fallaría la prueba.
Tiempo de espera del
En referencia al último punto: el mejor lugar de la OMI para el cálculo de impuestos es la clase TaxCalculator separada debido al principio de responsabilidad única.
Radek
gracias por la respuesta, pero qué práctica es. ¿Todas las empresas trabajan en modelos OOPS tan extensos y puros?
sunder
@shyamsunder No hay nada realmente puro en mi respuesta. No usa interfaces / herencia que son aspectos importantes de OOD, pero muestra el principio más importante, en mi opinión, y es poner las responsabilidades donde pertenecen. Como señalaron otras respuestas, el principal problema con su diseño es que mezcló responsabilidades entre varios actores y eso generará problemas al agregar funciones. La mayor parte del software grande solo puede crecer si sigue estos principios.
xxbbcc
Buena respuesta, pero también estoy de acuerdo en que el cálculo de impuestos debería ser un objeto aparte.
14

Primero que nada, esta es una muy buena pregunta de entrevista. Es un buen indicador de muchas habilidades.

Hay muchas cosas que debe comprender para proporcionar una buena respuesta (no existe una respuesta perfecta), tanto de alto nivel como de bajo nivel. Aquí hay un par:

  • Modelado de dominio -> ¿cómo se crea un buen modelo de la solución? ¿Qué objetos creas? ¿Cómo resolverán los requisitos? Buscar los sustantivos es un buen comienzo, pero ¿cómo decides si tu elección de entidades es buena? ¿Qué otras entidades necesitas? ¿Qué conocimiento de dominio necesitas para resolverlo?
  • Separación de preocupaciones, acoplamiento débil, alta cohesión -> ¿Cómo separa las partes del diseño que tienen diferentes preocupaciones o tasas de cambio y cómo las relaciona? ¿Cómo mantiene su diseño flexible y actualizado?
  • Pruebas unitarias, refactorización, TDD -> ¿Cuál es su proceso para encontrar una solución? ¿Escribe pruebas, usa objetos simulados, refactoriza, itera?
  • Código limpio, modismos del lenguaje -> ¿Utiliza las características de su lenguaje de programación para ayudarlo? ¿Escribe código comprensible? ¿Tienen sentido sus niveles de abstracción? ¿Qué tan fácil de mantener es el código?
  • Herramientas : ¿Usas control de fuente? ¿Construir herramientas? IDE?

A partir de ahí, puede tener muchas discusiones interesantes, que involucran principios de diseño (como los principios SOLID), patrones de diseño, patrones de análisis, modelado de dominio, opciones de tecnología, rutas de evolución futuras (por ejemplo, ¿qué pasa si agrego una base de datos o una capa de interfaz de usuario rica, ¿Qué necesita cambiar?), compensaciones, requisitos no funcionales (rendimiento, mantenibilidad, seguridad, ...), pruebas de aceptación, etc.

No comentaré cómo debería cambiar su solución, solo que debería centrarse más en estos conceptos.

Pero, puedo mostrarles cómo resolví (parcialmente) este problema , solo como un ejemplo (en Java). Mire en la Programclase para ver cómo se junta todo para imprimir este recibo:

------------------ ESTE ES SU PEDIDO ------------------
(001) Diseño impulsado por dominio ----- $ 69.99
(001) Software orientado a objetos en crecimiento ----- $ 49.99
(001) House MD Temporada 1 ----- $ 29.99
(001) House MD Temporada 7 ----- $ 34.50
(IMD) Software orientado a objetos en crecimiento ----- $ 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
                                TOTAL DE IMPUESTOS ----- $ 10.68
                                    TOTAL ----- $ 195.15
---------------- GRACIAS POR ELEGIRNOS ----------------

Definitivamente deberías echar un vistazo a esos libros :-)

Solo como advertencia: mi solución aún está muy incompleta, solo me concentré en el escenario del camino feliz para tener una buena base sobre la cual construir.

Jordão
fuente
Revisé tu solución y la encontré bastante interesante. Aunque creo que la clase Order no debería ser responsable de imprimir un recibo. De manera similar, la clase TaxMethod no debería ser responsable del cálculo de impuestos. Además, TaxMethodPractice no debe contener una lista de TaxMethod. En cambio, una clase llamada SalesPolicy debería contener esta lista. Una clase llamada SalesEngine debe recibir SalesPolicy, Order y TaxCalculator. SalesEngine aplicará SalesPolicy en los artículos en el Pedido y calculará el Impuesto usando el TaxCalculator
CKing
@bot: observaciones interesantes .... En este momento, Orderimprime el recibo, pero Receiptconoce su propio formato. Además, TaxMethodPractice es una especie de política fiscal, contiene todos los impuestos que se aplican a un determinado escenario. TaxMethods son calculadoras de impuestos. Siento que solo le falta una clase de enlace de nivel superior , como su SalesEngine propuesto. Es una idea interesante.
Jordão
Simplemente siento que cada clase debe tener una sola responsabilidad bien definida y las clases que representan objetos del mundo real deben comportarse de una manera que esté en línea con el mundo real. Por lo tanto, un método de impuestos se puede dividir en dos clases. TaxCriteria y TaxCalculator. Del mismo modo, un pedido no debe imprimir un recibo. Un ReceiptGenerator debe recibir un recibo para generar un recibo.
CKing
@bot: ¡Estoy completamente de acuerdo! ¡Los buenos diseños son SÓLIDOS ! Un TaxMethod es una calculadora de impuestos y un TaxEligibilityCheck es un criterio de impuestos. Son entidades separadas. En cuanto al recibo, sí, dividir la parte generadora mejoraría aún más el diseño.
Jordão
1
Esa idea proviene del patrón de especificación , ¡échale un vistazo!
Jordão
12

Excepto por el hecho de que está utilizando una clase llamada producto, no ha demostrado que sepa qué es la herencia, no ha creado herencias múltiples clasificadas de Producto, sin polimorfismo. El problema podría haberse resuelto utilizando múltiples conceptos de programación orientada a objetos (incluso solo para demostrar que los conoce). Este es un problema de la entrevista, por lo que querrá demostrar cuánto sabe.

Sin embargo, no me convertiría en depresión en este momento. El hecho de que no los haya demostrado aquí no significa que no los conozca o que no pueda aprenderlos.

Solo necesita un poco más de experiencia con OOP o entrevistas.

¡Buena suerte!

Andrei G
fuente
en realidad este fue mi primer diseño, creé otro pero no puedo mostrarte ya que el límite de caracteres está excediendo.
sunder
¿Puedes demostrarlo con la ayuda de cualquier ejemplo?
soltar
@sunder: Puede actualizar la pregunta con su nuevo diseño.
Bjarke Freund-Hansen
10

Las personas que han comenzado a aprender a programar con POO no tienen grandes problemas para entender lo que significa, porque es como en la vida real . Si tiene habilidades con otras familias de programación además de OO, podría ser más difícil de entender.

En primer lugar, apague la pantalla o salga de su IDE favorito. Tome un papel y un lápiz y haga una lista de entidades , relaciones , personas , máquinas , procesos , cosas , etc., todo lo que pueda encontrar en su programa final.

En segundo lugar, intente obtener las diferentes entidades básicas . Comprenderás que algunos pueden compartir propiedades o habilidades , tienes que ponerlo en objetos abstractos . Debería empezar a dibujar un buen esquema de su programa.

A continuación, debe poner funciones (métodos, funciones, subrutinas, llámelo como desee): por ejemplo, un objeto de producto no debería poder calcular el impuesto sobre las ventas . Un objeto de motor de ventas debería.

No se sienta en problemas con todas las palabras importantes ( interfaces , propiedades , polimorfismo , herencia , etc.) y patrones de diseño por primera vez, ni siquiera intente crear un código hermoso o lo que sea ... Solo piense en objetos simples y interacciones entre ellos como en la vida real .

Después, intente leer literatura seria y concisa sobre esto. Creo que Wikipedia y Wikilibros son una muy buena manera de comenzar y luego leer cosas sobre GoF y Design Patterns y UML .

smonff
fuente
3
+1 para "En primer lugar, apague la pantalla". Creo que el poder de pensar se confunde con demasiada frecuencia con el poder de la informática.
kontur
1
+1 por adoptar el enfoque más simple de usar lápiz y papel. Muchas veces la gente se confunde cuando se sienta frente al IDE :)
Neeraj Gulia
Algunos científicos dijeron que nuestro cerebro no presta atención cuando mira una pantalla. Cuando estudio diseño de arquitectura de software, nuestro profesor nos hace trabajar en papel. No le importan los potentes softwares UML. Lo importante es comprender las cosas primero.
smonff
4

Primero, no mezcle la Productclase con la clase Receipt ( ShoppingCart), quantitydebe ser parte de ReceipItem( ShoppingCartItem), así como Tax& Cost. El TotalTax& TotalCostdebe formar parte de ShoppingCart.

Mi Productclase, solo tiene Name& Price& algunas propiedades de solo lectura 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); } }
}

Su parte de cálculo de impuestos está emparejada con Product. Un producto no define políticas fiscales, son clases de impuestos. Según la descripción del problema, existen dos tipos de impuestos sobre las ventas: Basice Dutyimpuestos. Puedes usar Template Method Design Patternpara lograrlo:

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

Y finalmente una clase para aplicar impuestos:

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

    }
}

Puedes probarlos en MyFiddle .

Daniel B
fuente
2

Un muy buen punto de partida sobre las reglas de diseño son los principios SOLID .

Por ejemplo, el principio Abierto Cerrado establece que si desea agregar una nueva funcionalidad, no tiene que agregar código a la clase existente, sino que debe agregar una nueva clase.

Para su aplicación de muestra, esto significaría que agregar un nuevo impuesto sobre las ventas requeriría agregar una nueva clase. Lo mismo ocurre con los diferentes productos que son excepciones a la regla.

La regla de redondeo, obviamente, va en una clase separada: el principio de responsabilidad única establece que cada clase tiene una sola responsabilidad.

Creo que intentar escribir el código usted mismo traerá muchos más beneficios que simplemente escribir una buena solución y pegarla aquí.

Un algoritmo simple para escribir el programa diseñado perfecto sería:

  1. Escribe un código que resuelva el problema.
  2. Verifique si el código cumple con los principios SOLID
  3. Si hay violaciones de las reglas, vaya a 1.
devdimi
fuente
2

Una implementación OOP perfecta es completamente discutible. Por lo que veo en su pregunta, podría modularizar el código en función de la función que desempeñan para calcular el precio final, como Producto, Impuesto, ProductDB, etc.

  1. Productpodría ser una clase abstracta y los tipos derivados como Books, Food podrían heredarse de ella. La aplicabilidad fiscal puede decidirse por los tipos derivados. El producto indicaría si el impuesto es aplicable o no según la clase derivada.

  2. TaxCriteria puede ser una enumeración y esto se puede especificar durante la compra (importado, aplicabilidad del impuesto sobre las ventas).

  3. Taxla clase calculará el impuesto en base a TaxCriteria.

  4. Tener un ShoppingCartItemtal como lo sugiere XXBBCC puede encapsular instancias de Producto e Impuestos y es una excelente manera de segregar los detalles del producto con cantidad, precio total con impuestos, etc.

Buena suerte.

Karthik
fuente
1

Desde una perspectiva estrictamente OOA / D, un problema importante que veo es que la mayoría de los atributos de su clase tienen el nombre redundante de la clase en el nombre del atributo. por ejemplo , precio del producto , tipo de producto . En este caso, dondequiera que use esta clase, tendrá un código demasiado detallado y algo confuso, por ejemplo, product.productName. Elimine los prefijos / sufijos de nombre de clase redundantes de sus atributos.

Además, no vi ninguna clase relacionada con la compra y la creación de un recibo como se preguntó en la pregunta.

Peter Cetinski
fuente
1

Este es un gran ejemplo de un patrón OO para Productos, Impuestos, etc. Observe el uso de Interfaces, que es esencial en el diseño OO.

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

Chris Gessler
fuente
3
Preferiría hacer del producto una clase (abstracta) en lugar de convertirlo en una interfaz. Tampoco haría de cada producto una clase separada. Como mucho, crearía una clase por categoría.
CodesInChaos
@CodeInChaos: la mayoría de las veces necesita ambos, pero si está tratando de conseguir un trabajo como arquitecto, elegiría implementar interfaces en una clase abstracta.
Chris Gessler
1
Las interfaces en este ejemplo no tienen ningún sentido. Solo conducen a la duplicación de código en cada clase que los implementa. Cada clase lo implementa de la misma manera.
Piotr Perak
0

Atacó el problema del costo con impuestos usando un patrón 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);
    }
LucidCoder
fuente
Bienvenido a stackoverflow. Asegúrese de explicar su respuesta en respuesta a la pregunta. El OP no solo busca una solución, sino por qué una solución es mejor / peor.
Simon.SA