求高手优化面向对象设计问题
最近有高手给我和朋友出题,题目听上去不难,不过我和朋友作出的答案不能完全符合高手的要求,高手还嘲笑我们的设计非常地不面向对象,但又不给解释。百思不得其解,来此求救。以下是高手的问题,为抛砖引玉,将我们的答案一并列出。要求如下:
1。设计一个简化的财务系统,要求根据公司毛收入,税收,捐献,确定其净收入。要求提供本月份的毛收入,税收,捐献,净收入,及年度至今的毛收入,税收,捐献,净收入。
2。净收入 = 毛收入 - 税收 - 捐献。 税收依收入分四档,自行确定税率。 捐献也依收入分四档,自行确定比率,计算方法类似税收。如果本年度捐献超过一个数额(最大捐献额),停止捐献。
3。使用C#和面向对象设计。
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 Salary.");
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
= accountingCalculator.CalcNetIncome(oneCompany.GrossIncomeThisMonth, oneCompany.FundSupportYearToDate);
// add company income for period to year to date for company
= +
}
}
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 salary 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 salary 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 salary, 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 salary to be paid
return (grossIncomeThisMonth - tax - fund);
}
}
}
在计算税率时,还是使用了if/else.