• Advertisement
Sign in to follow this  

Get/Set Methods In OOD

This topic is 3312 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

What's the purpose of get/set methods when I can just declare the member public? Doesn't this do virtually the same thing?

Share this post


Link to post
Share on other sites
Advertisement
First, I'm going to assume that you mean plain get/set that don't add behavior; further, that both of them exist for a particular datapoint.

In that case, it's done as a matter of future-proofing. Should you ever need to change the details of a class (say, adding in some temporary debugging logic) you don't need to go through every single bit of code that uses the data and change it to use the new get/set methods rather than the object itself. It's part of the motivation behind C#'s property design. That sort of thing isn't necessary there. In Java or C++ the interface changes when you change a public member into a get/set pair.

Share this post


Link to post
Share on other sites
It's useful when you might want to perform secondary actions, like recalculating or caching a calculated value. Sometimes you don't know ahead of time when you'll want to add some new functionality that will require adding secondary effects in a get/set method.

It's also useful when other people are going to use your objects. They can look at *just* the object's methods instead of wondering which variables they should be tinkering with.

Finally, it's useful in designing objects since it helps you "program to an interface" as opposed to "programming to an implementation".

Share this post


Link to post
Share on other sites
Quote:
Original post by Kenny77
What's the purpose of get/set methods when I can just declare the member public? Doesn't this do virtually the same thing?



Its a convention. The generalization allows you to later add validation code for the set values if needed and can simplify the get/set if the real data element(s) is buried more complexly in the class data structure.

More need in future will be facilitating a multiprocess data access wrapper the detaisl of which can be hidden.

Of course making it a method you can do the whole class override mechanism.

Share this post


Link to post
Share on other sites
Also, having a get and no set is a way to create "read only" public access while using any sort of backing implementation.

Share this post


Link to post
Share on other sites
If you want to look it up, you'll find it under abstraction and information hiding.

Share this post


Link to post
Share on other sites
Quote:
Original post by Kenny77
What's the purpose of get/set methods when I can just declare the member public?

You shouldn't do either. get/set methods are not what OO is about. The class should be operating on the encapsulated data instead.

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
It's part of the motivation behind C#'s property design. That sort of thing isn't necessary there. In Java or C++ the interface changes when you change a public member into a get/set pair.


I've recently added a small c++ dump to wikipedia, showing that properties are simply unnecessary in c++ as they can be "emulated" by means of other features:

#include <iostream>

template <typename T> class strictly_typed_property {
T value;
public:
T & operator = (const T &i) {
::std::cout << i << ::std::endl;
return value = i;
}
// This template class member function template serves the purpose to make
// typing more strict. Assignment to this is only possible with exact identical
// types.
template <typename T2> T2 & operator = (const T2 &i) {
::std::cout << "T2: " << i << ::std::endl;
T2 &guard = value;
throw guard; // Never reached.
}
operator T const & () const {
return value;
}
};

struct Foo {
// Properties using unnamed classes.
class {
int value;
public:
int & operator = (const int &i) { return value = i; }
operator int () const { return value; }
} alpha;

class {
float value;
public:
float & operator = (const float &f) { return value = f; }
operator float () const { return value; }
} bravo;
};

struct Bar {
// Using the property<>-template.
strictly_typed_property <bool> alpha;
strictly_typed_property <unsigned int> bravo;
};

int main () {
Foo foo;
foo.alpha = 5;
foo.bravo = 5.132f;

Bar bar;
bar.alpha = true;
bar.bravo = true; // This line will yield a compile time error
// due to the guard template member function.
::std::cout << foo.alpha << ", "
<< foo.bravo << ", "
<< bar.alpha << ", "
<< bar.bravo
<< ::std::endl;
}



Basically it means that you can add temporary debugging features without get/set-pairs in c++, too. While the syntax is maybe a bit clumsy to c++ newcomers, it really adds another safety barrier, as the physical memory region can be hidden from the containing class. As far as I know, the latter is not possible in c# (but then I am not as fluent in c# as in c++). As far as I know, the latter is only possible with an additional named class:

