Jump to content
  • Advertisement
Sign in to follow this  
menyo

Should getters and setters be avoided?

This topic is 953 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have read this article and watched the webinar of Yegor and various other sources that imply that getters and setters can and should be completely avoided. While I do agree on a lot of things what Yegor says, I am still struggling to understand it completely. I do agree on having methods that do the thinking for the object itself instead of a plain setter. For example if we have a bank account we should not have setBalance(double balance) but instead withdraw(double amount) and deposit(double amount) this would clearly benefit the safety and readability of the exposed functionality for this object. So I agree that setters are in some way evil, we should always ask the object to do something and let it be handled by it's own behavior instead of pulling some data out and work with it somewhere else only to put it back in again. I also agree with him that this leads to better designed classes and easier abstraction when you want to split it up.

 

However, I am not sure how I would be able to get rid of getters, and I might not want to, but I am intrigued by this idea of Yegor. If we take this balance object, it seems to me it is important we can see it's properties, in this case the balance and perhaps the transactions that have been done on it. How would I do this without some sort of getter? I mean showBalance() would still function exactly like a getter. Even if I would add some sort of GUI to the balance object to display it's contents I still need to be able to "get" that GUI element. In the real world I am able to see some of the properties from objects and for some I might have to ask the objects. For example I can see the color of a box so I should be able to "get" it's color directly. I should not be able to get the combined weight directly from the box, I ask the box to calculate the weight of it's contents and itself and return this, I guess the latter does not fall under the term "getter" for Yegor.

 

One of the reasons Yegor mentions is the dependency of clients connected to the object. Say, we suddenly want to know who withdrawn something from the object? I am not understanding why his "idea" would help us in any way. We still have to change the withdraw method and all clients using this withdraw method need to be changed. I guess I still have no clue what he is talking about.

 

He also implies in his webinar that all objects should be immutable. This idea sounds crazy to me, he is actually proposing to pass a new object every time we want to mutate it.

public Balance withdraw(double amount)
{
  return new Balance(balance - amount);
}

I am aware that most people think this is nonsense but I would like to hear why. Or better, how this would help us in any way. I can only think of this as much slower and more typing:  myBalance = myBalance.withdaw(...). What is the use of a object when it is not allowed to change at all, and when it wants to change it clones itself. This eventually results in long constructors since all the final properties that are allowed to mutate (yes it sounds crazy) need to be set in the constructor.

 

I'd like to here your thoughts about the ideas from Yegor in the article. What are your thoughts and why do you think it's good or bad?

Share this post


Link to post
Share on other sites
Advertisement

One of the reasons Yegor mentions is the dependency of clients connected to the object. Say, we suddenly want to know who withdrawn something from the object? I am not understanding why his "idea" would help us in any way. We still have to change the withdraw method and all clients using this withdraw method need to be changed. I guess I still have no clue what he is talking about.

 

 

If you can only withdraw money by calling 'withdraw' you can just add a new parameter to withdraw and your compiler will scream at you until all places where money is withdrawn supply that information. If you just manually decreased amount everywhere you have to find all uses of 'amount' manually in the code, identify which are actually withdraw operations and add the withdrawer information manually (help the compiler can be to you in that case: zero).

 

 

This eventually results in long constructors since all the final properties that are allowed to mutate (yes it sounds crazy) need to be set in the constructor.

 

