我最近申请了初级.NET开发人员职位,被要求解决一个问题(销售税计划),但由于某种原因我被拒绝了。问题是这样的:


除食品,药品和书籍外,所有商品的基本营业税税率为10%。对于所有进口商品,无论其类型如何,均适用5%的附加税率。生成收据,其中显示所有已购买商品的名称及其价格(包括税金),商品总成本和已付营业税的总金额。营业税的四舍五入规则是,对于n%的税率,P的货架价格包含(np / 100四舍五入至最接近的0.05)营业税额。编写一个应用程序以生成具有所有这些详细信息的收据。


他们给了我输入的信息,输出应该是:


输入3:


1瓶进口香水的价格为27.99
1瓶香水的价格为18.99
1包头痛药的价格为9.75
1盒进口巧克力的价格为11.25

输出3:


1瓶进口香水:32.19
1瓶香水:20.89
1包头痛药:9.75
1盒进口巧克力:11.85
营业税:6.70
总计:74.68




他们又给了两套输入,但我只是没有节省时间。

请查看我的代码,让我知道这有什么问题,以及解决诸如

namespace Products
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // calling the Generatereciept class and invoking Generate method to display all the product details, price, taxes and total after taxes
                GenerateReciept generate = new GenerateReciept();
                generate.Generate();
            }
            catch (Exception ex)
            {
                // writing the exception to console.. we can log it to a database or eventviewer based on the requirements
                Console.WriteLine(ex.Message);

            }

            Console.ReadLine();
        }
    }
    /// <summary>
    /// enum for determining whether a product is imported or not
    /// </summary>
    public enum ProdType
    {
        Imported = 1,
        NonImported = 2
    }
    /// <summary>
    /// enum for determining whether product is tax exempted or not
    /// </summary>
    public enum TaxStatus
    {
        Taxable = 1,
        NonTaxable = 2
    }
/// <summary>
/// contains the product details
/// </summary>
class Product
{
    private string _Name;
    private TaxStatus _TaxStatus;
    private decimal _Price;
    private ProdType _Typeofproduct;

    public string Name
    {
        get
        {
            return _Name;
        }
    }

    public TaxStatus TaxStatus
    {
        get
        {
            return _TaxStatus;
        }
    }
    public decimal Price
    {
        get
        {
            return _Price;
        }
    }
    public ProdType Typeofproduct
    {
        get
        {
            return _Typeofproduct;
        }
    }

    /// <summary>
    /// Initializes the fields
    /// </summary>
    /// <param name="name"></param>
    /// <param name="taxStatus"></param>
    /// <param name="price"></param>
    /// <param name="typeOfProduct"></param>
    public Product(string name, TaxStatus taxStatus, decimal price, ProdType typeOfProduct)
    {
        _Name = name;
        _TaxStatus = taxStatus;
        _Price = price;
        _Typeofproduct = typeOfProduct;
    }
}