class SomeClass {
private class SomeIntProperty {
private int someValue;
public SomeValue {
set { someValue = value; }
get { value = someValue; }
}
}
SomeIntProperty someInt = new SomeIntProperty ();
public SomeInt {
set { someInt = value; }
get { value = someInt; }
}
}

Share this post


Link to post
Share on other sites
Quote:

What's the purpose of get/set methods when I can just declare the member public? Doesn't this do virtually the same thing?


The purpose is to add an extra layer, where you can operate on the data prior to it being get/set.

You can use that for debugging purposes, or if you need to add something in later that you didn't think about originally (ie. a hack or a new feature) you can do so easily.

I've worked on afew big projects recently with work and have had to do both of the above. I can say 95% of the time it just means extra code, for no benefit, and just wastes time. But that 5% when i can just change something in a get/set more than makes up for the extra time spent writing them in the first place, and saved so many headaches. For a smaller project it may not be so useful.

Quote:

You shouldn't do either. get/set methods are not what OO is about. The class should be operating on the encapsulated data instead.


Can you explain this abit more?


Share this post


Link to post
Share on other sites
or used for example to validate the setted value.
example: a int member that can only go from 0 to 1000.

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
properties are simply unnecessary in c++ as they can be "emulated" by means of other features:


Yes, I suppose technically that is correct. Practically though, that's full of suck. It's like saying C++ doesn't need delegates because someone could take a few years and concoct the black magic required to get boost::function working. It works, but it'd be a lot easier and cleaner if the language had the concept in mind.

Quote:

While the syntax is maybe a bit clumsy to c++ newcomers,


A bit clumsy for anyone compared to C#'s.

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
I've recently added a small c++ dump to wikipedia, showing that properties are simply unnecessary in c++ as they can be "emulated" by means of other features:

*** Source Snippet Removed ***

I'm a little confused as to what the purpose of that code is. Do you really think that properties are only useful for debugging?

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Quote:
Original post by phresnel
I've recently added a small c++ dump to wikipedia, showing that properties are simply unnecessary in c++ as they can be "emulated" by means of other features:

*** Source Snippet Removed ***

I'm a little confused as to what the purpose of that code is. Do you really think that properties are only useful for debugging?


Fear not, I don't do so (and actually never have done). I was answering on Telastyns example

Quote:
Should you ever need to change the details of a class (say, adding in some temporary debugging logic) [...]

.



Quote:
Telastyn
Yes, I suppose technically that is correct. Practically though, that's full of suck. It's like saying C++ doesn't need delegates because someone could take a few years and concoct the black magic required to get boost::function working. It works, but it'd be a lot easier and cleaner if the language had the concept in mind.


That is another story, delegates are something I liked in c++. But (IMHO!) 1st class properties would be pure sugar to anyone halfway fluent in c++.

Share this post


Link to post
Share on other sites
Quote:
Original post by supamike
Quote:

You shouldn't do either. get/set methods are not what OO is about. The class should be operating on the encapsulated data instead.

Can you explain this abit more?

OO is, amongst other things, about encapsulating data. Making the data private and then providing getters and setters for the data violates that principle. The functions operating on the data should be contained within the class.

Share this post


Link to post
Share on other sites
They are usally used to stop data being accessed directly so you add some validation to it. You don't want another object changing the varible, when the object containing the member doesn't know what to do with it when it's outside a certain range. That would course a error.

You don't wan't to many getters and setters as that means your proabbly doing oo wrong. Things that operate on object should be contained in the object it's self.

Such as

Money.Convert("Stirling");

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
Fear not, I don't do so (and actually never have done). I was answering on Telastyns example

That still doesn't answer my question. The code you posted doesn't allow property getter/setters to access the encompassing class, making them essentially useless as far as doing anything you'd actually want to do with properties (other than print a debugging message, I suppose).

