getters & setters are bad, design patterns etc.

Pages: 1... 567
TheIdeasMan wrote:
and MyArea is just a variable to put the answer in, rather than returning a value from a function.
That's counter-intuitive, why would a getter have that semantic?

I don't see what 'being restricted to one return type' has to do with what I'm trying to say here.

I'll show you two different implementations of a rectangle class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Rectangle
{
    //Implementation 1:
    Vect2 center, size;

    //Implementation 2:
    Vect2 topleft, bottomright;

   //All Implementations:
public:
    Rectangle(Vect2 center, Vect2::value_t width, Vect2::value_t height);
    Rectangle(Vect2 topleft, Vect2 bottomright);
//  Rectangle(Rectangle const &) = default;
//  Rectangle(Rectangle &&) = default;
//  Rectangle &operator=(Rectangle const &) = default;
//  Rectangle &operator=(Rectangle &&) = default;
//  ~Rectangle() = default;

    Vect2 TopLeft() const;
    Vect2 TopCenter() const;
    Vect2 TopRight() const;
    Vect2 CenterLeft() const;
    Vect2 Center() const;
    Vect2 CenterRight() const;
    Vect2 BottomLeft() const;
    Vect2 BottomCenter() const;
    Vect2 BottomRight() const;

    Vect2 Size() const;
};
It's also worth noting that the above interface is also suitable if the implementation uses a different coordinate system. Also, allowing for negative width and height is intentional here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//Implementation 1
Rectangle::Rectangle(Vect2 center, Vect2::value_t width, Vect2::value_t height)
: center(center)
, size(Vect2(width, height))
{
}

Rectangle::Rectangle(Vect2 topleft, Vect2 bottomright)
: center((topleft+bottomright)/2)
, size(bottomright-topleft)
{
}

Vect2 Rectangle::TopLeft() const
{
    return center - size/2;
}
Vect2 Rectangle::TopCenter() const
{
    return center - Vect2(0, size.y)/2;
}
Vect2 Rectangle::TopRight() const
{
    return center + Vect2(size.x, -size.y)/2;
}
Vect2 Rectangle::CenterLeft() const
{
    return center - Vect2(size.x, 0)/2;
}
Vect2 Rectangle::Center() const
{
    return center;
}
Vect2 Rectangle::CenterRight() const
{
    return center + Vect2(size.x, 0)/2;
}
Vect2 Rectangle::BottomLeft() const
{
    return center - Vect2(size.x, -size.y)/2;
}
Vect2 Rectangle::BottomCenter() const
{
    return center + Vect2(0, size.y)/2;
}
Vect2 Rectangle::BottomRight() const
{
    return center + size/2;
}