/// <summary>
/// maintains product list
/// </summary>
class ProductsList
{
    /// <summary>
    /// Based on the input given, added products and their respective info to the list
    /// </summary>
    /// <returns></returns>
    public List<Product> GetProducts()
    {
        List<Product> list = new List<Product>
       {
           //input 1
           new Product("Book", TaxStatus.NonTaxable, 12.49M, ProdType.NonImported),
           new Product("MusicCD", TaxStatus.Taxable, 14.99M, ProdType.NonImported),
           new Product("ChocolateBar", TaxStatus.NonTaxable, 0.85M, ProdType.NonImported),

           ////input 2
           //new Product("Imported  box of Chocolates", TaxStatus.NonTaxable, 10.00M, ProdType.Imported),
           //new Product("Imported bottle of Perfume", TaxStatus.Taxable, 47.50M, ProdType.Imported),

           ////input 3
           //new Product("Imported bottle of Perfume", TaxStatus.Taxable, 27.99M, ProdType.Imported), 
           //new Product("Bottle of Perfume", TaxStatus.Taxable, 18.99M, ProdType.NonImported), 
           //new Product("Packet of headache Pills", TaxStatus.NonTaxable, 9.75M, ProdType.NonImported), 
           //new Product("Box of Imported Chocolates", TaxStatus.NonTaxable, 11.25M, ProdType.Imported)
       };
        return list;
    }
}



 /// <summary>
    /// declares a method for calculating basic sales tax 
    /// </summary>
    interface IbasicTax
    {
        decimal CalculateBasicTax();
    }
    /// <summary>
    /// declares an method for calculating full tax( basic sales tax + additional tax for imported goods)
    /// </summary>
    interface IFullTax
    {
        decimal CalculateFullTax();
    }
    /// <summary>
    /// declares method for calculating imported tax only( non taxable imported products)
    /// </summary>
    interface IImportedTax
    {
        decimal CalculateAdditionalTax();
    }
    /// <summary>
    /// class inheriting from all the interfaces and implementing them
    /// </summary>
    class Tax : IFullTax, IbasicTax, IImportedTax
    {
        decimal tax = 0;
        decimal _price;

        public Tax(decimal price)
        {
            _price = price;
        }
        public decimal CalculateBasicTax()
        {
            return tax += 0.1M * _price;
        }

        public decimal CalculateAdditionalTax()
        {
            return tax += 0.05M * _price;
        }

        public decimal CalculateFullTax()
        {
            return tax += 0.15M * _price;
        }
    }

    class GenerateReciept
    {
        decimal TotalSum = 0, TotalTaxes = 0;

        /// <summary>
        /// Generates the products reciept based on the taxes imposed on the products... calculates and displays in an order suggested
        /// </summary>
        public void Generate()
        {
            // Program program = new Program();
            ProductsList list = new ProductsList();
            List<Product> products = list.GetProducts();

            foreach (Product p in products)
            {
                decimal a = 0;
                decimal Taxamount = 0;
                if (p.TaxStatus == TaxStatus.Taxable)
                {
                    if (p.Typeofproduct == ProdType.Imported)
                    {
                        IFullTax tax = new Tax(p.Price);
                        a = tax.CalculateFullTax();

                    }
                    else
                    {
                        IbasicTax tax = new Tax(p.Price);
                        a = tax.CalculateBasicTax();

                    }
                }
                else
                {
                    if (p.Typeofproduct == ProdType.Imported)
                    {
                        IImportedTax tax = new Tax(p.Price);
                        a = tax.CalculateAdditionalTax();

                    }
                }
                Taxamount = Math.Ceiling(a * 20) / 20;
                TotalTaxes += Taxamount;
                TotalSum += Taxamount + p.Price;

                Console.WriteLine("1 {0} : {1}", p.Name, Taxamount + p.Price);
            }

            Console.WriteLine("Total Sales Tax : {0}", TotalTaxes);
            Console.WriteLine("Total after taxes: {0}", TotalSum);

        }
    }
}


我有输出,但由于某种原因,它被拒绝了。请让我知道我的守则中有哪些缺陷。即使它完全有缺陷,也请让我知道。我想纠正自己,并尝试不再重蹈覆辙。

评论

您得到预期的输出还是其他?

我得到了输出..我得到了他们要求我显示的输出。

类似的问题在这里。

同样,没有什么可以阻止您实际询问面试官拒绝的具体原因。他们能做的最坏的事情是拒绝或不响应。否则,您会发现自己掌握了很好的信息,这些信息有望对给出的答案有所帮助。

#1 楼

作为面试官,我希望看到候选人证明我正在招聘的技能。特别是,我想查看单元测试,因为如果您为我工作,我希望您为代码编写测试。如果您为代码编写了单元测试,则可能会抓到其他答案中列出的许多项目。至少您会演示一种有组织的方法来编码您的解决方案。

很难说面试官一直在寻找什么,因为我们不是他。他可能想查看您是否识别出何时应用策略模式(由于不同类型的项目具有不同的税收规则)。他可能一直在寻找您来组织交易中的项目(而不仅仅是产品列表) ),首先使用“依赖项注入”将交易传递给税收计算器,然后传递给收据生成器。也许他查看了Generate()的单片函数,并说“单一方法过于复杂”。也许他正在寻找SOLID原则的整体更好的证据。

我们显然无法确切知道他想要什么。您展示了对语言的掌握,只需要在编码中应用面向对象的设计原理即可。我认为这可以教初级开发人员。因此,也许他是在找技术熟练的人钓鱼,但只想付初级开发商的工资。

评论


\ $ \ begingroup \ $
您是否正在寻找初级职位的单元测试?我认为我对大三的定义与您的大不相同。还是仅仅是一般性的说法?
\ $ \ endgroup \ $
–詹姆斯·科里(James Khoury)
2015年5月21日,3:46

