最近有高手给我和朋友出题,题目听上去不难,不过我和朋友作出的答案不能完全符合高手的要求,高手还嘲笑我们的设计非常地不面向对象,但又不给解释。百思不得其解,来此求救。以下是高手的问题,为抛砖引玉,将我们的答案一并列出。

要求如下:
1。设计一个简化的财务系统,要求根据公司毛收入,税收,捐献,确定其净收入。要求提供本月份的毛收入,税收,捐献,净收入,及年度至今的毛收入,税收,捐献,净收入。 
2。净收入  毛收入  税收  捐献。 税收依收入分四档,自行确定税率。 捐献也依收入分四档,自行确定比率,计算方法类似税收。如果本年度捐献超过一个数额(最大捐献额),停止捐献。
3。使用#和面向对象设计。
4。至少提供两个界面:财务界面,和收入计算界面。财务界面只提供一种方法: 计算财务; 收入计算提供三种方法:计算税收,计算捐献,计算净收入。计算财务可以提供本月份的毛收入,税收,捐献,净收入,及年度至今的毛收入,税收,捐献,净收入。计算财务的输入参数是一个公司的list.
5。收入计算界面和基于它的类会被其它系统使用,设计时必须注意软件重用问题。
6。税率和捐献比率及最大捐献额有可能改变。
7。尽量减少使用条件判断(if/else, switch/case)来计算税收和捐献。
8。为简化起见,不用考虑数据库和数据存储。假定数据已经存在。
9。其余自行决定。但系统必须简单易懂。程序中使用英文注释,非关键点可以不注释。

以下是我们的解答,共五个文件:
1. Company.cs  
using System.Diagnostics;

namespace XCompany.Accounting.Service
{
    public class Company
    {
        //In general, OOP prefers private and protected fields than public fields
        private decimal grossIncomeThisMonth = 0.00m; //Optional: provide a default value

        //Constrcutor
        public Company() 
        { 
        }

        //By implementing property, it will be easier to modify this class in the future.
        //For example, if we decide to use different data type, or add more constraints etc.

        //Property Name, auto implement for get/set
        public string Name { get; set; }

        //Property GrossIncomeThisMonth. 
        public decimal GrossIncomeThisMonth
        {
            get { return grossIncomeThisMonth; }
            set
            {
                if (value < 0.00m)
                    Trace.WriteLine("Invalid value for Income.");
                else
                {
                    grossIncomeThisMonth = value;

                }
            }
        }
        //Property NetIncomeThisMonth, auto implement for get/set
        public decimal NetIncomeThisMonth { get; set; }
        //Property FundSupportThisMonth, auto implement for get/set
        public decimal FundSupportThisMonth { get; set; }
        //Property TaxThisMonth, auto implement for get/set
        public decimal TaxThisMonth { get; set; }

        // year to date 
        //Property GrossIncomeYearToDate, auto implement for get/set
        public decimal GrossIncomeYearToDate { get; set; }
        //Property NetIncomeYearToDate, auto implement for get/set
        public decimal NetIncomeYearToDate { get; set; }
        //Property FundSupportYearToDate, auto implement for get/set
        public decimal FundSupportYearToDate { get; set; }
        //Property TaxYearToDate, auto implement for get/set
        public decimal TaxYearToDate { get; set; }

    }
}

2. IAccounting.cs
using System.Collections.Generic;

namespace XCompany.Accounting.Service
{
    // USE THIS INTERFACE
    public interface IAccounting
    {
        List<Company> DoAccounting(List<Company> companyList); 
    }
}

3. Accounting.cs
using System.Collections.Generic;
using System.Diagnostics;


namespace XCompany.Accounting.Service
{
    public class Accounting: IAccounting
    {
        private readonly ICalc accountingCalculator;
        private List<Company> companies;

        public Accounting()
        { 
            //In case the tax rates change, modify the following array accordingly.
            //Define an array of various Income Levels and tax rates, and fund contribution rates accordingly
            decimal[,] incomeTaxNew = new decimal[3,4]{ {0.00m, 2000.00m, 3000.00m, 4000.00m}, {5.00m, 6.00m, 8.00m, 10.00m}, {0.00m, 1.00m, 2.00m, 3.00m} };
            decimal maxFund = 2000.00m;

            accountingCalculator = new Calculator(incomeTaxNew, maxFund);
            
           
            //If the tax rates and fund contribution rates keep intact, OK to use the default constructor. 
            //accountingCalculator = new Calculator();    
        }

