How to avoid getters and setters

Deadly 提交于 2019-12-02 16:41:29
Jonathan Fingland

When should you use getters and setters?

Getters and setters are great for configuring or determining the configuration of a class, or retrieving data from a model

Getting the price of an item is an entirely reasonable use of a getter. That is data that needs to be available and may involve special considerations to protect the data by adding validation or sanitization to the setter.

You can also provide getters without setters. They do not have to come in pairs.

When shouldn't you use getters and setters?

Sometimes objects rely on internal properties that will never be exposed. For example, Iterators and internal collections. Exposing the internal collection could have dramatically negative and unexpected consequences.

Also, for example, let's say you are communicating via some HttpURLConnection. Exposing the setter for your HttpURLConnection means that you could end up with a very odd state should the connection be changed while waiting to receive data. This connection is something that should be created on instantiation or entirely managed internally.

Summary

If you have data that is for all intents and purposes public, but needs to be managed: use getters and setters.

If you have data that needs to be retrieved but under no circumstances should ever be changed: use a getter but not a setter.

If you have data that needs to be set for internal purposes and should never be publicly exposed (and cannot be set at instantiation): use a setter but not a getter (setter presumably prevents a second call affecting the internal property)

If you have something that is entirely internal and no other class needs to access it or change it directly, then use neither.

Don't forget that setters and getters can be private and even for internally managed properties, having a setter that manages the property may be desirable. For example, taking a connection string and passing it to the setter for HttpURLConnection.

Also note:

Allen Holub's article Why getter and setter methods are evil seems to be the source of OP's reasoning but, in my opinion, the article does a poor job of explaining its point.

Edit: Added summary
Edit 2: spelling corrections

It's a shame to see a small, vocal minority take a back lash against the whole "Getters and Setters" are evil debate. Firstly the article title is purposely provocative to draw you in, as should any blog post. I've in turn blogged about this before and several years later updated my opinions and ideas about this question. I'll summarise the best I can here.

  • Getters and setters (accessors) are not evil
  • They are "evil" (unnecessary) most of the time however
  • Encapsulation is not just adding accessors around private fields to control change, after all there is no benefit to added get/set methods that just modify a private field
  • You should write as much code as possible with the principle of "Tell, Don't Ask"
  • You need to use accessors for framework code, DTOs, serialisation and so forth. Don't try to fight this.
  • You want your core domain logic (business objects) to be as property free as possible however. You should tell objects to do stuff, not check their internal state at will.

If you have a load of accessors you essentially violate encapsulation. For example:

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

   // Methods with behaviour...
}

This is a crap domain object, because I can do this:

me.Salary = 100000000.00;

This may be a simple example, but as anyone who works in a professional environment can attest to, if there is some code that is public people will make use of it. It would not be wrong for a developer to see this and start adding loads of checks around the codebase using the Salary to decide what do with the Employee.

A better object would be:

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
}

Now we cannot rely on Salary being public knowledge. Anyone wanting to give a pay rise to employees must do this via this method. This is great because the business logic for this is contained in one place. We can change this one place and effect everywhere the Employee is used.

fiction

The following sample is a brilliant example of boilerplate setters and getters.

class Item{
  private double price;

  public void setPrice(final double price){
    this.price = price;
  }
  public double getPrice(){
    return this.price;
  }
}

Some coders think that this is called encapsulation, but in fact this code is exact equivalent of

class Item{
  public double price;
}

In both classes price is not protected or encapsulated, but the second class reads easier.

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

This is a real encapsulation, the invariant of the class is guarded by the setPrice. My advice - don't write dummy getters and setters, use getters and setters only if they guard the invariant of your class

I have read in many places that "getters and setters are evil".

Really? That sounds crazy to me. Many? Show us one. We'll tear it to shreds.

And I understood why so.

I don't. It seems crazy to me. Either your misunderstood but think you did understand, or the original source is just crazy.

But I don't know how to avoid them completely.

You shouldn't.

how to avoid getPrice?

See, why would you want to avoid that? How else are you suppose to get data out of your objects?

how to avoid them???

Don't. Stop reading crazy talk.

When someone tells you that getters and setters are evil, think about why they are saying that.

Getters

Are they evil? There is no such thing as evil in code. Code is code and is neither good nor bad. It's just a matter of how hard it is to read and debug.

In your case, I think it is perfectly fine to use a getter to calculate the final price.

The "evil"

Usecase: you think you want the price of an item when buying something.

People sometimes use getters like this:

if(item.getPrice() <= my_balance) {
   myBank.buyItem(item);
}

There is nothing wrong with this code, but it isn't as straight-forward as it could be. Look at this (more pragmatic approach):

myBank.buyItem(item); //throws NotEnoughBalanceException

It's not the buyers or the cashiers job to check the price of an item when buying something. It's the actually the bank's job. Imagine that customer A has a SimpleBank.java

public class SimpleBank implements Transaction {

  public void buyItem(Item item){
     if(getCustomer().getBalance() >= item.getPrice()){
        transactionId = doTransaction(item.getPrice());
        sendTransactionOK(transactionId);
     }
  }
}

The first approach seems fine here. But what if customer B has a NewAndImprovedBank.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);
     }
  }
}

You might think that you are being defensive when using the first approach, but actually you are limiting the capabilities of your system.