\ $ \ begingroup \ $
我希望每位自称软件工程师的人都可以进行单元测试,而不论其级别如何。除非您的测试证明,否则您还如何向我证明您的逻辑有效?除非您能够编写一个实际使用它的测试,否则您如何向我证明您的代码具有可用的接口?任何人最终都会偶然发现某些代码,这些代码的输出意外地符合要求;这并不意味着我有能力维护它。没有进行单元测试,就不是软件工程。
\ $ \ endgroup \ $
–约翰·迪特斯
2015年5月21日,3:57

\ $ \ begingroup \ $
我只是感到惊讶,您在初级阶段就可以期望它。我不是从大学那学到的,这是我在工作中必须学习的东西。我希望大三学生有能力学习,而不是有经验来了解这些东西。虽然如果候选人可以进行单元测试绝对是一个加号,但我认为这是可以学习的,而如果他们不能显示编程的基本概念,那么他们的单元测试就没有关系了。
\ $ \ endgroup \ $
–詹姆斯·科里(James Khoury)
2015年5月21日在4:04

\ $ \ begingroup \ $
@JamesKhoury取决于该公司中的“初级开发人员”是否表示“研究生开发人员”。可能不会。
\ $ \ endgroup \ $
–本·亚伦森
2015年5月21日在8:58

\ $ \ begingroup \ $
@BenAaronson“初级开发人员”和“研究生开发人员”不必是同一个人。但是,由于“初级开发人员”本质上是入门级,因此任何研究生开发人员都将是初级开发人员。
\ $ \ endgroup \ $
–碧玉
15年5月21日在9:01

#2 楼

Janos有一些很棒的指示。根据我的经验,还有很多其他问题尚未解决,因此我将添加我的内容:

Product Class之外的特定于产品的逻辑

您的Generate()函数具有似乎特定于Product的逻辑,这与C#的面向对象的性质相矛盾。我要指的特定代码是:

foreach (var p in products)
{
    decimal a = 0;
    decimal Taxamount = 0;
    if (p.TaxStatus == TaxStatus.Taxable)
    {
        if (p.Typeofproduct == ProdType.Imported)
        {
            IFullTax tax = new Tax(p.Price);
            a = tax.CalculateFullTax();
        }
        else
        {
            IbasicTax tax = new Tax(p.Price);
            a = tax.CalculateBasicTax();
        }
    }
    else
    {
        if (p.Typeofproduct == ProdType.Imported)
        {
            IImportedTax tax = new Tax(p.Price);
            a = tax.CalculateAdditionalTax();

        }
    }

    Taxamount = Math.Ceiling(a * 20) / 20;
    // ...
}


将产品逻辑隔离到类本身会更加清楚。例如,我建议最初将Generate()的代码重构为:

接口

使用接口的方式并没有任何实际用途。

此外,如果您只是为了添加接口而添加接口,通常我会发现这会使代码难以读取。保持简单没有错。

foreach (var p in products)
{
    decimal a = p.CalculateTax();
    decimal Taxamount = Math.Ceiling(a * 20) / 20;
    // ...
}


以后,如果您发现使用接口代替特定类的好方法,则可以对其进行重构。还要重构上面的函数,以免每次尝试计算税额时都不会继续创建新的Tax对象。我只是以它为例。

多个项目

我不确定这是否是要求的一部分。也就是说,您的代码无法处理多个项目。

Generate()仅一次打印出每个项目。您甚至还具有以下一项的硬编码输出:与FetchProducts()的相同实例相同。使用当前代码,将新书添加为以下内容似乎很自然:

相同的项目。您是否注意到第二本书又增加了0.10?

输出格式

运行代码时,TotalTaxes打印为Product而不是1.5。如果使用Console.WriteLine,则可以选择几种数字格式字符串。考虑到它们具有的格式,这将起作用:至少在其当前状态下,不需要1.50类。这是一个静态列表,不接受用户的任何输入,也不实现任何可能暗示它可以附加到包含产品清单的任何类型的数据存储中的接口。一直是一种模仿库存数据库清单并可以搜索特定物料的方法。


评论


\ $ \ begingroup \ $
感谢您花一些时间并解释错误的原因..是的,我有意使用界面,因为我上初中,所以我想向他们展示我可以使用继承链并将问题分解为单个模块和服务器的目的,但不幸失败了。如果您可以提供任何链接,材料或代码来显示如何使用SOLID原理和OOD以正确的方式编写代码,这对我将非常有帮助。非常感谢
\ $ \ endgroup \ $
–主宰
15年5月21日在13:50

#3 楼

一些严重的问题:



Main方法捕获到Exception,但我找不到原因。


