如何创建完美的OOP应用程序

时间:2012-02-25 14:25:06

标签: c# oop

最近我在尝试一家公司'x'。他们给我发了一些问题,告诉我只解决一个问题。

问题是这样的 -

基本销售税适用于所有商品的10%税率,但免税的书籍,食品和医疗产品除外。
进口税是适用于所有进口商品的额外销售税,税率为5%,不含豁免。

当我购买物品时,我会收到一张收据,上面列出了所有物品的名称及其价格(含税),最后是物品的总成本,以及所支付的销售税总额。
销售税的舍入规则是对于n%的税率,p的货架价格包含(np / 100四舍五入到最接近的0.05)销售税金额。

“他们告诉我,他们对您的解决方案的设计方面感兴趣,并希望评估我的面向对象的编程技巧。”

这是他们用自己的话说的话

  • 对于解决方案,我们希望您使用Java,Ruby或C#。
  • 我们对您的解决方案的DESIGN ASPECT感兴趣,并希望评估您的面向对象的编程技巧
  • 您可以使用外部库或工具进行构建或测试。具体来说,您可以使用单元测试库或可用于您所选语言的构建工具(例如,JUnit,Ant,NUnit,NAnt,Test :: Unit,Rake等)。
  • 您也可以选择包含您的设计和假设以及代码的简要说明。
  • 请注意,我们不期望基于网络的应用程序或全面的用户界面。相反,我们期待一个简单的,基于控制台的应用程序,并对您的源代码感兴趣。

所以我提供了以下代码 - 您只需复制粘贴代码并在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
     }
 }

您可以取消注册输入并运行不同的输入。

我提供了解决方案,但我被拒绝了。

“他们说,他们无法考虑我当前的空缺职位,因为代码解决方案并不令人满意。”

请指导我这里缺少的东西。这个解决方案不是一个好的OOAD解决方案 如何提高我的OOAD技能 我的老年人也说完美的OOAD申请也不会有效。

由于

12 个答案:

答案 0 :(得分:243)

首先关闭好天堂不要进行双重财务计算。以十进制进行财务计算;这就是它的用途。使用double来解决 physics 问题,而不是财务问题。

您的计划中的主要设计缺陷是政策位于错误的地方。谁负责计算税收?您已经产品负责计算税款,但是当您购买苹果或书籍或洗衣机时,您要购买的东西不是负责告诉你要支付多少税款。 政府政策负责告诉您。您的设计大量违反了基本的OO设计原则,对象应该对自己的关注负责,而不是其他任何人。洗衣机的问题是洗衣服,而不是收取正确的进口税。如果税法变更,您不想更改洗衣机对象,您想要更改策略对象

那么,将来如何解决这些问题?

我首先要强调问题描述中的每个重要名词:

  

基本销售税适用于所有商品 10%,图书,<强健>食物,以及医疗产品免税。 进口税是一项额外的销售税,适用于所有进口商品费率为5%,没有<强>豁免即可。当我购买商品时,我会收到收据,其中列出了所有商品名称及其价格(包括),完成商品的总费用,以及支付的销售税总额。 销售税的舍入规则是税率为n%,p的货架价格包含(np / 100四舍五入到最接近的0.05)金额<强>销售税。

现在,所有这些名词之间的关系是什么?

  • 基本销售税是一种销售税
  • 进口税是一种销售税
  • 销售税的费率为十进制
  • 书籍是一种物品
  • 食物是一种物品
  • 医疗产品是一种物品
  • 商品可能是进口商品
  • 项目的名称是字符串
  • 物品的货架价格为十进制。 (注意:一件物品真的有价格吗?两台相同的洗衣机可能会在不同的商店以不同的价格出售,或者在不同的时间在同一家商店出售。更好的设计可能是说定价政策将物品与物品相关联它的价格。)
  • 销售税免税政策描述了销售税不适用于物品的条件。
  • 收据上有物品清单,价格和税款。
  • 收据总计
  • 收据总税额

......等等。一旦你掌握了所有名词之间的所有关系,你就可以开始设计一个类层次结构了。有一个抽象基类Item。本书继承自它。有一个抽象类SalesTax; BasicSalesTax继承自它。等等。