Vect2 Rectangle::Size() const
{
    return size;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//Implementation 2
Rectangle::Rectangle(Vect2 center, Vect2::value_t width, Vect2::value_t height)
: topleft(center - Vect2(width, height)/2)
, bottomleft(center + Vect2(width, height)/2)
{
}

Rectangle::Rectangle(Vect2 topleft, Vect2 bottomright)
: topleft(topleft)
, bottomright(bottomright)
{
}

Vect2 Rectangle::TopLeft() const
{
    return topleft;
}
Vect2 Rectangle::TopCenter() const
{
    return topleft + Vect2(Size().x, 0)/2;
}
Vect2 Rectangle::TopRight() const
{
    return topleft + Vect2(Size().x, 0);
}
Vect2 Rectangle::CenterLeft() const
{
    return topleft + Vect2(0, Size().y)/2;
}
Vect2 Rectangle::Center() const
{
    return (topleft + bottomright)/2;
}
Vect2 Rectangle::CenterRight() const
{
    return bottomright - Vect2(0, Size().y)/2;
}
Vect2 Rectangle::BottomLeft() const
{
    return topleft + Vect2(0, Size().y);
}
Vect2 Rectangle::BottomCenter() const
{
    return bottomright - Vect2(Size().x, 0)/2;
}
Vect2 Rectangle::BottomRight() const
{
    return bottomright;
}

Vect2 Rectangle::Size() const
{
    return bottomright-topleft;
}
As you can see, despite the different implementations, the public interface is exactly the same in both cases. And, if Vect2 is a class you control or an alias, you can easily determine whether whole numbers or floating point numbers are supported without having to change anything in the Rectangle class.
@L B

OK, I see what you are saying & how you have done it. Thanks for your time & effort - cheers!

For the record, my example code was from the class tutorial on this site and not from anything I was working on. I noticed someone kept asking what LB's examples and the int x,y had to do with my Rectangle class. It isn't mine, I just borrowed it from the tutorial for a point I was arguing at that time. I've come to find that in programming terms there are programmers that agree with my point of it not breaking encapsulation while others agree with LB on how it breaks encapsulation so I have just come to accept it depends on the programmers interpretation of the term encapsulation.
Encapsulation has a definite definition, it is theoretically optimal in programming, and it should not depend on who you talk to. Different people might misunderstand the true definition in different ways, though. I can't guarantee that my understanding is 100% correct either, so feel free to argue my statements :)
As you can see, despite the different implementations, the public interface is exactly the same in both cases. And, if Vect2 is a class you control or an alias, you can easily determine whether whole numbers or floating point numbers are supported without having to change anything in the Rectangle class.


You've just changed which portion of the interface needs to be changed. If the interface needs to change it still impacts the client code. The fact that it impacts one area of "our" code and not another isn't particularly relevant. I could get the same level of 'encapsulation' for our value type using a typedef.

[edit: Obviously, if this level of flexibility in the underlying type is called for we would be using templates.]
Last edited on
@cire: I'm not sure what you mean?
TheIdeasMan wrote:
... and MyArea is just a variable to put the answer in, rather than returning a value from a function.


L B wrote:
That's counter-intuitive, why would a getter have that semantic?

I don't see what 'being restricted to one return type' has to do with what I'm trying to say here.


Well the idea was to provide the user with a choice of type for the value they were after. To do this I had overloaded functions. In order to overload them I had references to different types as parameters.

As cire has just said (& me a couple of times, earlier) templates would be better. And Cubbi's example of the boost library much earlier.

I guess the reason why one might want different types is, that at some point a calculation needs to be done, so we need a variable of a particular type to do it with. And we might need multiple types, as in a CAD system which has a circular selection area (unsigned) as well a Circle dwg object (double say) .

Also, one of main reasons that prompted this topic was the idea mentioned in the Java world article link posted by ne555 paraphrased as this: "If the type changes in the future then all the client code that uses it is broken "

@BHXSpecter

Sometimes tutorials provide simplistic examples because they are aimed at beginners. Unfortunately they are so simplistic that they actually promote bad practice. The example you posted is bad IMO mainly because it has the setter function instead of a constructor with an initialiser list. As mentioned earlier, if one wants to change the values after initialisation, then think about why, and provide well behaved functions that implement this.

Having said that, the vast majority of example code in the reference section on this site is very useful & educational.
TheIdeasMan wrote:
Sometimes tutorials provide simplistic examples because they are aimed at beginners. Unfortunately they are so simplistic that they actually promote bad practice. The example you posted is bad IMO mainly because it has the setter function instead of a constructor with an initialiser list. As mentioned earlier, if one wants to change the values after initialisation, then think about why, and provide well behaved functions that implement this.

@TheIdeasMan
Well I can agree that if you understand constructors and using them instead of setters is a better practice. Though, your last line shows why setters and getters aren't bad. They are poor practice to use just those, but there may be times, as you just pointed out, where you have to set new data for a field and require setters for each member. It does seem majorly odd to me for someone to say getters are bad practice though as in some cases you, again, need only one part of the data to verify it is about to show you the right data.

For example, an address book, you may have to update the name (maybe a woman you know got married or divorced so her last name has changed). You may have to update their number(s) or address and need to set just that section of data. You could do a search feature that allows the user to search through the names to find the number they need rather than just printing all the data for them to read through all data stored for the address book. You can certainly use a constructor to set the initial data but people do change numbers quite a bit and some move a lot so you have to be able to update just the address field or number field.

Though, I obviously don't grasp enough to have this debate so I will remove myself from it. I apparently don't understand encapsulation either so I'll just leave everyone else that has a better grasp to discuss this topic. :)
BHXSpecter wrote:
You can certainly use a constructor to set the initial data but people do change numbers quite a bit and some move a lot so you have to be able to update just the address field or number field.


Yeah, that's right, but it's set functions which are straight assignment & no checking that we are all unhappy about. I am guessing you wouldn't like it if anyone could change your name or bank balance at anytime! I imagine a Name changing function would be a class function & might at least check a private boolean DocumentsVerified variable, which is set via another function.

So that is an example of an Update function with some validation, which is what I meant about a 'well behaved function'. This type of function is probably not the definition of a setter function.

Also, that example involves only 1 object, you can imagine how bad things could be when it's a transaction between 2 or more objects. I think that was discussed a bit earlier.

Though, I obviously don't grasp enough to have this debate so I will remove myself from it. I apparently don't understand encapsulation either so I'll just leave everyone else that has a better grasp to discuss this topic. :)


Don't worry, everyone is entitled to have their say.
@TheIdeasMan: [quote=username]text[/quote]
Topic archived. No new replies allowed.
Pages: 1... 567