如果必须处理一些异常,则应捕获可能抛出的最具体的类型。
如果您不希望抛出异常,请不要t catch
Main方法中,仅捕获异常以将其打印到控制台,因此不会添加任何值。在这种情况下,您可以声明Main引发异常,运行时无论如何都将打印堆栈跟踪信息。这些接口的用途不多,而且命名也很差。具有3种方法的单个TaxCalculator接口会更好。但是从程序的其余部分来看,我根本看不出接口的用途。
ProductsList类似乎毫无意义。它没有封装名称所暗示的内容:不包含产品列表,也不管理产品列表。它具有创建产品列表的方法,但是此类功能不需要类。我建议阅读《代码完整》(第6章)一书中有关抽象数据类型的内容。这些是较小的问题,但仍然不是很漂亮:


为什么要在枚举中分配值?不要编写毫无用处的代码。您可以删除这些值而不会丢失任何内容。
GetProducts每次调用时都会创建一个新的对象列表。通常我不会期望一个名为GetSomething的方法来分配内存。如果重命名为CreateProducts,它将使其更加直观。本地list变量也是多余的,最好直接返回一个新列表。


评论


\ $ \ begingroup \ $
非常感谢您抽出宝贵的时间并提供意见。如果您可以向我推荐一些可以帮助我用C#编写干净代码的书,这对我真的很有帮助。
\ $ \ endgroup \ $
–主宰
15年5月20日在21:57

\ $ \ begingroup \ $
之所以包含接口,是因为我想向他们展示我对OOP的了解,但事与愿违。他们要求我在我编写的代码中显示OOP。因此,我使用接口来显示继承。我使用枚举仅对ProductType进行分类(是否导入,是否征税)..我不知道如何解决该问题,所以我使用了枚举。任何实现这一目标的建议将不胜感激。
\ $ \ endgroup \ $
–主宰
15年5月20日在22:03

\ $ \ begingroup \ $
“如果您不希望引发异常,请不要抓住” Hmm ...在这个微不足道的示例中,我同意这一点,但是对于大型生产系统而言,那不是必然是最好的建议。将堆栈跟踪记录到文件并显示比运行时未处理的异常对话框更干净的内容可能会更有用,并且不会降低用户对软件质量的看法。在可能需要与其他平台中的代码进行互操作的情况下,为枚举指定值也很有用。
\ $ \ endgroup \ $
– reirab
2015年5月21日在7:05

#4 楼

在我看来,最大的担忧是,以税收身份作为最重要的属性来建模产品。这使得很难在不同的上下文中重用对象,例如,库存控制系统或在线店面。此外,一本书的纳税状况不是固定不变的,因为在另一个辖区中,它可能需要按基本税率征税,在玻利维亚印刷的一本书在澳大利亚可能是进口书,但在玻利维亚却不是。

代码可能符合规范,但非常脆弱。它不容易修改以解决相关情况或支持做出逻辑上隐含的业务决策,因为它不努力适应更大的业务运营环境。代码是达到目的的一种手段,而不是目的本身。

建模

设计精心设计的解决方案的关键是拥有适当的系统模型。当然,有时候快速而肮脏的就足够了,但是通常没有诸如销售交易之类的核心业务功能。在计算机输入和输出方面。我们得到订单并产生收据。



它与转换如何进行或特定实现的长期影响无关。 。

简单的以业务为中心的模型

收据记录了要付款的货物交换。因此,在下订单时,稳固的业务不会产生收据。收据仅在订购商品售出后生成。



收据是交易的记录,而不是实际的交易。而且我们需要一个中间位置来实际执行交易。

系统接口

在任何地方都有箭头,都存在某种中间转换过程。 />订单是适合于订购过程的某种记录。它可能以与实际销售交易无关的方式绑定到其他系统。它的格式可能不适合进行销售,因此我们需要执行以下步骤:读取订单并生成适合销售过程的数据结构。



同样,对于销售而言,理想的销售数据结构可能对收据而言并不理想。因此,还有另一个接口可以将销售数据格式化为适合创建收据的数据结构。



课程粒度对象

在每个阶段:订单,销售,收据,都有一个聚合对象。



值得注意的是,在此抽象级别上,Order-ObjectSale Object之间存在明显的同构。 br />
细粒度对象

该过程的每个步骤都有对应的细粒度记录类型,可捕获聚合的各个项目。







注意此级别:


