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


98

最近,我正在尝试一家公司“ x”。他们向我发送了一些问题,并告诉我仅解决一个。

问题是这样的-

除免税的书籍,食品和医疗产品外,所有商品的基本销售税税率为10%。
进口税是对所有进口商品征收的附加销售税,税率为5%,且无任何豁免。

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

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

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

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

因此,我提供了以下代码-您可以复制粘贴代码并在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
     }
 }

您可以取消commnet输入并运行其他输入。

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

“他们说,由于代码解决方案不能令人满意,他们无法考虑我目前的空缺职位。”

请指导我这里缺少什么。是这个解决方案不是一个好的OOAD解决方案。
如何提高我的OOAD技能。
我的前辈们还说,完美的OOAD应用程序实际上也行不通。

谢谢


2
也许他们希望您使用继承层次结构而不是枚举来区分产品类型?(尽管我认为在给定的情况下该方法会比较复杂。)
Douglas

我的猜测是,由于您未定义任何接口,他们会拒绝您的解决方案。
克里斯·盖斯勒

28
根据经验,如果有人在面试中要求您展示OOP技能,则应尽量避免使用switch语句-而是使用继承层次结构。

4
应该在代码审查中发布。
德里克(Derek)2012年

我也在那里张贴过,但是在那里找不到很好的解决方案。但是每个人都可以看到我在别人的帮助下创建的新解决方案 codeproject.com/Questions/332077/… 在这里您还可以找到我的新代码。
2012年

Answers:


246

首先,美好的天堂不能双重地进行财务计算。用十进制进行财务计算; 这就是它的目的。使用double解决物理问题,而不是财务问题。

程序中的主要设计缺陷是策略放置在错误的位置。谁负责计算税金?您已经让产品负责计算税款,但是当您购买苹果,书籍或洗衣机时,要购买的商品并不能告诉您要支付多少税款。它。 政府政策负责告诉您这一点。您的设计严重违反了的基本OO设计原则对象应负责自己的关注,而不是其他任何人的关注。洗衣机的问题是要洗衣服,而不要收取正确的进口税。如果税法改变了,你不想改变洗衣机对象,您要更改策略对象

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

我将首先突出问题描述中的每个重要名词:

基本的销售税是适用在速度的10%的所有产品,除了书籍食品医疗产品是免税。进口税是一种适用于所有进口商品的附加销售税税率为5%,且无任何豁免。当我购买物品时,会收到一张收据,上面列出了所有物品名称及其价格含税),并附有总费用项目以及已支付的营业税总额。营业税的四舍五入规则是,对于n%的税率,货架价格 p包含(np / 100舍入为最接近的0.05)营业税额

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

  • 基本营业税是一种营业税
  • 进口税是一种营业税
  • 销售税的税率是小数
  • 书是一种物品
  • 食物是一种物品
  • 医疗产品是一种
  • 物品可能是进口货物
  • 一个项目的名称是一个字符串
  • 物料的货架价格为小数。(注意:某项商品确实有价格吗?两台相同的洗衣机可能在不同的商店或同一家商店在不同的时间以不同的价格出售。一种更好的设计可能是说定价政策将某商品与其价格。)
  • 营业税免税政策描述了在某项商品上不适用营业税的条件。
  • 收据包含商品,价格和税金的列表。
  • 收据总计
  • 收据总税

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


12
您需要的不仅仅是提供的东西?听起来您需要进一步了解如何实现继承以及什么是多态。
Induster

27
@sunder:这个答案绰绰有余。现在,您可能有责任发展自己的技能,也许以此为例。请注意,您的示例是一个真实示例的定义。您没有通过现实生活采访,因为该现实生活代码需要您没有提供的现实生活设计。
格雷格D

9
@Narayan:double非常适合在正确答案的0.00000001%以内就足够的情况。如果您想弄清楚砖在半秒钟后掉落的速度有多快,请进行两次数学运算。当您做两次金融理财交易时,最终会得到诸如税后价格为43.79999999999999美元的答案,即使它与正确答案极为接近,这看起来也很愚蠢。
埃里克·利珀特

31
+1您已经强调了一项非凡的练习,它是检查所陈述问题中的每个名词,然后枚举它们之间的关系。好想法。
克里斯·汤金森

3
@Jordão:以十进制表示,十次加0.10确实得到1.00。但是,将103乘以1.0 / 333.0并不一定得到十进制或双精度的1。以十进制表示的分母中具有10的幂的分数被精确表示;在双打中,它是具有2的幂的分数。其他任何东西都大致代表。
埃里克·利珀特

