int grandTotal() { int total = 0;
for (Item item: itemList) total += item.getPrice();
return total; }
在上面的例子中,如何避免使用getPrice()方法呢?因为Item类提供了getName,setName等方法……
那么,我该怎么避免使用它们呢?
int grandTotal() { int total = 0;
for (Item item: itemList) total += item.getPrice();
return total; }
在上面的例子中,如何避免使用getPrice()方法呢?因为Item类提供了getName,setName等方法……
那么,我该怎么避免使用它们呢?
Getter和setter非常适合用于配置或确定类的配置,或从模型中检索数据。
获取物品价格是使用getter的完全合理用途。这是需要可用的数据,并且可能涉及通过在setter中添加验证或净化来保护数据的特殊考虑。
您还可以提供没有setter的getter。它们不一定要成对出现。
有时对象依赖于永远不会公开的内部属性。例如,迭代器和内部集合。公开内部集合可能会产生极其负面和意外的后果。
此外,例如,假设您正在通过某个HttpURLConnection进行通信。公开HttpURLConnection的setter意味着在等待接收数据时更改连接可能会导致非常奇怪的状态。这个连接是应该在实例化时创建或完全在内部管理的东西。
如果您拥有实际上是公共的数据,但需要进行管理:请使用getter和setter。
如果您需要检索数据,但在任何情况下都不应更改数据:请使用getter而不是setter。
如果您需要设置用于内部目的并且不应公开(并且不能在实例化时设置)的数据:请使用setter而不是getter(setter可能会防止第二次调用影响内部属性)
如果您有某些完全内部的东西,没有其他类需要直接访问或更改它,请不要使用getter和setter。
不要忘记,getter和setter可以是私有的,即使对于内部管理的属性,具有管理属性的setter也可能是可取的。例如,将连接字符串传递给HttpURLConnection的setter。
还要注意:
Allen Holub的文章为什么getter和setter方法是有害的似乎是OP推理的来源,但在我看来,这篇文章没有很好地解释它的观点。
编辑:添加摘要
编辑2:拼写更正
很遗憾看到一小部分高声喊叫的人对整个 "获取器和设置器" 是邪恶的辩论进行反击。首先,文章标题是故意挑衅性的,以吸引你的注意,就像任何博客文章一样。我之前也写过关于这个问题的博客文章,几年后更新了我的观点和想法。我会在这里尽力概括。
如果你有大量的访问器,实际上你违反了封装的原则。例如:
class Employee
{
public decimal Salary { get; set; }
// Methods with behaviour...
}
这是一个糟糕的领域对象,因为我可以做到这一点:
me.Salary = 100000000.00;
class Employee
{
private decimal salary;
public void GivePayRise()
{
// Should this employee get a pay rise.
// Apply business logic - get value etc...
// Give raise
}
// More methods with behaviour
}
class Item{
private double price;
public void setPrice(final double price){
this.price = price;
}
public double getPrice(){
return this.price;
}
}
有些程序员认为这被称为封装,但实际上这段代码与以下代码完全等效:
class Item{
public double price;
}
两个类中都没有保护或封装 price
,但第二个类更易于阅读。
class Item{
private double price;
public void setPrice(final double price){
if(isValidPrice(price))
this.price = price;
else throw new IllegalArgumentException(price+" is not valid!");
}
public double getPrice(){
return this.price;
}
}
这是真正的封装,类的不变式由setPrice
保护。我的建议- 不要编写虚拟的getter和setter,只有在它们保护类不变式时才使用getter和setter
当有人告诉你getter和setter是邪恶的时候,请思考一下他们为什么这样说。
它们是邪恶的吗?在代码中不存在所谓的“邪恶”。代码只是代码,既不好也不坏。问题只在于阅读和调试的难度。
在您的情况下,我认为使用getter计算最终价格是完全可以的。
用例:您想在购买某物时获取其价格。
有时人们会像这样使用getter:
if(item.getPrice() <= my_balance) {
myBank.buyItem(item);
}
这段代码没有问题,但它并不像它本可以那样直截了当。看看这个更实用的方法:
myBank.buyItem(item); //throws NotEnoughBalanceException
当购买物品时,买家或收银员无需检查商品价格。这实际上是银行的工作。想象一下,客户 A
有一个名为 SimpleBank.java
的项目。
public class SimpleBank implements Transaction {
public void buyItem(Item item){
if(getCustomer().getBalance() >= item.getPrice()){
transactionId = doTransaction(item.getPrice());
sendTransactionOK(transactionId);
}
}
}
public class NewAndImprovedBank implements Transaction {
public void buyItem(Item item){
int difference = getCustomer().getBalance() - item.getPrice();
if (difference >= 0) {
transactionId = doTransaction(item.getPrice());
sendTransactionOK(transactionId);
} else if (difference <= getCustomer().getCreditLimit()){
transactionId = doTransactionWithCredit(item.getPrice());
sendTransactionOK(transactionId);
}
}
}
你可能认为当使用第一种方法时你正在进行防御,但实际上你是在限制你系统的能力。
不要请求授权,而是请求宽恕,也就是使用 NotEnoughBalanceException
而不是 item.getPrice()
。
我猜测getPrice()正在访问一个私有变量。
直接回答你的问题,将价格变量设置为public,并编写类似以下代码(语法可能因语言、指针使用等而异):
total += item.price;
avail = purse.getAvailableMoney()
和purse.setAvailableMoney(avail - 5.85)
),而你想要做的是调用person.makePayment(5.85)
。这个问题经常被讨论,甚至有时因对话中使用了贬义词"邪恶"而引起轰动。当然,在某些情况下你需要它们。但问题在于正确使用它们。你看,Holub教授的抱怨并不是关于你的代码现在做了什么,而是关于将自己限制住,以致未来的改变会变得痛苦和容易出错。
事实上,我读过他所有的东西都有这个主题。
这个主题如何适用于类 Item?
这里是虚构的物品类:
class Item{
private double price;
public void setPrice(final double price){
if(isValidPrice(price))
this.price = price;
else throw new IllegalArgumentException(price+" is not valid!");
}
public double getPrice(){
return this.price;
}
}
这很好,但从某种意义上说,它仍然是“邪恶”的,因为它可能会在未来给你带来很多麻烦。
这种麻烦可能来自于有一天,“价格”可能必须考虑不同的货币(甚至更复杂的易货交换计划)。通过将价格设置为double类型,在“启示录”(毕竟我们在谈论邪恶)之前和之后编写的任何代码都将连接到double类型的价格。
最好的做法(甚至可以说是好的做法)是传递一个Price对象而不是一个double类型。这样做可以轻松实现对“价格”含义的更改,而不会破坏现有接口。
如果您发现自己在简单类型上使用getter和setter,请确保考虑接口的可能未来变化。有很大的机会表明您不应该这么做。您是否正在使用setName(String name)
?您应该考虑使用setName(IdentityObject id)
或甚至setIdentity(IdentityObject id)
,以防其他身份验证模型出现(头像、密钥等)。当然,您始终可以四处走动并在所有内容上使用setAvatar
和setKey
,但是通过在方法签名中使用对象,您可以更轻松地将其扩展到可以使用新身份属性的对象,而不会破坏遗留对象。
Cloudanger的答案是“1”,但您还必须意识到,物品清单可能包含许多带有订购数量的物品对象。
解决方案:在它们中间创建另一个类,该类将您的物品存储在物品列表中,并存储该物品的订购数量(假设该类称为OrderLine)。
OrderLine将具有Item和qty字段。
之后,在Item中编写类似于calculateTotal(int qty)的代码,返回price * qty。在OrderLine中创建一个调用calculateTotal(qtyOrdered)的方法。
将返回值传递给itemList。
这样,您可以避免使用getter。ItemList只会知道总价格。您的代码应该与数据一起运行。询问具有数据的对象来计算totalPrice,而不是要求该对象提供原始数据来计算totalPrice。