同构性在Order ItemReceipt Item。这与输入输出的朴素模型保持一致。
Sale Item捕获与税收有关的业务逻辑。
Receipt Item是将销售的总计属性捕获为总计的地方。我们不一定需要在销售过程中缓存它们,因为如果有人需要它们,可以从Sale Object计算得出。在某些情况下,它们不会,例如是否存在提前付款折扣可能会在以后修改销售。

规范化

规范化过程包括:


List of Order Item排序在Order Object中。
处理每个Order Item


将任何Order Items分解为单独的行项目。 >将每个quantity > 1映射到Order Item中。



格式化

格式化过程包括:


Sale Item排序在List of Sale Item中。
处理每个Sale Object
>

将其成本加到运行总成本中。
将其税加到运行总成本中。
将所有Sale Item汇总到单个行项目中。
将每个聚合映射到Sale Items中。




评论


\ $ \ begingroup \ $
非常感谢您提供的信息。是的,我确实同意代码在很多方面都失败了。希望我将来不会犯此类错误。谢谢。感激:)
\ $ \ endgroup \ $
–主宰
2015年5月21日在14:06

\ $ \ begingroup \ $
@Juggernaut不客气。我添加了一些其他信息,但也许很无聊。
\ $ \ endgroup \ $
–本·鲁格斯
2015年5月21日在18:07

#5 楼

首先,您是否问过进口商品“额外5%税”是什么意思?假设100英镑的税前费用,10%的税= 110英镑,是从100英镑还是110英镑中提取5%?如果您不这样问,那将是主要的缺点。

其次,您有三种方法可以用四种方法中的三种来计算税率(10%税+进口税,10%税+无进口税,仅进口税),并假定在最后一种情况下税是零,不需要计算。这只是不可扩展的。如果您的税率为零,低,高和奢侈税,再加上来自一组国家和另一组国家的进口税,那么您突然有12种不同的方法。

我将有一个税计算对象,该对象被初始化为相关信息(基本税率是或否,进口税),然后可以退还税款以支付任何价格。界面很简单,正好是税额计算对象所需要的,实际的计算虽然很复杂,但是对调用者是隐藏的。

请注意,在其他情况下,整个系统可能也会失败:例如,如果您将商品进口到英国,则不能仅计算每件商品的进口税,因为要支付的税款取决于您的方式合并项目。

评论


\ $ \ begingroup \ $
第一个:他们给了我输出和输入..因此,我只用计算器一个接一个地分别计算了10%和5%,整体上也只计算了15%,而不是单独计算。对于下一个输入,我得到了给定输入的正确输出(整体为15%)。因此,我认为没有必要问他们如何计算税收。
\ $ \ endgroup \ $
–主宰
2015年5月21日在17:12

\ $ \ begingroup \ $
示例中提供了计算方法。但是,我认为封装税收计算的想法很好。这是对程序逻辑进行大量更改的地方,将其隔离将有助于长期维护。
\ $ \ endgroup \ $
–本·鲁格斯
15年5月22日在16:02

\ $ \ begingroup \ $
我相信这确实是关键。您必须能够正确思考问题,然后才能进行编程。两种税种的三种方法是不可扩展的,这是对的,但更重要的是,这是代码作者应发现和改进的事情,而不是软件的请求者。
\ $ \ endgroup \ $
–通配符
16-4-19的7:44

#6 楼

当我阅读代码时,我心想,

为什么在一个enum足够的情况下为什么要使用bool?为什么要征收三种税?规范中没有3种税种。
为什么Generate方法会根据产品类型做出决定?
完成后收据中包含哪些数据?回执是在创建产品吗?

我喜欢代码“可能最简单的方法”,所以我写类似的东西,

class Taxes
{
    readonly bool isSalesTax;
    readonly bool isImported;

    // alternatively instead of isSalesTax parameter,
    // could construct with a ProductType instance,
    // where ProductType has boolean properties like IsFood, IsMedicine, etc.
    internal Taxes(bool isSalesTax, bool isImported)
    {
        this.isSalesTax = isSalesTax;
        this.isImported = isImported;
    }

    internal decimal SalesTax(decimal price)
    {
        decimal rc = (isSalesTax) ? (price * 0.1M) : 0;
        // the spec says to round up to the nearest 0.05
        rc = Math.Ceiling(rc * 20) / 20;
        return rc;
    } 

    internal decimal ImportTax(decimal price)
    {
        return (isImported) ? (price * 0.05M) : 0;
    } 

    internal decimal TotalTax(decimal price)
    {
        return SalesTax(price) + ImportTax(price);
    } 
}