答案 1 :(得分:38)

如果公司对NUnit,JUnit或Test :: Unit这样的库有所了解,那么TDD实际上很可能是它们的重要组成部分。在您的代码示例中根本没有测试。

我会尝试展示以下方面的实用知识:

  • 单元测试(例如NUnit)
  • 模拟(例如RhinoMocks)
  • 持久性(例如NHibernate)
  • IoC容器(例如NSpring)
  • 设计模式
  • SOLID原则

我想推荐www.dimecasts.net作为令人印象深刻的免费,优质网络广播来源,涵盖上述所有主题。

答案 2 :(得分:19)

这是非常主观的,但以下是我对您的代码所做的几点:

  • 在我看来,你混合了ProductShoppingCartItemProduct应该包含产品名称,纳税身份等,但不包括数量。数量不是产品的属性 - 对于购买该特定产品的公司的每个客户而言,数量都不同。

  • ShoppingCartItem应该有Product和数量。这样,客户可以自由购买更多或更少的同一产品。使用您当前的设置是不可能的。

  • 计算最终税也不应该是Product的一部分 - 它应该是ShoppingCart之类的一部分,因为最终的税收计算可能涉及了解购物车中的所有产品。

答案 3 :(得分:14)

首先,这是一个非常好的面试问题。这是许多技能的良好衡量标准。

你需要了解很多东西才能提供一个好的答案(没有完美的答案),无论是高级还是低级。这是一对夫妇:

  • 域建模 - &gt;你如何创建一个良好的解决方案模型?你创建了什么对象?他们将如何解决这些要求?寻找名词是一个好的开始,但你如何决定你的实体选择是否良好?你需要什么其他实体?你需要解决什么领域知识
  • 关注点分离,松散耦合,高内聚 - &gt;如何将设计中具有不同关注点或变化率的部分分开,以及如何将它们联系起来?您如何保持设计灵活和最新?
  • 单元测试,重构,TDD - &gt;提出解决方案的流程是什么?你编写测试,使用模拟对象,重构,迭代吗?
  • 清洁代码,语言习语 - &gt;您是否使用编程语言的功能来帮助您?你写的是可以理解的代码吗?你的抽象层次是否有意义?代码的可维护性如何?
  • 工具:您使用的是源代码管理吗?构建工具?的IDE?

从那里,您可以进行许多有趣的讨论,包括设计原则(如SOLID原则),设计模式,分析模式,领域建模,技术选择,未来发展路径(例如,如果我添加数据库或富有UI层,需要改变什么?),权衡,非功能性要求(性能,可维护性,安全性......),验收测试等......

我不会评论你应该如何改变你的解决方案,只是你应该更多地关注这些概念。

但是,我可以向你展示如何I (partially) solved this problem,就像一个例子(在Java中)。查看Program class,了解如何将所有内容组合在一起打印此收据:

------------------ THIS IS YOUR ORDER ------------------
(001)                Domain Driven Design -----   $69.99
(001)    Growing Object Oriented Software -----   $49.99
(001)                 House M.D. Season 1 -----   $29.99
(001)                 House M.D. Season 7 -----   $34.50
(IMD)    Growing Object Oriented Software -----    $2.50
(BST)                 House M.D. Season 1 -----    $3.00
(BST)                 House M.D. Season 7 -----    $3.45
(IMD)                 House M.D. Season 7 -----    $1.73
                                SUB-TOTAL -----  $184.47
                                TAX TOTAL -----   $10.68
                                    TOTAL -----  $195.15
---------------- THANKS FOR CHOOSING US ----------------

你一定要看看这些书: - )

同样需要注意:我的解决方案仍然非常不完整,我只关注快乐路径场景,以便有一个良好的基础来构建。

答案 4 :(得分:12)

除了你使用的是一个名为product的类之外,你还没有证明你知道什么是继承,你还没有创建多个继承自Product的类,没有多态。可以使用多个OOP概念解决问题(甚至只是为了表明你了解它们)。这是一个面试问题,所以你想表明你知道多少。

