9

I was reading this page, about when getters/setters are justified, and the OP gave the following code sample:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

The accepted answer states:

By the way, in your example, I would give the class Fridge the putCheese() and takeCheese() methods, instead of get_cheese() and set_cheese(). Then you would still have encapsulation.

How is encapsulation preserved by renaming it from get/set to putCheese()/takeCheese() You're obviously getting/setting a value, so why not simply leave it as get/set?

In the same answer it also states:

Having getters and setters does not in itself break encapsulation. What does break encapsulation is automatically adding a getter and a setter for every data member (every field, in java lingo), without giving it any thought.

In this case, we only have one variable, cheese, and you may want to take and return the cheese to the fridge, so a get/set pair in this case is justified.

QueenSvetlana
  • 201
  • 1
  • 5
  • 7
    `putCheese` would add cheese to the fridge, and `takeCheese` would remove it -- these are (higher level) domain-oriented abstractions, rather than object field getters & setters (which are (low-level) computer programming abstractions). – Erik Eidt Aug 13 '19 at 13:15

4 Answers4

17

I think you are missing the point. Its not saying you should rename the setter and getter, but to have methods which add and remove items from the fridge. ie

public class Fridge
{
    private int numberOfCheeseSlices;

    public void AddCheeseSlices(int n)
    {
         if(n < 0) { 
             throw new Exception("you cant add negative cheese!");
         }
         if(numberOfCheeseSlices + n > this.capacityOfFridge) { 
             throw new Exception("TOO MUCH CHEESE!!");
         }
         ..etc
         this.numberOfCheeseSlices += n;
    }
}

Now the private variable is encapsulated. I cant just set it to anything I want, I can only add and remove cheese slices from the fridge using the methods, which in turn ensure that whatever cheese business logic rules I want are applied.

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • 2
    Right, but you can still leave it as `setCheese()` have have that same logic in the setter. To me anyways, you're attempting to hide the fact you're using a setter by renaming the method, but your obviously getter/setting something. – QueenSvetlana Aug 13 '19 at 12:56
  • 21
    @QueenSvetlana it's not a setter. It's an operation. You're telling the fridge "here's another cheese," and it is adding it to its internal, encapsulated cheese count. A setter would say "you now have 5 cheeses." These are functionally different operations, not just different names. – Ant P Aug 13 '19 at 12:57
  • 1
    you could call it setCheese, but that would be confusing as it would not set the value of the cheese. – Ewan Aug 13 '19 at 13:00
  • 6
    Note there's an integer overflow bug here. If there's more than 0 cheese already, `AddCheese(Integer.MAX_VALUE)` should fail, but won't. – user253751 Aug 13 '19 at 22:56
  • 3
    that would totaly be covered in the ..etc section – Ewan Aug 14 '19 at 07:28
  • 1
    @QueenSvetlana I think you're missing a key point: Even if a method is "merely" a "setter" today, it can be reimplemented to do something else in the future. But if you expose your public variable, you don't have that ability. Public fields don't break encapsulation merely because you can directly set values. They break encapsulation because you can directly set values, and *can never revoke that ability* without an API breaking change (making the method private, and mutating them behind public methods). – Alexander Aug 15 '19 at 06:41
  • @Alexander - I agree with you, and I'm not arguing that `setters`/`getters` are evil, you just have to think carefully about them first, but I incorrectly assumed from the other question that simply changing the name preserved encapsulation, but it doesn't. – QueenSvetlana Aug 15 '19 at 14:04
5

Getters and setters break encapsulation every single time, by definition. What might be argued is that sometimes we need to do that. With that out of the way, here are my answers:

How is encapsulation preserved by renaming it from get/set to putCheese()/takeCheese() You're obviously getting/setting a value, so why not simply leave it as get/set?

The difference is in semantics, i.e. the meaning of what you write. Encapsulation is not only about protecting, but hiding internal state. Internal state should not even be known outside, instead the object is expected to offer business-relevant methods (sometimes referred to as "behavior") to manipulate/use that state.

So get and set are technical terms and seem to have nothing to do with the "fridge" domain, while put and take might actually make some sense.

However, whether put or take make actual sense still depends on the requirements and can not be objectively judged. See next point:

