最近我在尝试一家公司'x'。他们给我发了一些问题,告诉我只解决一个问题。
问题是这样的 -
基本销售税适用于所有商品的10%税率,但免税的书籍,食品和医疗产品除外。
进口税是适用于所有进口商品的额外销售税,税率为5%,不含豁免。
当我购买物品时,我会收到一张收据,上面列出了所有物品的名称及其价格(含税),最后是物品的总成本,以及所支付的销售税总额。
销售税的舍入规则是对于n%的税率,p的货架价格包含(np / 100四舍五入到最接近的0.05)销售税金额。
“他们告诉我,他们对您的解决方案的设计方面感兴趣,并希望评估我的面向对象的编程技巧。”
这是他们用自己的话说的话
所以我提供了以下代码 - 您只需复制粘贴代码并在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申请也不会有效。
由于
答案 0 :(得分:243)
首先关闭好天堂不要进行双重财务计算。以十进制进行财务计算;这就是它的用途。使用double来解决 physics 问题,而不是财务问题。
您的计划中的主要设计缺陷是政策位于错误的地方。谁负责计算税收?您已经产品负责计算税款,但是当您购买苹果或书籍或洗衣机时,您要购买的东西不是负责告诉你要支付多少税款。 政府政策负责告诉您。您的设计大量违反了基本的OO设计原则,对象应该对自己的关注负责,而不是其他任何人。洗衣机的问题是洗衣服,而不是收取正确的进口税。如果税法变更,您不想更改洗衣机对象,您想要更改策略对象。
那么,将来如何解决这些问题?
我首先要强调问题描述中的每个重要名词:
基本销售税适用于所有商品的率 10%,图书,<强健>食物,以及医疗产品免税。 进口税是一项额外的销售税,适用于所有进口商品,费率为5%,没有<强>豁免即可。当我购买商品时,我会收到收据,其中列出了所有商品的名称及其价格(包括税),完成商品的总费用,以及支付的销售税总额。 销售税的舍入规则是税率为n%,p的货架价格包含(np / 100四舍五入到最接近的0.05)金额<强>销售税。
现在,所有这些名词之间的关系是什么?
......等等。一旦你掌握了所有名词之间的所有关系,你就可以开始设计一个类层次结构了。有一个抽象基类Item。本书继承自它。有一个抽象类SalesTax; BasicSalesTax继承自它。等等。
答案 1 :(得分:38)
如果公司对NUnit,JUnit或Test :: Unit这样的库有所了解,那么TDD实际上很可能是它们的重要组成部分。在您的代码示例中根本没有测试。
我会尝试展示以下方面的实用知识:
我想推荐www.dimecasts.net作为令人印象深刻的免费,优质网络广播来源,涵盖上述所有主题。
答案 2 :(得分:19)
这是非常主观的,但以下是我对您的代码所做的几点:
在我看来,你混合了Product
和ShoppingCartItem
。 Product
应该包含产品名称,纳税身份等,但不包括数量。数量不是产品的属性 - 对于购买该特定产品的公司的每个客户而言,数量都不同。
ShoppingCartItem
应该有Product
和数量。这样,客户可以自由购买更多或更少的同一产品。使用您当前的设置是不可能的。
计算最终税也不应该是Product
的一部分 - 它应该是ShoppingCart
之类的一部分,因为最终的税收计算可能涉及了解购物车中的所有产品。
答案 3 :(得分:14)
首先,这是一个非常好的面试问题。这是许多技能的良好衡量标准。
你需要了解很多东西才能提供一个好的答案(没有完美的答案),无论是高级还是低级。这是一对夫妇:
从那里,您可以进行许多有趣的讨论,包括设计原则(如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(方法,函数,子程序,根据需要调用它):例如,产品对象不应该计算销售税。 销售引擎对象应该。
不要对所有大字(接口,属性,多态,传统感到烦恼,等)和第一次设计模式,甚至没有尝试制作漂亮的代码或其他任何东西......只要想到简单的对象和之间的相互作用它就像在现实生活中一样。
之后,尝试阅读一些关于此的严肃简洁的文章。我认为Wikipedia和Wikibooks是一种非常好的开始方式,然后只需阅读有关GoF and Design Patterns和UML的内容。
答案 6 :(得分:4)
首先不要将Product
类与Receipt(ShoppingCart
)类混合使用,quantity
也应该是ReceipItem
(ShoppingCartItem
)的一部分为Tax
和Cost
。 TotalTax
和TotalCost
应该是ShoppingCart
的一部分。
我的Product
类只有Name
和Price
以及一些只读属性,例如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
耦合。产品未定义税收政策,而是税收类别。根据问题的描述,有两种营业税:Basic
和Duty
税。您可以使用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等。
Product
可以是一个抽象类,像Books,Food这样的派生类型可以从它继承。税收适用性可以由派生类型决定。产品将根据派生类判断税是否适用。
TaxCriteria
可以是枚举,可以在购买时指定(导入,销售税适用性)。
Tax
课程将根据TaxCriteria
计算税金。
根据XXBBCC建议的ShoppingCartItem
可以封装产品和税务实例,这是将产品详细信息与数量,总价格和税收等分开的好方法。
答案 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原则指出,如果要添加新功能,则不必向现有类添加代码,而是添加新类。
对于您的示例应用程序,这意味着添加新的销售税将需要添加新类。对于规则例外的不同产品也是如此。
舍入规则显然属于单独的类 - 单一责任原则规定每个班级都有一个责任。
我认为尝试自己编写代码会带来更多的好处,而不仅仅是编写一个好的解决方案并将其粘贴在这里。
编写完美设计程序的简单算法是:
答案 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);
}