class Product
{
    // data from constructor parameters
    internal string Name { get; private set; }
    internal decimal Price { get; private set; }
    readonly Taxes taxes;
    // calculated properties
    internal decimal SalesTax { get { return Taxes.SalesTax(Price); } }
    internal decimal Total { get { return Price + Taxes.TotalTax(Price); } }

    internal Product(string name, decimal price, Taxes taxes)
    {
        this.Name = name;
        this.Price = price;
        this.taxes = taxes;
    }
}

class Receipt
{
    readonly List<Product> products;
    internal Receipt(IEnumerable<Product> products)
    {
        // use .ToList() to make a copy of the passed-in products
        this.products = products.ToList();
    }
    void Show()
    {
        decimal salesTaxes = 0;
        decimal total = 0;

        foreach (Product product in products)
        {
            salesTaxes += product.SalesTax;
            total += product.Total;

            Console.WriteLine(...this product...)
        }
        Console.WriteLine(...the two totals...)
    }
}


注意:


税收逻辑是独立的(在Taxes类中)
产品包含用于计算税收的Taxes实例
遵守德米特律,产品将公开SalesTax和Total属性,而不是公开其Taxes实例。
收据不会创建产品列表:创建收据的人会将产品作为构造函数参数传递
收据对税款一无所知
在OP中定义9种类型(ProdTypeTaxStatusProduct等);我定义了3。

求求你原谅,但只是为了回家,对OP的批评是:


不必要的复杂(例如用enum代替bool,并且这些抽象接口什么也不添加,也可以删除)。
系统无法按原样为3种不同的产品清单生成3张收据;编辑和重新编译使您可以为3个列表中的任何一个生成一张收据
产品定义占用太多垂直空间
更改Tax类不足以更改税收算法,因为还有很多生成方法中的征税逻辑。
TotalTaxes成员数据的值将根据调用Generate方法的次数而有所不同。唯一可以保证被调用一次的方法,即构造函数。因此,您的Generate方法应该是类构造函数,或者TotalTaxes应该是Generate方法的局部变量(而不是类的数据成员)。
规范说,营业税有一个四舍五入规则,而进口税没有。您还要四舍五入进口税。

a是变量的废话。
调用new Tax(p.Price)(并向上转换到接口)有三行不同,因此不必要的复杂,我想知道是否/为什么您认为有必要。
您初始化但不使用Tax类的tax数据成员。
因为将TaxStatus存储在Product中,所以您没有数据/状态存放在Tax类中;它也可能是带有静态方法的静态类,这些静态方法将价格作为方法参数而不是构造函数参数
您有一个名为TaxStatus TaxStatus(名称等于类型)的属性,另一个名为ProdType Typeofproduct(名称不等于类型)。当程序员使用常规/一致的命名约定时,我比较喜欢它。 br />好,我可以对上面的内容进行修改,使其具有IProduct接口和ITaxes接口,以使Receipt可以使用IProduct实例的枚举(而不是取决于特定的Product类型),以便Product可以与ITaxes实例一起使用(而不是使用特定的Taxes类型)。

那就是说OOP的意义远不止继承。最基本的原则是将实现隐藏在一个类中,因此,如果您需要更改规范或更改规范的实现,则只需要更改一个类。

例如在将大多数规格封装到我的Taxes类中方面有些成功。

#7 楼

首先,这是不正确的:1盒进口巧克力:11.85 <-正确的11.25 * 1.05 = 11.81(四舍五入)

有一个基本的抽象类Item和4个具体的类Book,Medical,Food and其他,IsImported是项bool属性

有一个基本的抽象类SalesTax和2个具体的类BasicTax,ImportTax

有一个10%免税的项目豁免清单

有3种方法(在此示例中)以使用依赖注入:1)使用穷人DI,2)使用Ninject,3)使用SimpleInjector。具有税收清单和存储库(项目清单)的构造函数依赖性注入。在Receipt类中,为了计算税金,在“产品清单”和“产品清单”(按名称分组,所以我也有相同商品的数量)内有一个循环,在“ taxesList”和方法“ CalculateTax”上有一个循环针对每个项目/数量

由于使用了依赖注入

可以进行单元测试

使用的网络功能:提取类/继承/相依注入/ Linq / IOC(Ninject-简单注入器)

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime;
using System.Text;
using SimpleInjector;