Share this post


Link to post
Share on other sites
Quote:
Original post by Alastair Gould
They are usally used to stop data being accessed directly so you add some validation to it.



This, but they are not only for validation, but also to supply different perspectives onto the same (maybe blackboxed) data. Imagine a vector, for which you supply functions to handle them like polar coordinates or cartesian ones. Or it would be quite handy to have methods to interpret the same angle as radians on the one, and degrees on the other hands (of course this could then be combined with the blackbox vector). But then this all is no longer pure get/set-philosophy, but rather points out why get/set is not the alpha and the omega, and should only be used where it makes sense (also look at this item about friends).

Share this post


Link to post
Share on other sites
excessive get/set is a sign of bad design. whoever said proper OO is about encapsulation is right. I'm of the school that an object should take its specification on creation, enforce its constraints and then that's it.

Share this post


Link to post
Share on other sites
Quote:
Original post by DevFred
Quote:
Original post by supamike
Quote:

You shouldn't do either. get/set methods are not what OO is about. The class should be operating on the encapsulated data instead.

Can you explain this abit more?

OO is, amongst other things, about encapsulating data. Making the data private and then providing getters and setters for the data violates that principle. The functions operating on the data should be contained within the class.


Sure, writing get/set for each and every one of your private data is sign of bad design, but some properties are meant to be public. Like the color of a Renderable. You could name it SetColor(),ReColor(),RePaint() or what have you, but in essense it's a setter. I think there's a line between encapsulating and making your objects xenophobic.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Quote:
Original post by phresnel
Fear not, I don't do so (and actually never have done). I was answering on Telastyns example

That still doesn't answer my question. The code you posted doesn't allow property getter/setters to access the encompassing class, making them essentially useless as far as doing anything you'd actually want to do with properties (other than print a debugging message, I suppose).


I see. In that case, you can still cherry pick what you pipe into the properties, let it be single primitive types or the whole parent class, let them be mutable or const (see class Foo::Delta):

