编写一个程序以提要价格。如果物品是书籍,食品或
医疗产品,则无需缴税,如果没有,则需缴纳10%的税(其他
物品)。如果该商品是进口商品,则需缴纳5%的税(
可以进口15%税(10 + 5)的进口商品和“其他商品”类型的商品)。
该程序需要打印这样的收据...
书:9.12(2 @ 4.56)
进口狗粮:6.52
香水: 6.23
营业税:1.65
总计:21.87
在这种情况下,您可以看到应用程序可以将项目“分组”并提供税收计算,包括总税。
要描述一下,我的应用程序有一个菜单,继续提示用户输入信息,直到他们退出菜单。产品信息已放入
List
中。将针对列表写入一个lambda表达式以分组并获取重复项。嵌套在if
语句中的foreach
语句会触发税收计算以打印出收据。公司表示这“不是面向对象的”。因此,我距离还很遥远,或者可以进行一些调整以满足他们的要求。无论如何,我在2天(限时)内完成了工作。如何编写这些代码以遵循OOP概念?我不是要有人为我“重写”此内容。我认为我有正确的心态。我只需要有人向正确的方向“轻推”我即可。
#1 楼
我猜他们正在寻找的是类上的方法。另外,我建议您下载resharper并查看其对命名约定的说明,因为它们不是标准的。我称之为“经典OO”的方法可能看起来有点像
即使没有真正的要求,继承一些继承也不会受到损害。这些面试测试的目的是向您证明您了解一些东西。它假定您将能够解决问题。
作为指导,我总是提供以下内容:
单元测试-不能太强调这一点!名称匹配测试用例,例如。
givenXThenY(TDD)
名称与业务术语(DDD)匹配的类
这些类(OO)上的方法
具有业务逻辑(SoA)的服务类
一切接口(更多的OO / DI)也显示了继承性
知识*
通过接口(DI)将服务注入到构造函数中
ViewModel对象或其他一些显示分离(MVVM) )
存储库对象(存储库模式/数据层分离)
至少使用一种通用集合
至少使用一种Linq
XML注释所有内容(代码可读性)
描述其功能的方法名称(干净编码)
每个解决方案有多个项目(不包括测试项目)
名称项目,如名称空间,例如YourCompany.Example5.Models
>如果您认为问题有待解决,可以投入其他内容
抽象类/更多继承
模数运算符%的一些测试角度这个,显示哟你知道
数学吗?
异步方法和任务,多线程
*当我说所有内容时,我指的是服务和模型,实际上您并不需要两者,但是有些人喜欢彼此。这些事情很多都是矛盾的,或者是模式的两倍。关键不是编写好的代码,它表明您了解许多模式和编码实践等
评论
\ $ \ begingroup \ $
如果您删除了有关“一切”接口的评论,您将获得+1 :)
\ $ \ endgroup \ $
– RobH
2015年5月1日在8:51
\ $ \ begingroup \ $
从来没有! :)(尽管我已经详细说明了,也许我可以得到1/2分吗?)
\ $ \ endgroup \ $
–伊万
2015年5月1日晚上8:58
\ $ \ begingroup \ $
这就是为什么删除“分配”很糟糕的原因。它评估候选人是否能够对或多或少的琐碎问题应用尽可能多的“良好实践”模式。它完全没有说明应聘者对这些模式的理解,解决问题的能力,标准制定方面的经验,业务要求等。
\ $ \ endgroup \ $
–miki
2015年5月1日18:27
\ $ \ begingroup \ $
...如果您未进行真正的TDD,则绝对不要将其称为TDD。很明显,“ TDD”是“写代码,然后测试”给已经完成TDD一段时间的人的。
\ $ \ endgroup \ $
– enderland
2015年5月1日23:49
\ $ \ begingroup \ $
许多要点与“面向对象设计”无关。当然,他们会展示语言知识。但是要当心:不适当或任意使用语言功能表明编程技能很差。
\ $ \ endgroup \ $
– radarbob
15年5月4日在5:04
#2 楼
我认为他们可能正在寻找您来将职责划分为多个类。您创建了
product
类来封装数据,但是到此为止。您已将所有与UI(编写/读取控制台)有关的内容塞进去,并在主Program
类中计算税额。 > public class TaxCalculator
{
public decimal CalculateTax(Product product)
{
// Some code - everyone loves political humour...
return isMultinationalOrganisation ? 0 : 10000m;
}
}
除此之外,还有一些样式点可以依靠一些工作来完成:
product
应该是Product
。方法也应该是Pascal情况
customRound
应该是CustomRound
。customRound
到底在干什么。混合使用并杀死一只小狗。 private decimal _Total_tax;
public decimal Total_tax
{
get
{
return this._Total_tax;
}
set
{
this._Total_tax = value + Total_tax;
}
}
字段应为驼峰式。无论是
string.Format
还是totalTax
。保持连续运行的设置器不是一个好主意。考虑我的示例:
TotalTax = 10;
TotalTax = 15;
TotalTax == 15; // False!?!?!?!?
你不需要写:
if (is_import == true) {
你可以写:
if (is_import) {
断开键盘上的下划线键。只需将camelCase用作变量即可。
编辑
我认为说它不是非常面向对象的另一个原因之一是您的CalcTax方法(应为CalculateTax)接受一个参数列表,如果可以,最好使用
_totalTax
对象。评论
\ $ \ begingroup \ $
我认为这最好地体现了责任分离。计算自己的税金实际上不应该是产品的工作,但是将税金计算通常与用户交互隔离到一个单独的类中很有意义。这样可以使税费计算的单元测试与UI分离,并隔离税费计算(也许将来会转移到远程运行的服务中,该服务管理更复杂的规则,并经常通过税率变化进行集中更新?)
\ $ \ endgroup \ $
–丹·布莱恩特
2015年5月4日13:17
#3 楼
该公司可能正在寻找能证明面向对象建模的代码。您有产品对象,但是描述中也提到了税收和收据。更完整的模型可能如下所示:重要的是(其他审阅者/评论者也要注意):在此模型中,ProductTax
对象负责确定它们是否适用于给定的产品,并且“产品”不了解“税”。通过ReceiptCalculator的Taxes属性可以在计算中添加/删除税款,并且可以使用不同的税收考虑因素(例如,针对不同的州或国家)创建不同的ReceiptCalculators。基于税收分类不是一个好方法。如果诸如
IsImport
和IsBookFoodOrMedical
之类的标志属性经常更改或增长为太多,则最好将其概括为某种标志类型的ISet<...>
属性。在真实的系统中,您不需要每次引入或更改税费都必须更改类层次结构;您想通过更改数据来解决。如果引入了需要不同分类的新税种,那么您要做的就是标记适用的产品并创建ProductTax实例来查找它。 (实施此操作将是值得进行的练习。)评论
\ $ \ begingroup \ $
我将使用枚举ProductType而不是布尔值IsFoodBookOrMedical。
\ $ \ endgroup \ $
– ANeves认为SE是邪恶的
15年7月22日在13:03
\ $ \ begingroup \ $
Microsoft惯例是接口名称应以大写I开头,例如IProduct,IReceiptItem等。
\ $ \ endgroup \ $
–里克·戴文(Rick Davin)
15年7月22日在14:29
#4 楼
我要坦率地说,因为我认为这对您找工作最有好处。您说自己“有正确的心态”,“只是需要有人朝正确的方向'推'[您]”。您可以领先大多数产品,因为您的功能比FizzBuzz难得多。但是,此代码需要大量工作和大量重组。学习C#的文化将需要时间,而有效地学习OO原理也将需要时间。如果我是您,那么我会在空闲时间开始用C#编写很多OO代码并将其发布到codereview。这是C#。您不需要在字符串上调用
.Equals
。相反,请执行以下操作:if (men1 == "0")
您不需要
? true : false
或多余的括号。取而代之的是:is_other = men1.Equals("2");
正如其他人所说,您并没有遵循C#命名约定:
用于局部变量,第一个单词小写,之后的每个单词的首字母大写。不使用下划线。 (例如
isImport
)方法名,类名和属性名都以大写字母开头,与本地语言有所不同。没有任何东西可以访问
TotalTax
文件,因此,如果要访问该文件,也可能只是一个实例变量:private decimal _TotalTax;
如果需要添加到它....
_TotalTax += ...;
在
Program
中,您执行的操作与else中的很多操作相同。同样,if (item.count > 1)
的格式字符串有重载。使用那个而不是将一串字符串和变量连接在一起。此外,当它们全部放在一行上时,很难阅读:Console.Write("{0}: {1:0.00}",
item.prod_name,
CalcTax(item.sum, item.is_other, item.is_import));
if (item.count > 1)
Console.Write(" ({0} @ {1})", item.count, item.prod_price);
Console.WriteLine();
Console.WriteLine
返回价格加税。如果要这样做,我将其命名为CalcTax
。该方法也有很多重复之处。找出不重复的部分,然后首先执行:public decimal AddTax(decimal price, bool is_other, bool is_import)
{
int percentage = 0;
if (is_other) percentage += 10;
if (is_import) percentage += 5;
var tax = price * percentage / 100;
Total_Tax = tax; //have to because of the value + in the property
return price + tax;
}
我删除了
AddTax
,因为我认为这不是必需的。 我还将检查输入是否格式错误,因为
customRound
可能因错误而失败,并且Convert.ToDecimal
可能不是men1
,0
和1
。在进入该行业之前,如果您按照程序员的意图使用它,就会遇到一些不稳定的UI。有很多方法可以滥用它并破坏程序。我在工作中学到的第一件事就是那不会飞起来。 Ewan的回答对于OO原理非常有用,因此我将忽略它。
#5 楼
使用对象与进行面向对象的编程不同。您的代码具有很高的过程性,并且没有利用面向对象设计的固有灵活性。您应该在此处应用的主要概念是,您应该封装各种变化。您可以采用两种方法来实现此目的:装饰或注入。装饰
我们可以从装饰的角度看待这一问题。我们有一个基础对象,然后将其附加到其他行为上。装饰器模式是用于在基础对象上分层附加行为的一种很好的模式。我们可以分解每个概念并将其封装到代表该概念的对象中。 (我的C#可能不是完全惯用,但它应该可以工作并举例说明该概念)。首先,我们需要一个产品:
public class Product
{
public string Name { get; private set; }
protected decimal Price { get; private set; }
public Product(string name, decimal price) {
Name = name;
Price = price;
}
public decimal GetPrice() {
return Price;
}
// Since we are taxing things, we probably would want the original price too.
public decimal GetBasePrice() {
return Price;
}
}
现在要使用Decorator模式,我们需要提取一个接口:
public class IProduct
{
string Name { get; }
decimal GetPrice();
decimal GetBasePrice();
}
我们将接口添加到
Product
类中:class Product : IProduct { ...
现在,我们封装了产品的概念。我们没有涉及任何税费或进口费,因为它们是完全独立的概念,并且各不相同。由于我们封装了变化,因此我们知道应该将它们分开。现在,让我们为需要征税的
Product
创建一个装饰器: class TaxableProduct : IProduct
{
const decimal TAX_RATE = 0.10m;
public string Name { get { return BaseProduct.Name; } }
private IProduct BaseProduct { get; set; }
public TaxableProduct(IProduct baseProduct) {
BaseProduct = baseProduct;
}
public decimal GetPrice() {
return BaseProduct.GetPrice() + BaseProduct.GetBasePrice() * TAX_RATE;
}
public decimal GetBasePrice() {
return BaseProduct.GetBasePrice();
}
}
现在我们可以创建未征税,未进口的产品:
class ImportedProduct : IProduct
{
const IMPORT_FEE = 0.05m;
public string Name { get { BaseProduct.Name } }
private IProduct BaseProduct { get; set; }
public ImportedProduct(IProduct baseProduct) {
BaseProduct = baseProduct;
}
public decimal GetPrice() {
return BaseProduct.getPrice() + BaseProduct.GetBasePrice() * IMPORT_FEE);
}
public decimal GetBasePrice() {
return BaseProduct.GetBasePrice();
}
}
新的征税,未进口的产品: >
new Product("Milk", 2.50m);
新进口的免税产品:
new TaxedProduct(new Product("Wrench", 8.00m));
新进口的免税产品:
new ImportedProduct(new Product("French cheese", 25.00m));
我们能够在运行时将产品及其税费和进口费计算组合在一起,同时通过
ImportedProduct
界面使其透明。我们已经将应纳税和进口状态的变化行为封装到它们自己的对象中,并且它们可以独立变化。如果出现另一种费用,我们可以添加一个新的装饰器来封装它。 (顺便说一句,此代码中有重复项,可以很容易地将其排除。)注入
我们可以从注入的角度看待这个问题。我们有一个具有某种行为的对象,并且我们希望将更多行为推入其中。我们可以使用依赖注入将这种行为推入对象。我们可以分解每个概念并将其封装到表示该概念的对象中(我的C#可能不是完全惯用的,但它应该可以工作并举例说明该概念)。首先,我们需要一种产品:
new ImportedProduct(new TaxedProduct(new Product("Ferrari", 250000.00m)));
现在,税款和进口费都是产品的费用,所以让我们创建一个接口来封装费用的概念:
class Product
{
public string Name { get; private set; }
private decimal Price { get; set; }
public Product(string name, decimal price) {
Name = name;
Price = price;
}
public decimal GetPrice() {
return Price;
}
}
现在让我们创建两个收费实现,
IProduct
:interface IFee
{
decimal CalculateFee(decimal price);
}
和
TaxFee
:class TaxFee : IFee
{
const decimal TAX_RATE = 0.10m;
public decimal CalculateFee(decimal price) {
return price * TAX_RATE;
}
}
我们希望将行为注入到产品类中,最简单的方法是通过构造函数传递参数。由于我们要传递任意数量的费用,因此我们可以传递一个
ImportFee
:class ImportFee : IFee
{
const decimal IMPORT_FEE = 0.05m;
public decimal CalculateFee(decimal price) {
return price * IMPORT_FEE;
}
}
现在我们可以创建一个免税,未进口的产品:
class Product
{
public string Name { get; private set; }
private decimal Price { get; set; }
private IEnumerable<IFee> Fees { get; set; }
public Product(string name, decimal price) : this(name, price, new List<IFee>()) {
}
public Product(string name, decimal price, IEnumerable<IFee> fees) {
Name = name;
Price = price;
Fees = fees;
}
public decimal GetPrice() {
decimal fullPrice = Price;
foreach(IFee fee in Fees) {
fullPrice += fee.CalculateFee(Price);
}
return fullPrice;
}
}
一种新的免税进口产品:
new Product("Milk", 2.50m);
一种新的免税进口进口产品:
List<IFee> fees = new List<IFee>();
fees.Add(new TaxFee());
new Product("Wrench", 8.00m, fees);
新征税的进口产品:
List<IFee> fees = new List<IFee>();
fees.Add(new ImportFee());
new Product("French cheese", 25.00m, fees);
我们能够在运行时将产品及其税费和进口费计算组合在一起,同时通过
IEnumerable<IFee>
界面使其透明。我们已经将应纳税和进口状态的变化行为封装到它们自己的对象中,并且它们可以独立变化。如果出现其他类型的费用,我们可以添加一个新的IFee
对其进行封装。 (顺便说一句,此代码中有重复项,可以很容易地将其排除。)#6 楼
答案中的树太多各个答案中都有很多有效的点,但它大部分都错过了大的OO森林。遵循OOP概念?
创建连贯的类。
一致的类具有适当的属性来描述/定义它是什么,并具有适当的方法来描述/定义它的作用。无论具体的总体实现方式如何,这个设计目标都会发挥作用。
OP
static void Main()
会执行Product
自己应该做的事情:发出其属性值-ToString()
;和CalculateTax()
。最小化耦合并最大化一致性
那么,为什么代码不是面向对象的呢?
我希望方法是“处于适当的抽象级别”。
main
应该读得非常高级。请计算Product
以计算税金。请不要对产品执行此操作。请对
Product
显示其详细信息,请不要对产品进行此操作。请对
ProductList
进行纳税计算。请不要为列表执行此操作。告诉
ProductList
以显示其详细信息。请勿为产品执行此操作。我们真的不在乎它是什么,而是它的税率是多少。 我不仅考虑树,链接列表等经典事物,而且我倾向于考虑创建任何使数据(类属性)更易于操纵的类。数据结构大大降低了代码复杂度。
税率字典-每个应税项目类型都有一个条目。并且taxable-item-type将是字典键。
taxable-item-type-一个
enum
显然,每个应税项目都有自己的税率。即使它与其他项目相同。
ProductList-该人员将知道如何打印列表,如何计算列表的总税以及如何在自身内部找到副本。再说一次,告诉[您的课程在这里],不要为[您的课程在这里]
ItemType
默认值为“无效”或“未知”或“未定义”非常非常有用。
税表
public static void main() {
bool continueEntry = true;
int userEntry = 0;
while(continueEntry) {
continueEntry = PrompUser(out userEntry);
if(!continueEntry) break;
CreateProduct(userEntry);
}
CalculateTax();
PrintResults();
}
具有适当责任的产品类别
我不想缠在轴上,关于
public
,private
,命名约定等。这与Object的较大点无关面向。public enum ItemType {Invalid, Book, Food, Medical, Import, Other}
具有适当责任的产品列表
public class TaxTable {
public static Dictionary<ItemType, decimal> Taxes = new Dictionary<ItemType, decimal>(){
{ItemType.Invalid, 0},
{ItemType.Book, 0},
{ItemType.Food, 0},
{ItemType.Medical, 0},
{ItemType.Import, .05},
{ItemType.Other, .10}
}
public static decimal GetTaxFor(ItemType) { // lookup in Taxes}
}
客户代码
这是面向对象的
public class Product {
ItemType WhatIAm {get; set;}
// EDIT: Add "imported" as a separate concept. See @cbojar comment
public bool Imported {get; protected set;}
// a product knows what a valid item code is
public Product(int itemType) {
// convert to an ItemType
// even if it is "Invalid" you can still have an Invalid
// product type with a tax of zero, price of zero, etc.
// Client code will be none the wiser.
// The ProductCollection could simply elect to ignore it.
}
public override ToString(){
StringBuilder me = new StringBuilder();
me.AppendFormat("WhatIAm : {0}", WhatIAm);
me.AppendFormat("Price : {0}", Price);
// etc.
return me.ToString();
}
// EDIT: added "imported" as an additional tax. See @cbojar comment
public decimal CalculateTax() {
decimal tax = 0.0;
tax = Price * TaxTable.GetTaxFor(WhatIAm);
tax = Imported ? tax + Price * TaxTable.GetTaxFor(ItemType.Imported) : tax;
}
public decimal TotalCost(){
return Price + CalculateTax();
}
}
面向对象的良好代码的气味
客户端告诉类做某事。
代码在适当的抽象级别上读取。
方法往往很短!
将细节“压入”适当的类中。 br />
定义和分配主体部分nct
数据结构。学习,生活,爱它。
避免不必要的耦合
任何人都可以使用
TaxTable
和ItemType
每个类都为自己做。我们不依赖其他类来做到这一点。
代码是可重用的。
类更改最好没有副作用。 >更改
Product
不会导致TaxTable
发生变化。对对象进行相同的处理。
Product.ToString()
产品没有特殊情况。客户代码将不知道或不在乎。 ProductList.ToString()
不能将无效的产品添加到您想要的列表中,但是即使这样,客户还是很无知的。 ,但到处都强调创建ItemType.Invalid
s太多了。评论
\ $ \ begingroup \ $
我发现您将面向对象代码的所需属性称为“气味”会产生误解。起初,我以为您将要列出那些标志性很差的面向对象代码的东西。
\ $ \ endgroup \ $
–mkrieger1
2015年5月4日9:36
\ $ \ begingroup \ $
我对这个答案的一个主要问题是,它不允许对产品实施实施征税和进口。创建另一个枚举值可能会解决此问题,但是如果税种数量增加,则会创建重复项并导致组合数字枚举。此外,如果税收的计算不仅仅涉及价格乘以价格(例如,对第一个$ x不征税,等等),则将需要不同的策略。
\ $ \ endgroup \ $
–cbojar
2015年5月4日14:57
\ $ \ begingroup \ $
@radarbob无需更改Product类以实现新的赋税算法,对我来说既不会感到封闭,也不会感到单一责任。我什至不知道您将如何更改Product类以适应此类更改。您是否将多个ItemTypes传递给构造函数?如果允许多个类型,您将如何处理传递的不兼容类型?
\ $ \ endgroup \ $
–cbojar
2015年5月4日15:54
\ $ \ begingroup \ $
@cbojar-如果可以导入任何(某种)项目,那么是的,我的示例未实现。并且感谢您有机会说明OO代码的“类更改避免副作用”属性。请参阅产品类别修改。
\ $ \ endgroup \ $
– radarbob
2015年5月4日15:56
\ $ \ begingroup \ $
@radarbob随着需求的变化,我们的应用程序应具有强大的变化能力。尽管可能需要更改设计,但是如果我能花相同的时间和精力来创建一个健壮的设计,即使付出额外的一两个接口的代价,我也会做出健壮的设计,因为当不可避免的变更发生时路,我会为此做好准备的。作为一个实际问题,税费一直在变化。产品可能也一直在变化。它们也可能独立变化,因此仅出于方便而将它们混在一起可能会很危险。
\ $ \ endgroup \ $
–cbojar
15年5月4日在16:23
#7 楼
“它不是面向对象的”是一个奇怪的回答。这会让我立即担心这家公司,因为它暗示他们过去生活,所以这里不是我想要工作的地方。就像您声明一个接口和类来处理每种税种。像这样:public interface IProduct
{
string Name { get; set; }
decimal Price { get; set; }
decimal Tax { get; }
bool Import { set; }
}
public class BookFoodOrMedical : IProduct
{
public string Name { get; set; }
public decimal Price { get; set; }
public decimal Tax
{
get { return customRound(Price * (Import ? 5 : 0) / 100); }
}
public bool Import { set; private get; }
}
public class Other : IProduct
{
public string Name { get; set; }
public decimal Price { get; set; }
public decimal Tax
{
get { return customRound(Price * (10 + (Import ? 5 : 0)) / 100); }
}
public bool Import { set; private get; }
}
然后,您的菜单方法最后更改为:
if (is_other)
{
prod_list.Add(new Other { Import = is_import, Name = prod_name, Price = prod_price });
}
else
{
prod_list.Add(new BookFoodOrMedical { Import = is_import, Name = prod_name, Price = prod_price });
}
CalcTax
方法变为: public decimal CalcTax(IProduct product)
{
return product.Price + product.Tax;
}
这是否比您写的还要好。您的解决方案有效。通过使它“更面向对象”,我可以说使代码难以维护。因此,尽管我已经展示了如何编写这些代码以遵循OOP概念,但您最好还是坚持自己的方法,而只需遵循其他答案中关于如何更好地遵循C#约定以及如何改进代码的建议即可。
评论
\ $ \ begingroup \ $
我认为该产品不应该了解其税率。如果一本书有资格获得税收怎么办?现在很难更改代码。
\ $ \ endgroup \ $
– RobH
2015年5月1日晚上8:41
\ $ \ begingroup \ $
绝对是,这就是为什么我认为“还不够面向对象”的评论很讨厌的原因。通过将税额计算从CalcTax方法移到一系列类中,我使代码更加难以维护。
\ $ \ endgroup \ $
– David Arno
2015年5月1日在8:44
\ $ \ begingroup \ $
但是,您可以在不将税额计算到产品中的情况下进行OOed。您可以让产品负责报告其税率,或者产品报告其税种,并有一个单独的组件来跟踪每个类别的税率。我认为您没有证明OO会弄乱项目,但您肯定已经证明添加更多的OO并不一定会改善项目,您必须添加正确的OO。因此,“ OO不够”不应该被认为是“盲目添加更多的OO”,就像“文档不足”不应该被认为是“添加不正确的文档” :-)
\ $ \ endgroup \ $
–史蒂夫·杰索普(Steve Jessop)
2015年5月1日9:08
\ $ \ begingroup \ $
我认为让BookFoodOrMedical不是产品的原因使我很难。书是产品。食物是一种产品。医疗是一种产品。但不是BookFoodOrMedical。如果每页要对书籍加税,则现在需要创建Book类和FoodOrMedical类。另外,如果我们有一堆产品类别并且有新的税款可用,我们将不得不将该税款计算添加到所有类别中,而不是在一处更改根据产品类型进行计算的税款计算。 Ewan的解决方案使用枚举解决了所有这些问题,并为以后节省了复杂性。
\ $ \ endgroup \ $
–米莉·史密斯(Millie Smith)
15年5月2日在16:32
\ $ \ begingroup \ $
创建基类/接口并从基类/接口继承并不容易实现,将税率硬编码为产品描述在任何范式中都不是好主意。税率和产品以及分类仅仅是数据。除非实际计算税收或确定税收状态的算法发生变化,否则您不必更改代码。
\ $ \ endgroup \ $
–user2313838
15年5月2日在17:08
#8 楼
这种情况下的步骤1是识别描述中的参与者和行为。我们并不是在做一个证券交易所,它每隔一纳秒就监控公司与其分项财务状况相关的价值,我们只是在准确地描述所描述的内容。编写一个程序以输入价格。演员?行为?价格上涨是一种行为,因此尚无参与者。不幸的是,“程序”一词在程序类型中并不清楚,因此我们将其称为注册程序,这就是价格的输入地。书籍,食品或医疗产品无需缴税,如果没有,则需缴纳10%的税(其他项目)。演员?行为?这里有四个演员,书籍,食品,医疗和其他。他们每个人都有税收行为。它们都是产品。以前我们知道我们也在处理这些行为者的价格。
如果该项目是进口商品,则需缴纳5%的税(可能有进口商品和“其他商品”类型的商品15%的税(10 + 5))。演员?行为?存在产品导入的行为。这是对现有Actor的修改。
程序需要打印收据Actor?行为?这可以追溯到没有一个程序名称的原因,因此我将假定程序再次是我的Register类。打印是一种操作,收据显示带有格式的名称。
这是解析需求的结尾。到目前为止,我们需要一个注册类,一个用于每种产品类型的类,一个用于共享行为的基类以及一个用于修改税收的枚举的要求。收银机类应该能够计算价格并打印。让我们对此稍作讨论。
税项和基本产品类别的枚举
public enum TaxModificationPercent
{
Import = 5
}
public abstract class Product
{
public decimal Price { get; protected set; }
public string Name { get; protected set; }
protected decimal _Tax = 0;
public virtual decimal Tax
{
get { return _Tax; }
protected set { _Tax = value; }
}
public Product(string Name, decimal Price)
{
this.Name = Name;
this.Price = Price;
}
public Product(string Name, decimal Price, TaxModificationPercent taxMod)
: this(Name,Price)
{
this.Tax += (decimal)taxMod/100;
}
}
产品类别集
public class Book : Product
{
public Book(string Name, decimal Price) : base(Name, Price){}
public Book(string Name, decimal Price,TaxModificationPercent taxMod)
: base (Name, Price, taxMod){}
}
public class Food : Product
{
public Food(string Name, decimal Price) : base(Name, Price){}
public Food(string Name, decimal Price,TaxModificationPercent taxMod)
: base (Name, Price, taxMod){}
}
public class Medical : Product
{
public Medical(string Name, decimal Price) : base(Name, Price){}
public Medical(string Name, decimal Price,TaxModificationPercent taxMod)
: base (Name, Price, taxMod){}
}
public class Other : Product
{
private decimal _tax = 0.1M;
public override decimal Tax
{
get { return _tax; }
protected set { _tax = value; }
}
public Other(string Name, decimal Price) : base(Name, Price){}
public Other(string Name, decimal Price,TaxModificationPercent taxMod)
: base (Name, Price, taxMod){}
}
以及用于存放产品和打印收据的注册类别
public class Register
{
private List<Product> products = new List<Product>();
public void Feed(Product product)
{
this.products.Add(product);
}
public void Print()
{
decimal total = 0;
foreach(var nameGroup in this.products.GroupBy( p => p.Name ))
{
decimal item = nameGroup.Sum( p => p.Price );
total += item;
StringBuilder lineItem = new StringBuilder();
lineItem
.Append(nameGroup.Key)
.Append(": " )
.Append(string.Format("{0:C}",item));
if( nameGroup.Count() > 1 )
lineItem.Append(" (")
.Append(nameGroup.Count())
.Append(" @ ")
.Append(string.Format("{0:C}",nameGroup.First().Price))
.Append(")");
Console.WriteLine(lineItem.ToString());
}
decimal tax = this.products.Sum( p => p.Price*p.Tax );
Console.WriteLine("Sales Taxes: " + string.Format("{0:C}",tax));
Console.WriteLine("Total: " + string.Format("{0:C}",total));
}
}
最后,此操作的用途是简单地添加这些项目的新实例
var register = new Register();
register.Feed(new Book("Robinson Crusoe",4.56M));
register.Feed(new Book("Robinson Crusoe",4.56M));
register.Feed(new Food("Imported dog food",6.52M,TaxModificationPercent.Import));//Was uncertain if this was Food or Other
register.Feed(new Other("Perfume",6.23M));
register.Print();
这里是一个演示:https://dotnetfiddle.net/gCTYOtback返回顶部
#9 楼
在文本中描述了几种类别。 OOD将尝试将这些类别捕获为类。有进口商品和未进口商品的类别。有免税商品和含税商品的类别。如果只喝一种香水或一种狗食,这种方法就太致命了。主要思想是显示即使分类相交时也要进行分类的方法。可以有多个变体,可以子类化
var book = new Product("book", 4.56m);
类hierachi可能看起来像这样
#10 楼
重构我使用了您拥有的代码,花了大约10分钟的时间进行重构,因为我认为这是获得OOD和OOP的好方法。重构只是重新排列代码(开始)。这个想法是使易于理解。
下面是一个重构示例。关于VS2013重构的几件我不在乎的事情:1)当您生成方法时,它们有时会变为静态,而有时在您不希望的时候出现; 2)如果您具有全局var,并且您生成了一个方法,它将命名输入parm同样的事情。我没有更改这两件事来向您显示发生了什么。我本人这样做是为了提醒需要初始化内容,并确保始终在以后重新初始化它们。
每个if语句调用
customRound
(两次),当您看到重复的模式时,可以考虑抽象它们。 我想到的一件事是传入产品或具有税项的超类产品。所有产品都有价格,并且即使价格为零,所有产品都将征税。税收通常不是产品本身的一部分,而可能是产品的装饰。
namespace Cash
{
public class product
{
public bool is_import { get; set; }
public bool is_other { get; set; }
public string prod_name { get; set; }
public decimal prod_price { get; set; }
}
这是主要的切入点,现在看起来更像是控制器而不是控制器一大堆代码。
internal class Program
{
private decimal _Total_tax = 0;
private bool isImport = false;
private bool isOther = false;
private List<product> productList = new List<product>();
private string productName = string.Empty;
private decimal productPrice = 0;
private string selectedMenuItem = string.Empty;
public decimal Total_tax
{
get
{
return this._Total_tax;
}
set
{
this._Total_tax = value + Total_tax;
}
}
这就是重构使我陷入需要传递产品的方式:
public decimal CalcTax(decimal price, bool is_other, bool is_import)
{
decimal _price = 0;
if (is_import == true && is_other == true)
{
_price = customRound((price * 15) / 100) + price;
Total_tax = customRound((_price - price));
}
if (is_import == true && is_other == false)//
{
_price = customRound((price * 5) / 100) + price;
Total_tax = customRound((_price - price));
}
if (is_import == false && is_other == true)
{
_price = customRound((price * 10) / 100) + price;
Total_tax = customRound((_price - price));
}
if (is_import == false && is_other == false)
{
_price = price;
Total_tax = customRound((_price - price));
}
return _price;
}
public decimal customRound(decimal num)
{
return Math.Round(num * 20.0M, MidpointRounding.AwayFromZero) / 20.0M;
}
public void Print(List<product> prod_list)
{
var result = prod_list.GroupBy(x => new { x.prod_name, x.prod_price, x.is_other, x.is_import })
.Select(x => new
{
prod_name = x.Key.prod_name,
sum = x.Sum(z => z.prod_price),
count = x.Count(),
prod_price = x.Key.prod_price,
is_other = x.Key.is_other,
is_import = x.Key.is_import,
});
foreach (var item in result)
{
if (item.count > 1)
{
Console.WriteLine(item.prod_name + ": " + string.Format("{0:0.00}", CalcTax(item.sum, item.is_other, item.is_import)) + " (" + item.count + " @ " + item.prod_price + ")");
}
else
{
Console.WriteLine(item.prod_name + ": " + string.Format("{0:0.00}", CalcTax(item.sum, item.is_other, item.is_import)));
}
}
Console.WriteLine("Sales Taxes: " + string.Format("{0:0.00}", Total_tax));
Console.WriteLine("Total: " + string.Format("{0:0.00}", prod_list.Sum(item => item.prod_price) + Total_tax));
Console.ReadLine();
}
这些是Visual Studio自动生成的方法,请注意,它们都是静态的(不一定是好东西)。像这样重构为小块,可以让您开始考虑关注点分离。随着时间的流逝,您会自动执行此操作,因为它更容易理解。
public void ShowMenu()
{
while (true)
{
Initialize();
selectedMenuItem = GetAnswers(selectedMenuItem);
if (UserSelectedEnd()) break;
isImport = CheckImport(isImport);
isOther = CheckOther(selectedMenuItem, isOther);
productPrice = EnterPrice(productPrice);
productName = EnterProductName(productName);
AddProduct(productName, isImport, isOther, productPrice, productList);
}
}
注意:以下每种方法都可以将product实例作为参数。我发现在处理许多属性时传递类实例通常会更好。这将是对程序的重新设计,在问题循环顶部实例化一个新产品。
public void ShowMenu()
{
while (true)
{
Initialize();
selectedMenuItem = GetAnswers(selectedMenuItem);
if (UserSelectedEnd()) break;
var product = new product();
GetImportAnswer(product);
GetOtherAnswer(product);
GetPriceAnswer(product);
GetProductName(product);
productList.Add(product);
}
}
如果我想将产品用作参数,则上面的代码如下所示:
private static void AddProduct(string productName, bool isImport, bool isOther, decimal productPrice, List<product> productList)
{
productList.Add(new product { is_import = isImport, prod_name = productName, prod_price = productPrice, is_other = isOther });
}
无论如何,我认为您显示出一些不错的C#技能。你会找到一份好工作的!
#11 楼
从问题陈述中,我们可以布置如下所示的类: >
class Product {
private String name;
private doublt price;
//Getter setter, constructor etc
}
class Book extends Product implements NonTaxable { ... }
class Food extends Product implements NonTaxable { ... }
或者也可以是(特殊情况)
interface Taxable {
float taxRate();
}
必须能够通过查看输入文字:
interface NonTaxable extends Taxable {
default float taxRate(){
return 0.0f;
}
}
对于特殊情况,我们有一些“额外”计算:
interface ProductFactory {
Taxable parseProduct(String desc);
}
评论
从您描述问题的方式来看,我怀疑他们想要Book,Food,Medical和Other子类,而没有它们是一个危险信号。话虽这么说,我认为一个好的设计真的不需要那些类,而且我不确定我是否想与一家认为可以做到的公司合作...
老实说,我理解公司为什么这么说。该代码对我而言似乎是业余的。它未遵循任何常见的行业标准(命名等)。我真的建议您阅读amazon.es/Clean-code-Handbook-Software-Craftsmanship/dp / ...
如果他们没有真正说出面向对象操作,那么在此基础上让您失望似乎是不合理的。人为通信故障而不是编程故障的情况。是否在任何给定的项目中使用OO代码都是一个意见问题,并且有赞成和反对的理由。因此,实际上,由于缺乏有关此事的任何精确指导,它们实际上使您无法使用自己的判断。哪个不好。
我认为您不应该太重视“不是面向对象的”。他们不喜欢您的代码,而且可能要做的比向您详尽解释为什么不这样做要好得多。