然而,我现在不会变成抑郁症。你没有在这里展示它们的事实并不意味着你不了解它们或者无法学习它们。

您只需要在OOP或面试方面获得更多经验。

祝你好运!

答案 5 :(得分:9)

开始使用OOP学习编程的人在理解它的含义方面没有太大的问题,因为它与现实生活中的一样。如果你有其他编程技能而不是面向对象的技能,那么理解它可能会更难。

首先,关闭屏幕,或退出您喜欢的IDE。使用论文铅笔,并列出实体关系计算机进程东西 所有 可能遇到的情况进入你的最终计划。

其次,尝试获取不同的基本实体。您将了解有些人可以共享属性能力,您必须将其放在抽象对象中。你应该开始绘制一个很好的程序模式。

接下来,你必须设置fonctionnalities(方法,函数,子程序,根据需要调用它):例如,产品对象不应该计算销售税销售引擎对象应该。

不要对所有大字(接口属性多态传统感到烦恼,等)和第一次设计模式,甚至没有尝试制作漂亮的代码或其他任何东西......只要想到简单的对象之间的相互作用就像在现实生活中一样

之后,尝试阅读一些关于此的严肃简洁的文章。我认为WikipediaWikibooks是一种非常好的开始方式,然后只需阅读有关GoF and Design PatternsUML的内容。

答案 6 :(得分:4)

首先不要将Product类与Receipt(ShoppingCart)类混合使用,quantity也应该是ReceipItemShoppingCartItem)的一部分为TaxCostTotalTaxTotalCost应该是ShoppingCart的一部分。

我的Product类只有NamePrice以及一些只读属性,例如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); } }
}

您的税款计算部分与Product耦合。产品未定义税收政策,而是税收类别。根据问题的描述,有两种营业税:BasicDuty税。您可以使用Template Method Design Pattern来实现:

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

最后是一个课税:

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

    }
}

您可以在MyFiddle上试用它们。

答案 7 :(得分:2)

完美的OOP实现是完全值得商榷的。从我在您的问题中看到的,您可以根据他们执行的角色来模块化代码,以计算最终价格,如Product,Tax,ProductDB等。

  1. Product可以是一个抽象类,像Books,Food这样的派生类型可以从它继承。税收适用性可以由派生类型决定。产品将根据派生类判断税是否适用。

  2. TaxCriteria可以是枚举,可以在购买时指定(导入,销售税适用性)。

  3. Tax课程将根据TaxCriteria计算税金。

  4. 根据XXBBCC建议的ShoppingCartItem可以封装产品和税务实例,这是将产品详细信息与数量,总价格和税收等分开的好方法。

  5. 祝你好运。

答案 8 :(得分:1)

从严格的OOA / D角度来看,我看到的一个主要问题是大多数类属性在属性名称中都有类的冗余名称。例如产品价格,类型产品。在这种情况下,无论你在哪个地方使用这个类,你都会有过于冗长和有些混乱的代码,例如: product.productName。从属性中删除冗余的类名前缀/后缀。

此外,我没有看到任何关于购买和创建收据的课程,如问题所示。

答案 9 :(得分:1)

以下是产品,税务等OO模式的一个很好的例子......请注意接口的使用,这在OO设计中是必不可少的。

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

答案 10 :(得分:1)

关于设计规则的一个非常好的起点是SOLID原则。

例如,Open Closed原则指出,如果要添加新功能,则不必向现有类添加代码,而是添加新类。

对于您的示例应用程序,这意味着添加新的销售税将需要添加新类。对于规则例外的不同产品也是如此。

舍入规则显然属于单独的类 - 单一责任原则规定每个班级都有一个责任。

我认为尝试自己编写代码会带来更多的好处,而不仅仅是编写一个好的解决方案并将其粘贴在这里。

编写完美设计程序的简单算法是:

  1. 编写一些解决问题的代码
  2. 检查代码是否符合SOLID原则
  3. 如果违反规则而不是goto 1。

答案 11 :(得分:0)

使用“访问者”模式解决含税成本问题。

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