如何避免吸气剂和二传手

问题描述 投票:24回答:15

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

int grandTotal()
{
int total = 0;

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

return total;
}

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

我该如何避免它们?

oop design-patterns properties ooad accessor
15个回答
41
投票

什么时候应该使用吸气剂和二传手?

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

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

你也可以提供没有setter的getter。它们不必成对出现。

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

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

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

摘要

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

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

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

如果你有一些完全是内部的东西,没有其他类需要访问它或直接更改它,那么两者都不使用。

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

另请注意:

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

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


1
投票

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

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

OrderLine将Item和qty作为字段。

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

将返回值传递给itemList。

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


0
投票

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

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


0
投票

使用辅助类ShoppingCartItem的方法item.addTo(ShoppingCart cart)将使用totalSum将价格加到购物车的shoppingCart.addItem(Item item, int price)

如果Items是ShoppingCarts的项目,那么从ItemShoppingCart的依赖并不是不利的。

Items仅为ShoppingCart生活并且Item类很小的情况下,我更有可能将Item作为ShoppingCart的内部类,因此ShoppingCart可以访问项目的私有变量。

其他想法

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

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


0
投票

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

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


0
投票

到目前为止,这里缺少一个不同的视角:getters和setters邀请违反Tell Don't Ask原则!

想象一下,你去超市购物。最后,收银员想要你的钱。 getter / setter方法是:你将钱包交给收银员,收银员计算你钱包里的钱,拿走你欠的钱,然后还钱包。

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

因此:你通过思考行为而不是国家来避免吸气者和制定者。 Getters / setters操纵状态,从“外部”(通过做avail = purse.getAvailableMoney()purse.setAvailableMoney(avail - 5.85)。相反,你想要调用person.makePayment(5.85)


-1
投票

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

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

然后你可以写item.getPrice()而不是_Item__price

它会工作正常。


19
投票

看到一个小的,有声音的少数人对整个“Getters and Setters”采取反对抨击是邪恶的辩论,这是一种耻辱。首先,文章标题是故意挑衅性的吸引你,就像任何博客文章一样。 I've in turn blogged关于此之前和several 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
}

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


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


6
投票

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

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

我理解为什么这样。

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

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

你不应该。

如何避免getPrice

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

怎么避免他们?

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


4
投票

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

Getters

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

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

恶魔”

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

人们有时会使用这样的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);
     }
  }
}

第一种方法似乎很好。但如果客户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);
     }
  }
}

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

Conclusion

不要求同意item.getPrice()的许可,请求宽恕又名NotEnoughBalanceException


3
投票

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

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

total += item.price;

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

请看我对这个问题的评论。


2
投票

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


2
投票

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

无论如何,吸毒者都会对数据撒谎。在Item.getPrice()的例子中,我可以看到我得到了一个int。但是价格是美元还是美分?它包含税吗?如果我想知道不同国家或州的价格怎么样,我还能使用getPrice()吗?

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


2
投票

'邪恶'为.getAttention()

这经常被讨论,甚至可能有点病毒,因为对话中使用的贬义术语“邪恶”。当然,有些时候你需要它们。但问题是正确使用它们。你看,Holub教授的咆哮不是关于你的代码现在在做什么,而是关于装箱自己,以便将来的变化是痛苦的,容易出错。

事实上,我读过的所有内容都以此为主题。

该主题如何应用于类Item?

看一下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对象而不是double对象要好得多(甚至好)。通过这样做,您可以在不破坏现有界面的情况下轻松实现对“价格”的更改。

吸气剂和二传手的外卖

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

© www.soinside.com 2019 - 2024. All rights reserved.