        //Property Companies
        public List<Company> Companies
        {
            get { return companies; }
            set { companies = value; }
        }

        public List<Company> DoAccounting(List<Company> companyList)
        {
            try
            {
                Companies = companyList;

                foreach (Company oneCompany in Companies)
                {
                    // calcualte the company taxes for accounting
                    oneCompany.TaxThisMonth = accountingCalculator.CalcTax(oneCompany.GrossIncomeThisMonth);
                    // add company taxes to year to date for company
                    oneCompany.TaxYearToDate = oneCompany.TaxYearToDate + oneCompany.TaxThisMonth;

                    // calculate company fund contributions 
                    oneCompany.FundSupportThisMonth = accountingCalculator.CalcFundSupport(oneCompany.GrossIncomeThisMonth, oneCompany.FundSupportYearToDate);
                    // add company contributions to year to date for company
                    oneCompany.FundSupportYearToDate = oneCompany.FundSupportYearToDate + oneCompany.FundSupportThisMonth;

                    // calculate company net income for period
                    oneCompany.NetIncomeThisMonth = accountingCalculator.CalcNetIncome(oneCompany.GrossIncomeThisMonth, oneCompany.FundSupportYearToDate);
                    // add company income for period to year to date for company
                    oneCompany.NetIncomeYearToDate = oneCompany.NetIncomeYearToDate + oneCompany.NetIncomeThisMonth;

                }
            }
            catch (System.ApplicationException ex)
            {
                Trace.WriteLine(ex.ToString() );
            }
            catch (System.Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }

            return Companies;
        }        
    }
}

4. ICalc.cs
namespace XCompany.Accounting.Service
{
    public interface ICalc
    {
        decimal CalcTax(decimal grossIncomeThisMonth);
        decimal CalcNetIncome(decimal grossIncomeThisMonth, decimal fundSupportYearToDate);
        decimal CalcFundSupport(decimal grossIncomeThisMonth, decimal fundSupportYearToDate);
    }
}

5. Calculator.cs
using System.Diagnostics;

namespace XCompany.Accounting.Service
{
    // This class determines the amount of deductions to be made for  
    // company based on their income.  Depending on the amount of 
    // income will determine the tax rate. 
    // The company will donate to a fund based on the amount of income,
    // similar to the tax rate determination. However, once a maximum 
    // amount has been reached, the company will stop donation.
    public class Calculator : ICalc
    {
        // local variables

        //In case the tax rates change in the new year, modify the following array accordingly.
        //Define an array of various Income Levels and tax rates, and fund contribution rates accordingly
        public readonly decimal[,] IncomeTax = new decimal[3, 4] { { 0.00m, 2000.00m, 3000.00m, 4000.00m }, { 5.00m, 6.00m, 8.00m, 10.00m }, { 0.00m, 1.00m, 2.00m, 3.00m } };
        public readonly decimal MaxFund = 2000.00m;

        // constructor. If no parameters are provided in the calling function, the constructor will use default tax rates.
        public Calculator(decimal[,] _incomeTax = null, decimal _maxFund = 2000.00m)
        { 
           if (_incomeTax != null)
               IncomeTax = _incomeTax;

           MaxFund = _maxFund; 
        }

        //Create a custom exception class based on System.ApplicationException.
        //It is a good practice to define a custom exception class for a specific application exception.
        public class InvalidInputException : System.ApplicationException
        {
            // The default constructor needs to be defined
            // explicitly now since it would be gone otherwise.
            public InvalidInputException()
            {
            }

            public InvalidInputException(string message)
                : base(message)
            {
            }
        }