38

如果公司对NUnit,JUnit或Test :: Unit之类的库有所了解,则TDD确实很可能会导入到它们中。在您的代码示例中根本没有测试。

我将尝试证明以下方面的实践知识:

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

我想推荐www.dimecasts.net作为令人印象深刻的免费优质屏幕录像的来源,其中涵盖了上述所有主题。


19

这是非常主观的,但是我对您的代码有几点要点:

  • 在我看来你混ProductShoppingCartItemProduct应该具有产品名称,纳税状态等,但没有数量。数量不是产品的属性-购买该特定产品的公司的每个客户都会有所不同。

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

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


我对此答案的唯一问题是,它描述了如何构建更好的产品支付系统(有效),但并未真正阐述OOP方法。可以用任何语言来实现。如果不显示某种接口,继承性,多态性等,他仍然会通过测试。
2012年

关于最后一点:由于单一的可重复性原则,IMO最佳的税收计算地点是单独的TaxCalculator类。
Radek

感谢您的答复,但实践如何。每个公司都在如此广泛和纯净的OOPS模型中工作吗?
2012年

@shyamsunder我的回答并没有真正的纯粹。它没有使用OOD的重要方面的接口/继承性,但确实显示了最重要的原理-在我看来-就是将责任放在它们的位置。正如其他答案所指出的那样,设计的主要问题是您在各个参与者之间混合了责任,这会在添加功能时导致问题。大多数大型软件只有遵循这些原则,才能发展壮大。
xxbbcc 2012年

很好的答案,但我也同意税收计算应该是一个单独的对象。

14

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

要提供一个好的答案(没有完美的答案),您需要了解很多事情,无论是高级的还是低级的。这是一对:

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

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

我不会评论您应该如何更改解决方案,只是您应该更加关注这些概念。

但是,作为示例(在Java中),我可以向您展示如何(部分)解决此问题。查看Program班级以了解如何一起打印此收据:

------------------这是您的订单------------------
(001)域驱动设计-$ 69.99
(001)面向对象的成长软件--- $ 49.99
(001)众议院MD季1 ----- $ 29.99
(001)众议院MD季7 ----- $ 34.50
(IMD)增长的面向对象软件----- 2.50美元
(BST)House MD Season 1 ----- $ 3.00
(BST)House MD Season 7 ----- $ 3.45
(IMD)House MD Season 7 ----- $ 1.73
                                小计----- $ 184.47
                                税金总计----- $ 10.68
                                    总计----- $ 195.15
----------------感谢选择我们----------------

您绝对应该看看那些书:-)

需要注意的是:我的解决方案仍然很不完善,我只是专注于快乐的道路场景,以便有良好的基础。


我仔细研究了您的解决方案,发现它很有趣。虽然我认为Order类不应该负责打印收据。同样,TaxMethod类不应负责计算税款。另外,TaxMethodPractice不应包含TaxMethod的列表。相反,名为SalesPolicy的类应包含此列表。应该将名为SalesEngine的类传递给SalesPolicy,Order和TaxCalculator。SalesEngine将对订单中的项目应用SalesPolicy并使用TaxCalculator来计算税款-CKing
2012年

@bot:有趣的观察....现在,Order打印收据,但Receipt知道其自身的格式。同样,TaxMethodPractice是一种税收政策,它包含适用于特定情况的所有税收。TaxMethods 税收计算器。我觉得您只缺少一些更高级别的绑定类,例如您建议的SalesEngine。这是一个有趣的想法。
Jordão酒店

我只是觉得每个类都必须有一个明确定义的职责,代表现实世界对象的类的行为应与现实世界相一致。为此,可以将TaxMethod分为两个类。TaxCriteria和TaxCalculator。同样,订单不得打印收据。ReceiptGenerator应该传递给Receipt以生成收据。
2000年

@bot:我完全同意!好的设计是坚实的!TaxMethod 一个税收计算器,而TaxEligibilityCheck 一个税收准则。他们是独立的实体。至于收据,是的,拆分生成部分将进一步改善设计。
Jordão酒店

1
这个想法来自规范模式,看看吧!
Jordão酒店

12

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

但是我现在不会变成沮丧。您未在此处演示它们的事实并不意味着您不了解它们或无法学习它们。

您只需要多一点OOP或面试经验。

祝好运!