namespace CreateReceiptConsole
{
    class Program
    {
        private static void Main(string[] args)
        {
            Console.WindowWidth = 120;
            Console.WindowHeight = 20;

            try
            {
                //1)Using Poor Man DI (No IOC Container)

                ItemsRepository productList = new SqlRepository();
                IList<SalesTax> taxesList = new List<SalesTax>
                {
                    new BasicTax(),
                    new ImportTax()
                };

                var receipt = new Receipt(productList, taxesList);
                receipt.CreateReceipt();
                Console.Read();

                //2) Using Ninject

                //using (var kernel = new StandardKernel())
                //{
                //kernel.Bind(r => r
                //.FromThisAssembly()
                //.SelectAllClasses()
                //.InheritedFrom<SalesTax>()
                //.BindAllBaseClasses()
                //.Configure(x => x.InSingletonScope()));
                //kernel.Bind<ItemsRepository>().To<SqlRepository>();
                //var receipt = kernel.Get<Receipt>();
                //receipt.CreateReceipt();
                //Console.Read();
                //}

                //3) Using Simple Injector IOC Container/Framework

                //using (var container = new Container())
                //{
                //    container.Register<ItemsRepository, SqlRepository>(Lifestyle.Singleton);
                //    container.RegisterCollection(typeof(SalesTax), typeof(SalesTax).Assembly);
                //    container.Verify(VerificationOption.VerifyAndDiagnose);
                //    var receipt = container.GetInstance<Receipt>();
                //    receipt.CreateReceipt();
                //    Console.Read();
                //}
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }

    public class Receipt
    {
        private readonly IList<SalesTax> taxesList;
        private readonly ItemsRepository repository;

        public Receipt(ItemsRepository repository, IList<SalesTax> taxesList)
        {
            if (repository == null)
            {
                throw new ArgumentNullException(@"repository");
            }
            if (taxesList == null)
            {
                throw new ArgumentNullException(@"taxesList");
            }

            this.repository = repository;
            this.taxesList = taxesList;
        }

        public void CreateReceipt()
        {
            var itemsGroups = repository.Getproducts().OrderBy(item => item.GetType().Name).GroupBy(item => item.Name).ToList();

            decimal total = 0;
            decimal totalTaxes = 0;
            int totalItems = 0;
            StringBuilder sbLine = new StringBuilder();
            StringBuilder sbItemName = new StringBuilder();
            StringBuilder sbItemCategory = new StringBuilder();

            for (int i = 0; i < itemsGroups.Count; i++)
            {
                total += itemsGroups[i].Sum(p => p.Price);
                totalItems += itemsGroups[i].Count();
                decimal itemTaxes = taxesList.Sum(t => t.CalculateTax(itemsGroups[i].First(), itemsGroups[i].Count()));
                totalTaxes += itemTaxes;
                sbItemName.Append(itemsGroups[i].Key.PadRight(10, ' '));
                sbItemCategory.Append(itemsGroups[i].First().GetType().Name.PadRight(8, ' '));
                var itemCount = itemsGroups[i].Count();
                var itemPriceSum = itemsGroups[i].Sum(p => p.Price);

                sbLine.Append(@" Name: " + sbItemName.ToString().PadRight(26, ' ') + @" - Category: " + sbItemCategory + @" - Items: " + itemCount.ToString().PadRight(2, ' ') + @"- Price: " + itemPriceSum.ToString(CultureInfo.InvariantCulture).PadLeft(5, ' ') + " - Taxes: " + itemTaxes.ToString("F2") + " - Total: " + (itemPriceSum + itemTaxes).ToString("F2"));

                Console.BackgroundColor = i % 2 == 0 ? ConsoleColor.Black : ConsoleColor.White;
                PrintReceipt.PrintToScreen(sbLine.ToString(), i%2 == 0 ? ConsoleColor.White : ConsoleColor.Blue);
                sbLine.Clear();
                sbItemName.Clear();
                sbItemCategory.Clear();
            }

            sbLine.AppendLine("");
            sbLine.AppendLine("");
            sbLine.AppendLine(" Total Items: " + totalItems.ToString("F2"));
            sbLine.AppendLine(" Total Taxes: " + totalTaxes.ToString("F2"));
            sbLine.AppendLine(" Total Price: " + (total + totalTaxes).ToString("F2"));
            Console.BackgroundColor = ConsoleColor.Black;
            PrintReceipt.PrintToScreen(sbLine.ToString(), ConsoleColor.Red);        
        }
    }

    public abstract class Item
    {
        public decimal Price { get; }
        public string Name { get; }
        public bool IsImport { get; }

        protected Item(string name, decimal price, bool isImport)
        {
            Name = name;
            Price = price;
            IsImport = isImport;
        }

        public override string ToString() => Name;
    }

    public class Book : Item
    {
        public Book(string name, decimal price, bool isImport) : base(name, price, isImport) { }
    }

    public class Medical : Item
    {
        public Medical(string name, decimal price, bool isImport) : base(name, price, isImport) { }
    }

    public class Food : Item
    {
        public Food(string name, decimal price, bool isImport) : base(name, price, isImport) { }
    }

    public class Other : Item
    {
        public Other(string name, decimal price, bool isImport) : base(name, price, isImport) { }
    }

    public abstract class SalesTax
    {
        protected string Description { get; }
        protected decimal Rate { get; }
        protected IList<Type> ExemptionsList { get; }

        protected SalesTax()
        {
            ExemptionsList = Exemptions.GetExemptionsList();
            Rate = TaxRateAndDescription.GetTaxRate(this);
            Description = TaxRateAndDescription.GetTaxDescription(this);
        }

        protected bool IsImport(Item item)
        {
            return item.IsImport;
        }

        public abstract decimal CalculateTax(Item item, int quantity);

        public override string ToString() => $"Description: {Description} -  Rate {Rate}";
    }

    public class BasicTax : SalesTax
    {
        public override decimal CalculateTax(Item item, int quantity)
        {
            return ExemptionsList.Contains(item.GetType()) ?
                0m : decimal.Round(item.Price * quantity * Rate, 2, MidpointRounding.AwayFromZero);
        }
    }

    public class ImportTax : SalesTax
    {
        public override decimal CalculateTax(Item item, int quantity)
        {
            return IsImport(item) ?
                decimal.Round(item.Price * quantity * Rate, 2, MidpointRounding.AwayFromZero) : 0m;
        }
    }

    public abstract class ItemsRepository
    {
        public abstract IList<Item> Getproducts();
    }

    public class SqlRepository : ItemsRepository
    {
        public override IList<Item> Getproducts()
        {
            return new List<Item>
            {
                new Other("imported bottle of perfume", 27.99m, true),
                new Other("bottle of perfume", 18.99m, false),
                new Medical("packet of headache pills", 9.75m, false),
                new Food("box of imported chocolates", 11.25m, true)
            };
        }
    }

    public class TaxRateAndDescription
    {
        public static decimal GetTaxRate(SalesTax tax)
        {
            switch (tax.GetType().Name)
            {
                case "BasicTax":
                    return 0.1m;
                case "ImportTax":
                    return 0.05m;
                default:
                    return 0.1m;
            }
        }

        public static string GetTaxDescription(SalesTax tax)
        {
            switch (tax.GetType().Name)
            {
                case "BasicTax":
                    return "BasicTax";
                case "ImportTax":
                    return "ImportTax";
                default:
                    return "BasicTax";
            }
        }
    }

    public class Exemptions
    {
        public static List<Type> GetExemptionsList()
        {     
            return new List<Type>
            {
                typeof(Book),
                typeof(Food),
                typeof(Medical)
            };
        }
    }

    public class PrintReceipt
    {
        public static void PrintToScreen(string debug, ConsoleColor color)
        {
            Console.ForegroundColor = color;
            Console.WriteLine(debug);
        }
    }
}


评论


\ $ \ begingroup \ $
这个答案不是对已发布代码的评论。您指出了所发布代码中的错误,并提供了替代实现。另一种实现方式对于证明答案中解释的要点非常有用。但是,它作为主要功能很少有用。您需要解释有关已发布代码的更多信息。请阅读帮助中心内的操作指南页面。
\ $ \ endgroup \ $
– janos
16年4月19日在8:01



\ $ \ begingroup \ $
谢谢janos的回答,您完全正确。对我来说,重新编写代码要容易得多,因为对我而言,复查代码将花费很长时间。谢谢
\ $ \ endgroup \ $
–AngelBlueSky
16年4月19日在8:41

\ $ \ begingroup \ $
您的答案比原来的要好得多。删除后,它是一个代码转储,没有任何说明。添加说明可以改善这一点。我看到两个代码检查点:1.您指出一个错误,并且2.建议重新组织代码以使其可测试。您的方向很好,您有我的投票权;-)
\ $ \ endgroup \ $
– janos
16年4月19日在8:51

\ $ \ begingroup \ $
非常感谢,有时我没有很多时间来编写全部内容,但我知道如果多评论会更好。
\ $ \ endgroup \ $
–AngelBlueSky
16年4月19日在8:59