        /*
         * class methods 
         */
        // The gross income determines the tax rate
        public decimal GetTaxRate(decimal grossIncomeThisMonth)
        {
            decimal taxRate = 0.00m;

            try
            {
                //It is not the way that CRA calculates our tax. However, since it is a demo, let's follow the way of the sample.
                if (grossIncomeThisMonth >= IncomeTax[0, 3])
                {
                    taxRate = IncomeTax[1, 3];
                }
                else if (grossIncomeThisMonth >= IncomeTax[0, 2])
                {
                    taxRate = IncomeTax[1, 2];
                }
                else if (grossIncomeThisMonth >= IncomeTax[0, 1])
                {
                    taxRate = IncomeTax[1, 1];
                }
                else if (grossIncomeThisMonth >= IncomeTax[0, 0])
                {
                    taxRate = IncomeTax[1, 0];
                }
                else
                {
                    //Report Error: Invalid Income amount.
                    throw new InvalidInputException("Error: Invalid gross income amount.");

                }
            }
            catch (InvalidInputException ex)
            {
                Trace.WriteLine(ex.ToString());
            }
            catch (System.ApplicationException ex)
            {
                Trace.WriteLine(ex.ToString());
            }
            catch (System.Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }

           return taxRate;
        }

        // The gross income determines the fund rate. 
        public decimal GetFundRate(decimal grossIncomeThisMonth, decimal fundSupportYearToDate)
        {
           decimal fundRate = 0.00m;

           try
           {   //If it exceeds the maximum amount, stop contribution
               if (fundSupportYearToDate >= MaxFund)
                   return 0.00m;

               //Calculates rates based on income
               if (grossIncomeThisMonth >= IncomeTax[0, 3])
               {
                   fundRate = IncomeTax[2, 3];
               }
               else if (grossIncomeThisMonth >= IncomeTax[0, 2])
               {
                   fundRate = IncomeTax[2, 2];
               }
               else if (grossIncomeThisMonth >= IncomeTax[0, 1])
               {
                   fundRate = IncomeTax[2, 1];
               }
               else if (grossIncomeThisMonth >= IncomeTax[0, 0])
               {
                   fundRate = IncomeTax[2, 0];
               }
               else
               {
                   //Report Error: Invalid Income amount.
                   throw new InvalidInputException("Error: Invalid gross income amount.");

               }
           }
           catch (InvalidInputException ex)
           {
               Trace.WriteLine(ex.ToString());
           }
           catch (System.ApplicationException ex)
           {
               Trace.WriteLine(ex.ToString());
           }
           catch (System.Exception ex)
           {
               Trace.WriteLine(ex.ToString());
           }

           return fundRate;
        }


        // This method takes gross income, and returns tax.
        virtual public decimal CalcTax(decimal grossIncomeThisMonth)
        {
            // declare and initialize variables
            decimal taxRate = 0.00m;
            decimal taxAmount = 0.00m; 

            // determine the Tax Rate to use
            taxRate = GetTaxRate(grossIncomeThisMonth);
            // calculate the tax amounts
            taxAmount = (grossIncomeThisMonth * taxRate) / 100.00m;
         
            // return the tax amount
            return taxAmount;
        }

        // This method takes gross income, and returns fund contribution.
        // Once the fund reaches the maximum amount, stop contribution.
        virtual public decimal CalcFundSupport(decimal grossIncomeThisMonth, decimal fundSupportYearToDate)
        {
            // declare and initialize variables
            decimal fundRate = 0.00m;
            decimal fundAmount = 0.00m;

            // determine the Tax Rate to use
            fundRate = GetFundRate(grossIncomeThisMonth, fundSupportYearToDate);
            // calculate the tax amounts
            fundAmount = (grossIncomeThisMonth * fundRate) / 100.00m;

            //If it exceeds the maximum amount, stop contribution
            if ( (fundAmount+ fundSupportYearToDate) > MaxFund)
                fundAmount = MaxFund - fundSupportYearToDate;

            // return the contributed fund amount
            return fundAmount;
        }


        // This method takes a Income, and return the net income
        virtual public decimal CalcNetIncome(decimal grossIncomeThisMonth, decimal fundSupportYearToDate)
        {
            // declare and initialize variables
            decimal tax = CalcTax(grossIncomeThisMonth);

            // calculate the fund support
            decimal fund = CalcFundSupport(grossIncomeThisMonth, fundSupportYearToDate);

            // return the Income 
            return (grossIncomeThisMonth - tax - fund);
        }       
    }
}

在计算税率时,还是使用了if/else.