struct Foo {
// Properties using unnamed classes.
class Alpha {
int value;
public:
int & operator = (const int &i) { return value = i; }
operator int () const { return value; }
} alpha;

// Cherry picking.
class Bravo {
float value;
Alpha & alpha;
public:
float & operator = (const float &f) { alpha = 0xdeadbeef; return value = f; }
operator float () const { return value; }
Bravo (Alpha &#945;) : alpha (alpha) {};
} bravo;

// Alias for bravo. No cherry picking this time.
class Charlie {
Foo & foo;
public:
float & operator = (const float &f) { return foo.bravo = f; }
operator float () const { return foo.bravo; }
Charlie (Foo &foo) : foo (foo) {};
} charlie;

// Readonly alias for bravo. No cherry picking this time.
/*
class Delta {
const Foo & foo;
public:
float & operator = (const float &f) { return foo.bravo = f; } // < compile time error, as foo is immutable
operator float () const { return foo.bravo; }
Delta (const Foo &foo) : foo (foo) {};
} delta;
*/


Foo () : bravo (alpha), charlie (*this), delta (*this) {}
};

int main () {
Foo foo;
foo.charlie = 5.132f;

::std::cout << ::std::hex << foo.alpha << ", "
<< foo.bravo << ", "
<< ::std::endl;
}


Allowedly, Foo will grow and grow in bytesize. So as for the size, clearly an advantage for c#. But as for the degree of freedom w.r.t. security levels, I'd say advantage for c++. But all in all, I am unsure which properties and property-hacks are better. C++ is good if you are paranoid, and make everything const- and restricted-first. But c#'s syntax is clearly the easier and more compact, plus it doesn't need additional storage.

To give a more realistic example:


#include <iostream>

class Angle {
float value;

class Radian {
float &value;
public:
float & operator = (const float &f) { return value = f; }
operator float () const { return value; }
Radian (float &value) : value (value) {}
};

class Degree {
float &value;
public:
float & operator = (const float &f) { return value = f * 0.0174532925f; }
operator float () const { return value * 57.2957796f; }
Degree (float &value) : value (value) {}
};

public:
Radian radian;
Degree degree;

Angle () : radian (value), degree (value) {}
};


int main () {
Angle alpha;
alpha.degree = 180.0f;
::std::cout << alpha.degree << " degrees = " << alpha.radian << " radians\n";

alpha.radian = 3.1415927f * 0.5f;
::std::cout << sizeof (alpha) << "]" << alpha.degree << " degrees = " << alpha.radian << " radians\n";
}


or just (main function skipped)

class Angle {

class Degree {
float &value;
public:
float & operator = (const float &f) { return value = f * 0.0174532925f; }
operator float () const { return value * 57.2957796f; }
Degree (float &value) : value (value) {}
};

public:
float radian;
Degree degree;

Angle () : degree (radian) {}
};


compared to

class Angle {
float value;


public float Radian {
get { return this.value; }
set { this.value = value; }
}

public float Degree {
get { return this.value * 57.2957796f; }
set { this.value = value * 0.0174532925f; }
}
};

namespace ConsoleApplication1 {
class Program {
static void Main (string [] args) {
Angle alpha = new Angle ();
alpha.Degree = 180.0f;
System.Console.WriteLine ("{0} degrees = {1} radians", alpha.Degree, alpha.Radian);

alpha.Radian = 3.1415927f * 0.5f;
System.Console.WriteLine ("{0} degrees = {1} radians", alpha.Degree, alpha.Radian);
}
}
}


or just (main function skipped)

class Angle {
public float Radian;

public float Degree {
get { return Radian * 57.2957796f; }
set { Radian = value * 0.0174532925f; }
}
}


.





Quote:
Original post by mikeman
Sure, writing get/set for each and every one of your private data is sign of bad design, but some properties are meant to be public. Like the color of a Renderable. You could name it SetColor(),ReColor(),RePaint() or what have you, but in essense it's a setter. I think there's a line between encapsulating and making your objects xenophobic.


There the divergence already begins, as in my world the things I render can have a different color at each spacetime-point on it's surface (yes, it's about a ray tracer), making it impossible to supply a virtual SetColor method for all of them (just imagine spheres, cubes, player-models, or whole terrain including flora).




edit: But one thing I dislike about properties in general: They look like peas when used, but can really be coconuts. I like the convention where you name your functions like convertToYYY() or computeZZZ(), when some work is done internally, so fellow programmers don't underestimate a line of code in performance when in eye parse mode. And for the same reason, I virtually never use get/set-calls, because it makes sense only in the rarest conditions (that is, in my code).


Also, I really wouldn't use the above Degree/Radian-Angle class, as it is hard to analyze which form is used more often in general (and based on that knowledge to decide which form is first class), but the wrong decision would introduce a plethora of unneccessary multiplications. So I would instead provide two distinct classes (again using the radian/degree case, but you could also apply this to other things, e.g. normal vectors / general vectors / points):


// NOTE: if you detect a "&deg;" somewhere in the following,
// please replace it with an actual "&" and a following "deg"
namespace {
const float to_degree = 57.2957796f;
const float to_radian = 0.0174532925f;
}

class Degree;

class Radian {
float value;
public:
Radian (const float &init) : value (init) {}

explicit inline Radian (const Degree &);
inline operator Degree () const ;
inline Radian & operator = (const Degree &rad);

operator float () const { return value; }
float & operator = (const float &f) { return value = f; }
};

class Degree {
float value;
public:
Degree (const float &init) : value (init) {}

explicit inline Degree (const Radian &rad) ;
inline operator Radian () const ;
inline Degree & operator = (const Radian &rad);

operator float () const { return value; }
float & operator = (const float &f) { return value = f; }
};