实际上,这是我的第一个设计,我创建了另一个设计,但由于字符数超出限制而无法显示。
2012年

您能否借助任何示例进行演示?
2012年

@sunder:您可以使用新设计来更新问题。
比尔克·弗洛伊德·汉森

10

开始使用OOP学习编程的人并没有很大的问题去理解它的含义,因为它就像在现实生活中一样。如果您具有与OO以外的其他编程技能,那么可能很难理解。

首先,关闭屏幕,或退出您喜欢的IDE。拿一支和一支铅笔,列出实体关系工艺材料一切可能遇到到您的最终方案。

其次,尝试获得不同 基本实体。您将了解,有些可以共享 属性功能,因此必须将其放在抽象对象中。您应该开始绘制程序的漂亮架构。

接下来,您必须放入函数(方法,函数,子例程,根据需要调用):例如,产品对象不应该能够计算营业税。一个销售引擎对象应该。

不要为所有大词而烦恼(接口属性多态性传统等)和设计模式甚至不要尝试编写漂亮的代码或其他任何东西……只需考虑简单的对象它们之间的干扰如在现实生活中

之后,尝试阅读一些有关此问题的简明文献。我认为 WikipediaWikibooks是一个很好的开始方式,然后阅读有关GoF,Design PatternsUML的知识


3
为“首先关闭屏幕”设置+1。我认为思考的能力常常被误认为计算的能力。
kontur 2012年

1
+1是使用铅笔和纸的最简单方法。很多时候当人们坐在IDE面前时都会感到困惑:)
Neeraj Gulia's 2012年

一些科学家说,我们的大脑在观看屏幕时注意力不集中。当我学习软件架构设计时,我们的老师让我们在纸上工作。他不介意强大的UML软件。重要的是首先了解事物。
smonff'3

4

首先不要将Product类与Receipt(ShoppingCart)类混合使用,该类quantity应该是ReceipItemShoppingCartItem)以及Tax&的一部分Cost。在TotalTaxTotalCost应成为其中的一部分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中试用它们。


2

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

例如,开放封闭原则指出,如果您想添加新功能,则不必向现有类中添加代码,而只需添加新类。

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

四舍五入规则显然是在单独的类别中进行的-“单一职责”原则规定,每个类别都具有单一职责。

我认为,尝试自己编写代码比单纯编写一个好的解决方案并将其粘贴到这里会带来更多的好处。

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

  1. 编写一些解决问题的代码
  2. 检查代码是否符合SOLID原则
  3. 如果存在违反规则,则转到1。

2

完美的OOP实现是完全有争议的。根据我在问题中看到的内容,您可以根据代码在计算最终价格(例如产品,税金,产品数据库等)时所扮演的角色来对代码进行模块化。

  1. Product可以是一个抽象类,派生类型(如Books,Food)可以从中继承。税收适用性可以由派生类型决定。产品会根据派生类别来判断该税是否适用。

  2. TaxCriteria 可以是一个枚举,并且可以在购买期间指定(导入,适用于营业税)。

  3. Tax类将基于计算税TaxCriteria

  4. 有一个ShoppingCartItem通过的建议XXBBCC可以封装产品和税收情况下,这是一个伟大的方式来隔离产品的细节与数量,总价格与税收等。

祝好运。


1

从严格的OOA / D角度来看,我看到的一个主要问题是,大多数类属性在属性名称中都具有该类的冗余名称。例如product Price,typeOf Product。在这种情况下,在任何使用该类的地方,您都将拥有过于冗长和有些混乱的代码,例如product.productName。从属性中删除多余的类名前缀/后缀。

另外,我没有看到任何与购买和创建收据有关的课程,正如问题所要求的那样。


1

这是产品,税项等OO模式的一个很好的示例。请注意,接口的使用在OO设计中至关重要。

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


3
我宁愿使产品成为(抽象)类而不是使其成为接口。我也不会将每个产品都设置为单独的类。最多我将为每个类别创建一个类。
CodesInChaos

@CodeInChaos-在大多数情况下,您都需要两者,但是如果您想担任建筑师的工作,我会选择在Abstract类上实现Interfaces。
克里斯·盖斯勒

1
本示例中的接口完全没有意义。它们只会在实现它们的每个类中导致代码重复。每个类都以相同的方式实现它。
皮奥特·霹雳

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

欢迎使用stackoverflow。请确保您对问题做出解释。OP不仅在寻求解决方案,而且还在寻求更好/更差的解决方案。
Simon.SA
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.