如何避免吸气剂和二传手

时间:2012-02-23 15:40:46

标签: oop design-patterns properties ooad accessor

我在很多地方都读过“吸气鬼和恶魔都是邪恶的”。我理解为什么这样。但我不知道如何完全避免它们。 Say Item是一个包含项目名称,数量,价格等信息的类...... 和ItemList是一个类,它有一个Items列表。找到总计:

int grandTotal()
{
int total = 0;

for (Item item: itemList)
       total += item.getPrice();

return total;
}

在上面的例子中,如何避免getPrice()? Item类提供了getName,setName等....

我该如何避免它们?

15 个答案:

答案 0 :(得分:40)

什么时候应该使用getter和setter?

Getters和setter非常适合配置或确定类的配置,或从模型中检索数据

获取物品的价格完全合理地使用了吸气剂。这是需要可用的数据,可能需要特别考虑,通过向设置器添加验证或清理来保护数据。

您还可以提供没有setter的getter。他们不必成对

什么时候不应该使用getter和setter?

有时对象依赖于永远不会暴露的内部属性。例如,迭代器和内部集合。公开内部收集可能会产生极大的负面和意外后果。

另外,例如,假设您正在通过某些HttpURLConnection进行通信。为您的HttpURLConnection公开setter意味着如果在等待接收数据时更改连接,您可能会得到非常奇怪的状态。此连接应该在实例化时创建或完全在内部管理。

摘要

如果您拥有公开的所有意图和目的的数据,但需要进行管理:使用getter和setter。

如果您有需要检索的数据但在任何情况下都不应该更改:使用getter而不是setter。

如果您有需要为内部目的设置的数据且不应公开公开(并且不能在实例化时设置):使用setter而不是getter(setter可能会阻止第二次调用影响内部属性)< / p>

如果您的内容完全是内部的,并且没有其他类需要访问它或直接更改它,那么两者都不使用。

不要忘记,setter和getter可以是私有的,甚至可以是内部管理的属性,因此可能需要一个管理属性的setter。例如,获取连接字符串并将其传递给HttpURLConnection的setter。

另请注意:

Allen Holub的文章Why getter and setter methods are evil似乎是OP推理的来源,但在我看来,这篇文章在解释其观点方面做得很差。

编辑:添加摘要
编辑2:拼写更正

答案 1 :(得分:19)

看到一个小小的声音少数人反对整个“Getters and Setters”是一场邪恶的辩论,这是一种耻辱。首先,文章标题是故意挑衅性的吸引你,就像任何博客文章一样。关于此问题的I've in turn bloggedseveral years later updated my opinions and ideas about this question。我会在这里总结一下最好的。

  • 吸气剂和二传手(存取者)不是邪恶的
  • 他们 “邪恶” (不必要)大部分时间然而
  • 封装不只是在私有字段周围添加访问器来控制更改,毕竟没有有益于添加只修改私有字段的get / set方法
  • 您应该使用“Tell, Don't Ask
  • 的原则编写尽可能多的代码
  • 需要为框架代码,DTO,序列化等使用访问器。 不要试图打击这个。
  • 您希望核心域逻辑(业务对象)尽可能不受属性影响。你应该告诉对象做事情,而不是随意检查他们的内部状态。

如果你有一堆访问者,你基本上违反了封装。例如:

class Employee
{
   public decimal Salary { get; set; }

   // Methods with behaviour...
}

这是一个垃圾域对象,因为我可以这样做:

me.Salary = 100000000.00;

这可能只是一个简单的例子,但正如在专业环境中工作的任何人都可以证明的那样,如果有一些公共代码会使用它。开发人员看到这一点并开始使用Salary在代码库周围添加大量检查以决定如何处理Employee,这不是错误的。

更好的对象是:

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
}

现在我们不能依靠薪水作为公共知识。任何想要给员工加薪的人都必须通过这种方法来做到这一点。这很好,因为它的业务逻辑包含在一个的地方。我们可以在使用员工的每个地方改变这个地方和效果。

答案 2 :(得分:13)

以下示例是样板设定器和吸气剂的绝佳示例。

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

答案 3 :(得分:6)

  

我在很多地方都读过“吸气鬼和恶魔都是邪恶的”。

真的?这听起来很疯狂。许多?告诉我们一个。我们会撕成碎片。

  

我理解为什么会这样。

我没有。这对我来说似乎很疯狂。要么你误解了,要么认为你明白了,或者原始来源只是疯了。

  

但我不知道如何完全避免它们。

你不应该。

  

如何避免getPrice

看,你为什么要避免这种情况?你怎么想从你的对象中获取数据呢?

  

如何避免它们?

别。停止阅读疯狂的谈话。

答案 4 :(得分:4)

当有人告诉你吸气剂和制定者是邪恶的时候,想想为什么他们这么说。

吸气剂

他们是邪恶的吗?代码中没有 evil 这样的东西。代码是代码,既不好也不坏。这只是一个阅读和调试有多难的问题。

在您的情况下,我认为使用吸气剂计算最终价格是完全正常的。

&#34;邪恶&#34;

用例:您认为在购买商品时您想要商品的价格。

人们有时会使用这样的吸气剂:

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);
     }
  }
}

第一种方法似乎很好。但是,如果客户BNewAndImprovedBank.java

,该怎么办?
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);
     }
  }
}

在使用第一种方法时,您可能会认为自己处于防御状态,但实际上您正在限制系统的功能。

结论

不要求同意item.getPrice(),请求原谅NotEnoughBalanceException

答案 5 :(得分:3)

getPrice()正在访问我假设的私有变量。