inline Radian::Radian (const Degree & deg) : value (to_radian * deg) {}
inline Radian & Radian::operator = (const Degree & deg) { value = to_radian * deg; return *this; }
inline Radian::operator Degree () const { return Degree (value * to_degree); }

inline Degree::Degree (const Radian &rad) : value (to_degree * rad) {}
inline Degree & Degree::operator = (const Radian &rad) { value = to_degree * rad; return *this; }
inline Degree::operator Radian () const { return Radian (value * to_radian); }



This erases hidden computations, and introduces additional ones only on demand:

int main () {
Degree deg = 90.0f;
Radian radA (deg);
Radian radB = deg;

::std::cout << deg << " degrees = " << radA << " radians = " << radA << " radians\n";

Degree degB = 45.0f + deg + 45.0f + (Degree)radA;
::std::cout << "degB = " << degB << "\n";
}


[Edited by - phresnel on January 26, 2009 8:23:40 AM]

Share this post


Link to post
Share on other sites
That's all well and good, phresnel, but those implementations of properties in C++ will only allow basic types (int, float, bool, etc). Meaning you can't store a property of a class, and access the class members/methods, at least without forcing operator -> and pointers. Versus dot syntax and references with get/set functions. :-/

Share this post


Link to post
Share on other sites
Quote:
Original post by Dae
That's all well and good, phresnel, but those implementations of properties in C++ will only allow basic types (int, float, bool, etc). Meaning you can't store a property of a class, and access the class members/methods, at least without forcing operator -> and pointers. Versus dot syntax and references with get/set functions. :-/


Veto:


#include <iostream>

enum { debug = 0 };

class UberFloat {
float value;
public:
float & operator = (const float &f) {
if (debug) { ::std::cout << "UberFloat::operator = (" << f << ")" << ::std::endl; }
return value = f;
}

float & operator = (const UberFloat & f) {
if (debug) { ::std::cout << "UberFloat::operator = (" << f << ")" << ::std::endl; }
return value = f.value;
}

operator float () const {
if (debug) { ::std::cout << "UberFloat::operator float () (" << value << ")" << ::std::endl; }
return value;
}

UberFloat (float value = 0.0f) : value (value) {}

friend const UberFloat operator + (const UberFloat &lhs, const UberFloat &rhs);
friend const UberFloat operator + (const float lhs, const UberFloat &rhs);
friend const UberFloat operator + (const UberFloat &lhs, const float rhs);
friend std::ostream& operator<< (std::ostream& o, const UberFloat& uber);
};

const UberFloat operator + (const UberFloat &lhs, const UberFloat &rhs) {
return UberFloat (lhs.value + rhs.value);
}
const UberFloat operator + (const float lhs, const UberFloat &rhs) {
return UberFloat (lhs + rhs.value);
}
const UberFloat operator + (const UberFloat &lhs, const float rhs) {
return UberFloat (lhs.value + rhs);
}
std::ostream& operator<< (std::ostream& o, const UberFloat& uber) {
return o << uber.value << "uf";
}
UberFloat, an imaginary class type.


template <typename T> class Angle {
T value;

class Radian {
T &value;
public:
T & operator = (const T &f) { value = f; return value; }
operator const T & () const { return value; } // Give back reference to T.
Radian (T &value) : value (value) {}
};

class Degree {
T &value;
public:
T & operator = (const T &f) { value = f * 0.0174532925f; return value; }
operator float () const { return value * 57.2957796f; } // Give back a float.
Degree (T &value) : value (value) {}
};

public:
Radian radian;
Degree degree;

Angle () : radian (value), degree (value) {}
};
Angle<>, (note how Angle<T>::Radian::operator const T& returns a reference of type T, while Angle<T>::Degree::operator float gives back a float; this is just for demonstration purposes)