If your constructor becomes so complicated you have to pass so many parameters you probably have a class which just tries to do far too much. Look into the Single Responsibility Principle (https://en.wikipedia.org/wiki/Single_responsibility_principle). Having immutable classes where reasonably possible certainly simplifies a lot of problems, but especially in the context of game programming it is not always doable. Spending a few moment to ponder "Do I actually need that to be mutable?" for a new class is not a bad idea though.

 

Edit: Stupid editor screwed up the link...

Edited by BitMaster

Share this post


Link to post
Share on other sites


He also implies in his webinar that all objects should be immutable. This idea sounds crazy to me, he is actually proposing to pass a new object every time we want to mutate it.
public Balance withdraw(double amount)
{
  return new Balance(balance - amount);
}

 

Ouch, that's an interesting idea but surely that is just asking for trouble.

Share this post


Link to post
Share on other sites

If you can only withdraw money by calling 'withdraw' you can just add a new parameter to withdraw and your compiler will scream at you until all places where money is withdrawn supply that information. If you just manually decreased amount everywhere you have to find all uses of 'amount' manually in the code, identify which are actually withdraw operations and add the withdrawer information manually (help the compiler can be to you in that case: zero).

 

Yeah, I was actually putting that under the obvious and was looking for some supreme way I was not aware of. It is actually the same as just making a variable public to access it. Then you decide there needs to be done more to function. You make the variable private and create a method for it. The IDE will throw errors since the property is not accessible anymore and you have to change it to the method you created.

 

 


If your constructor becomes so complicated you have to pass so many parameters you probably have a class which just tries to do far too much. Look into the Single Responsibility Principle (https://en.wikipedia.org/wiki/Single_responsibility_principle). Having immutable classes where reasonably possible certainly simplifies a lot of problems, but especially in the context of game programming it is not always reasonably possible. Spending a few moment to ponder "Do I actually need that to be mutable?" for a new class is not a bad idea though.

 

Well yeah, I am doing that as much as possible. But some classes are just getting large no matter what. Since we are on gamedev take a creature class. It holds a ton of mutable properties like many stats and equipment, position, effects, etc. These all belong to that creature. I can abstract all stats to there own implementation but creature would still hold a stats object, likewise for it's inventory and equipment, etc. If we would make all these properties final then each time a player moves I have to create a new creature with all these properties in it's constructor. There is only so much to abstract unless we rip apart the creature class and map everything separately which seems ridiculous to me and would run into the same issues for the class that is implementing this.

Map<Integer, Creature> CreatureMap = new HashMap<Integer, Creature>();
Map<Integer, Stats> statsMap = new HashMap<Integer, Stats>();
Map<Integer, Equipment> equipmentMap = new HashMap<Integer, Equipment>();

public void createCreature()
{
  int index = getNextIndex();
  creatureMap.put(index, new Creature())
  statsMap.put(index, new Stats());
  equipmentMap.put(index, new Equipment);
}

public void mutateCreature(int index)
{
  creatureMap.put(index, creatureMap.get(index).mutateSomething());
}

This looks crazy to me (but is it? I'm trying to learn something here and my assumptions or thinking pattern might be completely wrong), the creature should have it's properties inside it. Some properties can be abstracted in there own classes if that makes common sense but one can still end up with an object that has many properties. If we do what Yegor suggests we do, making all these properties final then we inevitably end up with large constructors or a builder pattern with a large amount of mandatory properties.

Edited by menyo

Share this post


Link to post
Share on other sites

There's the question of whether you are going for pure, dogmatic object-orientation, or something more pragmatic.  Sometimes, you have chunks of data that make sense to be grouped together, and don't really have any behavior associated with them.  Go wild with getters and setters there - it's a lot more future-proof and change-friendly than relying on public fields, instead, when it becomes necessary.  Even better if you're using a programming language that has support for C#-style properties that sugar over the getter-setter boilerplate.

 

Especially if you are trying to program in a more functional style, you end up wanting lots of more behavior-poor data objects with public getters, though you would tend towards keeping your setters private, as leaning harder towards immutability tends to be the better choice.

Share this post


Link to post
Share on other sites

Like Samoth said, while in many (or in my experience, most) cases it results in bloat and overengineering, using getters and setters is, in some cases, necessary. I personally use them only ever so occasionally when I am absolutely sure that is the type of encapsulation I need. Basic getters and setters do not provide thread safety, they do not result in cleaner code and in my perspective they do not provide too much safety in the way of accidentally writing into a raw member variable. Unless... you have a reason to classify access to these member variables, that is.

 

A somewhat more interesting scenario is providing a seaparate accessor structure, which then friends only those classes that should have access to your base structure. Eg:

struct base {
   private:
     friend struct base_accessor;

     int a;
};

struct base_accessor {
    private:
      friend struct interface1;
      friend struct interface2;

     public:
       inline void set_a(base& b, int a) const { b.a = a; }
       inline int get_a(base& b) const { return b.a; }
}

Now only interface1 and interface2 have access to base::a and need to do so via an instance of base_accessor. My engine codes uses this to limit certain components from accidentally writing into a class to limit accessibility to a given thread. Also, this way you can separate the write interface from the read interface by defining a base_writer and a base_reader, should you need to.

 

Also, getters and setters are invaluable when you're maintaining an intermediate variable, which might be updated only when one of its component variables changes. If you do use them on raw variables, you should, in the very least, declare them inline and make the getter const so you'll have const correctness.

 

Share this post


Link to post
Share on other sites

As usual, you should not take everything too seriously.

 

There is a common behavioural pattern of someone doing something, then someone else noticing that it's actually a clever idea and calling it "best practice". Then someone calls it a "pattern", and soon a witch hunt starts against anyone not applying the pattern. A year later, someone says "considered evil" and the word "antipattern" comes up, and the same witch hunt starts in the opposite direction. And then, another year later, someone says "no longer considered evil".

I can't upvote this enough ; )

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!