要直接回答您的问题,请将价格变量公之于众,并编写类似的内容(语法可能因语言,指针的使用等而有所不同):

total += item.price;

然而,这通常被认为是糟糕的风格。类变量通常应保持私有。

请参阅我对该问题的评论。

答案 6 :(得分:2)

如何避免Java中的getter和setter? 使用Project Lombok

答案 7 :(得分:2)

如何避免吸毒者和二传手?设计类实际上对它们持有的数据起作用。

无论如何,Getters对数据撒谎。在Item.getPrice()示例中,我可以看到我收到了int。但是价格是美元还是美分?它包含税吗?如果我想知道不同国家或地区的价格怎么办?我还可以使用getPrice()吗?

是的,这可能超出了系统设计的范围,是的,您可能最终会从方法中返回变量的值,但是使用getter通过广告宣传该实现细节会削弱您的API。 / p>

答案 8 :(得分:2)

'邪恶'为.getAttention()

这经常被讨论,甚至可能有点病毒,因为对话中使用的贬义术语“邪恶”。当然,当你需要时, 次。但问题是正确使用它们。你看,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;
    }
  }

这一切都很好 - 但它仍然是'邪恶',因为它可能会在未来给你带来很多悲伤。

悲伤很可能来自这样一个事实:有一天“价格”可能不得不考虑不同的货币(甚至可能更复杂的易货方案)。通过将价格设置为双倍,从现在到“启示录”之间写的任何代码(毕竟我们说的是邪恶的)都会将价格连接到双倍。

传递 Price 对象而不是双精度要好得多(甚至好)。通过这样做,您可以在不破坏现有界面的情况下轻松实现对“价格”的更改。

吸气剂和二传手的外卖

如果您发现自己在简单类型上使用getter和setter,请确保您考虑将来可能对界面进行更改。你不应该有这样的机会。你在使用setName(String name)吗?如果出现其他识别模型(头像,钥匙等),您应该考虑setName(IdentityObject id)甚至setIdentity(IdentityObject id)。当然,您可以随时查看所有内容setAvatarsetKey,但通过在方法签名中使用对象,您可以更轻松地将来扩展到可以使用新标识属性的对象,不要破坏遗留物品。

答案 9 :(得分:1)

Cloudanger的回答是一个,但您还必须意识到项目列表可能包含许多订购数量的项目对象。

解决方案:在它们之间创建另一个类,它将项目存储在项目列表中,并为该项目订购数量(假设该类称为OrderLine)。

OrderLine将Item和qty作为字段。

之后,在Item中编写类似calculateTotal(int qty)的代码,返回价格* qty。 在OrderLine中创建一个调用calculateTotal(qtyOrdered)

的方法

将返回值传递给itemList。

这样,你可以避免吸气剂。 ItemList只知道总价。 您的代码应该与您的数据一起使用。 询问拥有数据的对象计算totalPrice,而不是要求该对象提供原始数据来计算totalPrice。

答案 10 :(得分:0)

真的? 我不这么认为。相反,吸气剂和制定者可以帮助您保护变量的一致性。

getter和setter的重要性在于为私有属性提供保护,以便无法直接访问它们,最好创建一个具有属性item的类,其中包含相应的get和集。

答案 11 :(得分:0)

使用帮助程序类ShoppingCartItem方法item.addTo(ShoppingCart cart)会使用totalSum

将价格添加到购物车的shoppingCart.addItem(Item item, int price)

如果Item s是ShoppingCart s的项目,则ItemShoppingCart的相关性并不是不利的。

如果Item仅为ShoppingCart生活而Item类较小,我更有可能将Item作为内部类的ShoppingCart,以便ShoppingCart可以访问项目的私有变量。

其他想法

虽然设计非常不直观,但Item类可以计算总和(item.calculateSum(List<Item> items))也是可能的,因为它可以在不破坏封装的情况下访问其他项的私有部分。

其他人想知道为什么吸气剂不好。考虑getPrice()返回整数的给定示例。如果您希望将其更改为更好的类似BigDecimal或具有货币的自定义货币类型,则由于返回类型int公开内部类型,因此无法进行更改。

答案 12 :(得分:0)

getter和setter是邪恶的,因为它们破坏了封装并且可能不必要地暴露对象内部状态并允许它以不应该的方式进行修改。以下文章详细阐述了这个问题:

http://programmer.97things.oreilly.com/wiki/index.php/Encapsulate_Behavior,_not_Just_State

答案 13 :(得分:0)

到目前为止,这里还缺少一种不同的观点:getter和setter邀请违反Tell Don't Ask原则!

想象一下您去超市购物。最后,收银员要您的钱。吸气剂/装卸剂的方法是:您将钱包交给收银员,收银员会计算钱包中的钱,拿走您欠的钱,然后退还钱包。

您是如何在现实中做事情的?一点也不。在现实世界中,您通常不关心“自治”的其他“对象”的内部状态。收银员告诉您:“您的账单是5,85美元”。然后你付钱。您的操作方式取决于您自己,收银员唯一想要/需要的是他从您这边获得那笔钱。

因此:您可以通过<行为>行为而不是根据状态来思考,从而避免使用getter和setter方法。 getter / setter方法从“外部”操纵状态(通过执行avail = purse.getAvailableMoney()purse.setAvailableMoney(avail - 5.85)。相反,您想调用person.makePayment(5.85)

答案 14 :(得分:-1)

您可以使用_classname__attributename避免在某些地方使用getter和setter方法,因为一旦您声明对任何属性私有,这就是更改后的新名称。

因此,如果Item是具有私有属性声明为__price的类

然后可以写item.getPrice()代替_Item__price

它将正常工作。