Conclusion

Don't ask for permission aka item.getPrice() , ask for forgiveness aka NotEnoughBalanceException instead.

getPrice() is accessing a private variable I'm assuming.

To answer your question directly, make the price variable public, and code something like (syntax may differ depending on language, use of pointers etc):

total += item.price;

However this is generally considered bad style. Class variables should generally remain private.

Please see my comment on the question.

How to avoid getters and setters in Java? Use Project Lombok

How to avoid getters and setters? Design classes that actually act upon the data they hold.

Getters lie about the data anyway. In the Item.getPrice() example, I can see I'm getting an int. But is the price in dollars or cents? Does it include tax(es)? What if I want to know the price in a different country or state, can I still use getPrice()?

Yes, this might be beyond the scope of what the system is designed to do, and yes, you might just end up returning a variable's value from your method, but advertising that implementation detail by using a getter weakens your API.

'Evil' as .getAttention()

This has been discussed often, and even perhaps went a bit viral, as a result of the pejorative term "Evil" used in the dialog. There are times when you need them, of course. But the problem is using them correctly. You see, Professor Holub's rant isn't about what your code is doing now, but about boxing yourself in so that change in the future is painful and error prone.

In fact, all I have read by him carries this as its theme.

How does that theme apply to the class Item?

A look at the future of Item

Here is fictions's item class:

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

This is all well and good- but it is still 'Evil' in the sense that it could cause you a lot of grief in the future.

The grief is apt to come from the fact that one day 'price' may have to take different currencies into account (and perhaps even more complex barter schemes). By setting price to be a double, any code that is written between now and the 'apocalypse' (we're talking evil, after all) will be wiring price to a double.

It is much better (even Good, perhaps) to pass in a Price object instead of a double. By doing so you can easily implement changes to what you mean by 'price' without breaking the existing interfaces.

The takeaway on getters and setters

If you find yourself using getters and setters on simple types, make sure you consider possible future changes to the interface. There is a very good chance you shouldn't be. Are you using setName(String name)? You should consider setName(IdentityObject id) or even setIdentity(IdentityObject id) in case other identification models show up (avatars, keys, whatever). Sure you can always go around and setAvatar and setKey on everything, but by using an object in your method signature you make it easier to extend in the future to the objects that can use the new identity properties and not break the legacy objects.

Cloudanger answer is is one, but you must also realize that the item list will likely contain many item objects with quantity ordered on it.

Solution : create another class in between them that stores your item in the item list and the qty ordered for that item (Let's say the class is called OrderLine).

OrderLine will have Item and qty as fields.

After that, code something like calculateTotal(int qty) in Item which return price*qty. Create a method in OrderLine that call calculateTotal(qtyOrdered)

Pass the return value to the itemList.

This way, you avoid getters. The ItemList will only know the total price. Your code should live with your data. Ask the Object who has the data to calculate the totalPrice instead of asking that object for raw data to calculate your totalPrice.

Really? I don't think that. on the contrary the getters and setters help you to protect the consistense of the variables.

The importance of getters and setters is to provide protection to private attributes so that they can not be accessed directly for this it is best that you create a class with the attribute item in which you include the corresponding get and set.

Use a helper class ShoppingCart. Item's method item.addTo(ShoppingCart cart) would add the price to the totalSum of the cart using shoppingCart.addItem(Item item, int price)

Dependency from Item to ShoppingCart isn't disadvantageous if the Items are meant to be items of ShoppingCarts.

In the case where Items live solely for the ShoppingCart and the Item class is small, I would more likely have the Item as an inner class of the ShoppingCart, so that the ShoppingCart would have access to the private variables of the items.

Other thoughts

It would also be possible, although quite unintuitive design, to have the Item class count the sum (item.calculateSum(List<Item> items)), since it can access the private parts of other items without breaking encapsulation.

To others wondering why the getters are bad. Consider the given example where the getPrice() returns integer. If you would want to change that to something better like BigDecimal at least or a custom money type with currency, then it wouldn't be possible since the return type int exposes the internal type.

Getters and setters are evil because they break encapsulation and can unnecessarily expose an objects internal state and allow it to be modified in way it should not be. The following article elaborates on this problem:

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

A different perspective that is missing here so far: getters and setters invite to violate the Tell Don't Ask principle!

Imagine you go shopping in the supermarket. In the end, the cashier wants money from you. The getter/setter approach is: you hand over your purse to the cashier, the cashier counts the money in your purse, takes the money you owe, and gives back the purse.

Is that how you do things in reality? Not at all. In the real world, you typically don't care about the internal state of "autonomous" other "objects". The cashier tells you: "your bill is 5,85 USD". Then you pay. How you do that is up to you, the only thing the cashier wants/needs is he receives that amount of money from your side.

Thus: you avoid getters and setters by thinking in terms of behavior, not in terms of state. Getters/setters manipulate state, from the "outside" (by doing avail = purse.getAvailableMoney() and purse.setAvailableMoney(avail - 5.85). Instead, you want to call person.makePayment(5.85).

SSRULES

You can avoid getter and setter at places by using _classname__attributename because that's the changed new name once you declare private to any attribute.

So if Item is the class with a private attribute declared as __price

then instead of item.getPrice() you can write _Item__price.

It will work fine.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!