template <typename T> void test () {
Angle<T> alpha;
alpha.degree = 180.0f;
::std::cout << alpha.degree << " degrees = "
<< alpha.radian << " radians\n";

alpha.radian = 3.1415927f * 0.5f;
alpha.radian = alpha.radian + 3.1415927f;
::std::cout << alpha.degree << " degrees = " << alpha.radian << " radians\n";
}

int main () {
test <UberFloat>();
test <float>();
}
A test, including a primitive type and a class type.

Output:
180 degrees = 3.14159uf radians
270 degrees = 4.71239uf radians
180 degrees = 3.14159 radians
270 degrees = 4.71239 radians


Note how radians are printed with the "uf"-postfix in the first test. This is because Angle<UberFloat>::Radian::operator const UberFloat & () gives back a reference to UberFloat, for which we defined an operator <<, which outputs the underlying value plus the "uf"-postfix. Whereas for Angle<>::Degrees we only defined an operator float ().


Still, I really don't use such constructs. See my sooner peas/coconuts example.

[Edited by - phresnel on January 28, 2009 4:56:59 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnelVeto:



That's nice phresnel, but not what I meant. Sorry, I meant basic syntax:

#include <iostream>

class A
{
public:
int roar;
A() : roar(5) { }
};

class B
{
public:
class
{
A value;
public:
A& operator = (const A &a) { return value = a; }
operator A() const { return value; }
} property;
};

int main()
{
B b;

std::cout << A(b.property).roar; // have to cast or use -> operator, can't use dot operator - unlike C# - ugly syntax either way
//std::cout << b.property.roar; // :-(
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Dae
Quote:
Original post by phresnelVeto:



That's nice phresnel, but not what I meant. Sorry, I meant basic syntax:

*** Source Snippet Removed ***


Now it's getting interesting [smile]

#include <iostream>

struct A {
int roar;
A(int roar=5) : roar(roar) { }
};

struct B {


The ugly solution as you mentioned:

// The cherry pick or ugliness solution.
class property_t {
A value;
public:
A& operator = (const A &a) { return value = a; }
operator A() const { return value; }

property_t () : roar (value.roar) {}

// Ugly operators.
A const * const operator -> () const { return &value; }
A * const operator -> () { return &value; }

// Can make it a property again.
int &roar;
} property;


The more interesting solution:

// Allow member access, still can validate.
struct : public A {
A& operator = (const A &a) {
if (a.roar > 42)
::std::cout << "Out of cheese error. Redo from start." << ::std::endl;
A::operator = (a);
return *this;
}
operator A() const { return *this; }
} property2;
};


And the test:
int main()
{
B b;

::std::cout << A(b.property).roar << ::std::endl;

b.property->roar = 42;
::std::cout << b.property->roar << ::std::endl;

b.property.roar = 42 * 2;
::std::cout << b.property.roar << ::std::endl;

b.property2.roar = 42 * 4;
::std::cout << b.property2.roar << ::std::endl;

b.property2 = A (42 * 4);
::std::cout << b.property2.roar << ::std::endl;
}


Plus the output:

5
42
84
Out of cheese error. Redo from start.
168



edit: But clearly (that is, after a coffee break), "the more interesting solution" fails to inject code there:

b.property2.roar
^ here ^


So, the last (not serious, don't use! it will break all your code and stuff! really!) resort to enable dot-notation on c++-properties is to fall back to preprocessor romanticism:

**** snip ****
// Allow member access, still can validate.
struct : public A {
A& operator = (const A &a) {
if (a.roar > 42)
::std::cout << "Out of cheese error. Redo from start." << ::std::endl;
A::operator = (a);
return *this;
}
A &rebind () {
::std::cout << "Gerbils are the better programmers." << ::std::endl;
return *this;
}
} property2;
};

#define property2 property2.rebind()
int main()
{
B b;
b.property2.roar = 42;
::std::cout << b.property2.roar << ::std::endl;
}


Output:
Gerbils are the better programmers.
42


[Edited by - phresnel on January 28, 2009 6:55:37 AM]

Share this post


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

  • Advertisement