In this case, we only have one variable, cheese, and you may want to take and return the cheese to the fridge, so a get/set pair in this case is justified.

That can not be objectively determined without more context. Your example contains a go_shopping() method somewhere else. If that is what the Fridge is for, than it does not need get or set, what it needs is Fridge.go_shopping(). This way you have a method that is derived from the requirements, all "data" that it needs is local and you have actual behavior instead of just a thinly veiled data-structure.

Remember, you are not building a generic, reusable Fridge. You are building a Fridge for your requirements only. Any effort spent making it more than what it needs to be is actually wasteful.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • 10
    `Getters and setters break encapsulation every single time` -- That's a bit too strongly worded for my taste. Methods (including getters and setters) have the same purpose as gates do at a concert: they allow orderly entry and exit from the venue. – Robert Harvey Aug 13 '19 at 16:08
  • If you're implying that methods are there to preserve class invariants, I think that's a pretty mechanical point of view. Methods are there to implement *behavior*. While doing that they have to of course preserve invariants, but that's not to reason they're there. – Robert Bräutigam Aug 13 '19 at 16:21
  • I'm not implying anything, other than what I actually said. – Robert Harvey Aug 13 '19 at 16:23
  • 8
    *"Getters and setters break encapsulation every single time, by definition"* - by which definition? I too have an issue with this, because getters and setters are just part of the public interface, as any other public member; they only break encapsulation if they expose implementation details that are deemed to be internal *by design* (that is, by a decision of the programmer (or a team)). The fact that getters and setters are often added haphazardly, with coupling control being an afterthought, is another matter entirely. – Filip Milovanović Aug 13 '19 at 17:12
  • 2
    @FilipMilovanović Fair point. As I mention in the answer, "encapsulation" is used to *hide* object internals (== object state == instance variables). Getters and setters *publish* object internals. Therefore by these definitions they are in perpetual conflict. Now, whether sometimes we *need* to break encapsulation is a different matter, which should be handled separately. To be clear, by saying that setters/getters always break encapsulation I'm not saying it's always "wrong", if that helps. – Robert Bräutigam Aug 13 '19 at 17:25
  • 5
    getters and setters do not "break encapsulation literally always". Getters and setters often don't even refer to members underneath directly, perform some sort of operation, or are only exclusively defined, you might only have a getter or might only have a setter. It breaks encapsulation when getters and setters don't do anything more than member access and are both defined for the same fields. Even this may be necessary for library maintainers who don't want to break ABIs/APIs with internal changes and avoid client recompilation. – Krupip Aug 13 '19 at 21:07
  • Your strong stance that _all_ object state is internal (private) information that needs to be hidden seems to be where the push back in the comments is coming from. If we accept this position, then yes getters and setters (and many other methods) will always break encapsulation of an object's _abstract_ state. Even with this strong stance though, the encapsulation of the object's actual state (as determined by the implementation) is still preserved. However in a very practical sense, not all state is, _or even should be_ private for all objects. (cont) – Mr.Mindor Aug 13 '19 at 22:36
  • (cont) this, I believe is what Filip was getting at. Some objects _by design_ also have a _public_ state. It isn't that encapsulation needs to be broken for these objects to work or exist. Encapsulation can't be broken for things that were not (and should not be) encapsulated to begin with. The contents of a fridge btw, is an example of something that would be included in public state. – Mr.Mindor Aug 13 '19 at 22:43
  • 4
    Ok, getters and setter only break encapsulation 99% of the time. Happy? And, by exposing the **type** of the encapsulated object, they definitely break encapsulation. Once you have a `public int getFoo()` it's nearly impossible, in practice, to change to another type. – user949300 Aug 13 '19 at 23:52
  • @RobertBräutigam: It looks like we are perhaps conceptualizing things in a slightly different way, but aren't fundamentally in disagreement, especially when it comes to how these ideas manifest themselves in practice. BTW, just to be clear, I'm not saying that objects that include, as Mr.Mindor puts it, "public state" as a part of their contract should be extensively used, just that they are an option (i.e., if not designed with care, they can lead to tight coupling due to breaking the Hollywood Principle and/or Law of Demeter). – Filip Milovanović Aug 14 '19 at 07:47
  • 1
    This is a spectrum. Public fields: bad. Properties: better. Tell Don't Ask: Best. – Ant P Aug 14 '19 at 07:56
3

Almost all of this shows a fundamental misunderstanding of encapsulation, and how it applies.

The initial response that you were breaking encapsulation is just wrong. Your application may have a need to simply set the value of cheese in the fridge instead of increment/decrement or add/remove. Also, it is not semantics, no matter what you call it, if you have a need to access and/or change attributes you don't break encapsulation by providing them. Finally, encapsulation is not really about "hiding", it's about controlling access to state and values that should not need to be public or manipulated outside of the class, while granting it to those that should and performing the task made available internally.

A getter or setter doesn't break encapsulation when there is a legitimate need to get or set a value. This is why methods can be made public.

Encapsulation is about keeping data and the methods that modify that data directly together in one logical place, the class.

In this particular case, there is clearly a need to change the value of cheese in the application. Regardless of how this is done, via get/set or add/remove, as long as the methods are encapsulated in the class you are following the object oriented style.

For clarification, I'll give an example of how encapsulation is broken by providing access regardless of method name or logical execution.

Say your fridge has a "lifetime", just a number of ticks before the fridge is no longer operational (for the sake of argument, the fridge can't be repaired). Logically there is no way a user (or the rest of your application) should be able to change this value. It should be private. It would only be visible through say a different public attribute known as "isWorking". When the lifetime expires, internally the fridge sets isWorking to false.

The execution of the lifetime counting down and its flipping the isWorking switch is all internal to the fridge, nothing outside could/should be able to effect the process. isWorking should only be visible, thus a getter does not break the encapsulation. However adding accessors for elements of the lifetime process would break your encapsulation.

Like most things, the definition of encapsulation is not literal, it's relative. Should you be able to see X outside of the class? Should you be able to change Y? Is everything that applies to your object here in this class or is the functionality spread across multiple classes?

user343330
  • 31
  • 1
  • Let's look at it pragmatically. You are saying encapsulation is not broken if the code is *intended* that way, or if there is a "legitimate" reason. That basically means we can never ever say encapsulation is broken, because the developer only has to say she "intended" that way, or there was a "legitimate" reason. So this definition is basically useless then, isn't it? – Robert Bräutigam Aug 16 '19 at 06:45
  • No, I'm say encapsulation is not broken simply by providing access. And it has nothing to do with what a programmer says, but what the needs of the application are. An application needs to have access to manipulate the objects, encapsulation is about controlling access. An application may need to "set" an attribute of an object, but the logic and or calculations required to perform that "set" operation should be encapsulated in the object class. – user343330 Aug 16 '19 at 17:25
  • I think this is where we fundamentally differ, you say: "An application may need to "set" an attribute of an object...". I don't think so. Choosing a design that involves setting or getting an attribute is up to the developer. It's a choice! There are a lot of ways to code something, not all of those involve getting or setting instance variable values. – Robert Bräutigam Aug 16 '19 at 20:06
  • Could be...Usually design is based around meeting a need, whether either is chosen by the programmer is based on the business environment. In either case though, if the application has a fundamental need to manipulate a value that is part of an object, then you have to make that functionality accessible in some way. Providing an entry point to do work isn't breaking encapsulation. Take a sales app. At some point your transaction should calculate tax, doing that in the transaction object would keep it encapsulated, but the app should be able to state transaction.CalcTax – user343330 Aug 16 '19 at 20:16
  • Setters are bad examples because of their simplicity, think in terms of a function that does far more work that results in an object attribute being updated vs something outside the class doing the work and then saying object.attribute = x. The first is good encapsulation while providing the appropriate access, the second is not. As for getters, does the application need to know just that piece of an object to do something else that cannot be contained in the object class? if yes, exposing it doesn't break your encapsulation. if no, then encapsulate it in the object class – user343330 Aug 16 '19 at 20:17
1

It's not just renaming a method. The two methods function differently.

(Picture this in your mind)

get_cheese and set_cheese exposes the cheese. putCheese() and takeCheese() keeps the cheese hidden and takes care of managing it and give the user a way to handle it. The observer doesn't see the cheese, he/she only sees two methods of manipulating it.

Daneesha
  • 139
  • 1
  • 5