iDevGames Forums

Full Version: Data Encapsulation Help (newbie)
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Hello. (This is all Objective-C) I am having trouble grasping data encapsulation. It is used to access variables and change them directly in the main section/file, I know. But, here is my issue: Whenever I type my main section for a fractions program I am working with, I type:

[myFraction setNumerator: 1];
[myFraction setDenominator: 3];

And I (obviously) can change those integers, and have them changed when I run the program. If you can do that, why do you need to make a whole new method to "access the variables from your main file"? I don't understand the point of it if you can simply edit the integers/variables like that...

(And yes, I realize there is some kind of difference between editing the integers I typed up there and saying "numerator = ___; but I don't understand what the difference is!)

Obviously I am a newbie at this and am having a hard time grasping some of the concepts (although I am making a lot of progress) haha I will probably answer my own question here overnight! Rasp

Anyways, thanks. I am missing something and hope that one of you guys can help me out! Smile
I am not entirely certain I understand the question, but I'll give it a shot.

Typically, for each class you have a header file (e.g. myFraction.h) and an implementation file (e.g. myFraction.m). Each class may have some instance variables. From inside the implementation file the instance variables can be accessed directly (e.g. numerator = 1) OR indirectly through "accessors methods" (e.g. [myFraction setNumerator: 1]). From outside of the implementation file those instance variables may only be accessed through the accessors (e.g. [myFraction setNumerator: 1]).

So when would you use an accessor from inside the implementation file? I hardly ever do. The only times are if it is something where there is some constraint on the variable, such as I don't want it to ever be more than 1.0 or less than 0.0. Like let's say I do:

Code:
- (void)someMethod
{
   [self setValue:3.4];
}

- (void)setValue:(float)newValue
{
    value = newValue;

    if (value > 1.0f)
        value = 1.0f;
    if (value < 0.0f)
        value = 0.0f;
}

That seems pretty trivial since I should know better than to try to set it to 3.4, but let's say the 3.4 was a result of some other calculation:

Code:
- (void)someMethod:(float)input
{
    float result;
    result = [someInstance calculateResult:input];
    [self setValue:result];
}

Now all of a sudden we don't know what calculateResult is going to do to the input. While this is a contrived example, this is very much like something you'd see in real life programming. We want to be sure that the value is constrained between 0.0 and 1.0, so we access the value through the accessor, setValue, to be certain it is automatically constrained for us.

Another example would be for your fractions program. If you did denominator = 0 it would be undefined because you can't divide by zero! So to make sure that doesn't ever happen, always access the denominator through an accessor method:

Code:
- (void)setDenominator:(int)value
{
    denominator = value;
    if (denominator == 0)
        denominator = 1;
}

For the numerator, we don't have a special condition to watch out for, so I'd never use an accessor for it from within the implementation file, just use numerator = 1, or whatever value. Actually, I shouldn't say I'd *never* use an accessor for the numerator, since sometimes I might if it made the code look more consistent.

Code:
- (void)myMethod
{
    numerator = 1;
    [self setDenominator:3];
}

vs. this, which *looks* cleaner:

Code:
- (void)myMethod
{
    [self setNumerator:1];
    [self setDenominator:3];
}
Thanks.

So basically, whenever I edit the integer inside of the [myFraction setDenominator/Numerator: __] That is called an accessor? And data encapsulation is referring to directly editing it by doing something like numerator = __? Why do you even need to do that when you can just edit it how I did above? (using the accessor)
First, we don't say that we "edit a variable", we say that we "set a variable" (or maybe "assign a value to a variable"). I never see anyone say "edit" a variable. Minor nitpick, but it's just the lingo.

Next, let's take a few steps back and forget about encapsulation and accessors for a bit and focus on the fundamental topic here.

There is one and only one way to set a variable, and it looks like this:

numerator = 4;

Let that really sink in hard, and then we'll move on.
(Apr 4, 2013 11:25 PM)AnotherJake Wrote: [ -> ]First, we don't say that we "edit a variable", we say that we "set a variable" (or maybe "assign a value to a variable"). I never see anyone say "edit" a variable. Minor nitpick, but it's just the lingo.

Next, let's take a few steps back and forget about encapsulation and accessors for a bit and focus on the fundamental topic here.

There is one and only one way to set a variable, and it looks like this:

numerator = 4;

Let that really sink in hard, and then we'll move on.

Heh heh, alrighty. Got it. Rasp
Okay, so now we can look at where those variables are stored. There are three general types of variables: local, global, and instance.

The main difference between the variable types is where they can be accessed from. They are also stored differently in RAM, but that's another topic for another day. Briefly, local variables can only be accessed from within a block (i.e. within the curly braces {...}), global variables can be accessed from anywhere in the program, and instance variables can be accessed from anywhere within an instance of a class.

Instance variables are what we are focusing on here. Since they can only be accessed from within an instance of a class, anything outside of the class which wants to access those variables must do so by asking a method of that class to set or read a variable for it. That means that the variables are protected from the world outside of that class. This is what encapsulation means -- nothing but methods of that class can touch those instance variables. They are "encapsulated" within an instance of that class.

A method which has the sole purpose of accessing an instance variable on behalf of something else in the program is called an "accessor". They are also referred to as "getters" or "setters", because they get or set variables. Again, anything outside of the class must use an accessor to get to those encapsulated instance variables. HOWEVER, any method within a class has direct access to all instance variables of that class, so they have the option of setting the variables directly, by using an assignment operator (e.g. "="), OR they can go through an accessor which will use the assignment operator for them.
(Apr 5, 2013 07:00 AM)AnotherJake Wrote: [ -> ]Okay, so now we can look at where those variables are stored. There are three general types of variables: local, global, and instance.

The main difference between the variable types is where they can be accessed from. They are also stored differently in RAM, but that's another topic for another day. Briefly, local variables can only be accessed from within a block (i.e. within the curly braces {...}), global variables can be accessed from anywhere in the program, and instance variables can be accessed from anywhere within an instance of a class.

Instance variables are what we are focusing on here. Since they can only be accessed from within an instance of a class, anything outside of the class which wants to access those variables must do so by asking a method of that class to set or read a variable for it. That means that the variables are protected from the world outside of that class. This is what encapsulation means -- nothing but methods of that class can touch those instance variables. They are "encapsulated" within an instance of that class.

A method which has the sole purpose of accessing an instance variable on behalf of something else in the program is called an "accessor". They are also referred to as "getters" or "setters", because they get or set variables. Again, anything outside of the class must use an accessor to get to those encapsulated instance variables. HOWEVER, any method within a class has direct access to all instance variables of that class, so they have the option of setting the variables directly, by using an assignment operator (e.g. "="), OR they can go through an accessor which will use the assignment operator for them.

Ok. I get all that. Thanks.

So, whenever I do this:

[myFraction setNumerator: 1];
[myFraction setDenominator: 3];


and it allows me to change those numbers right then and there, I am using a method within the class? Or if not what is that doing? haha Would you like me to post the entire program? And the only reason that I am finding all this confusing, is because the book I am working out of is using extremely simple examples, which are so impractical that they make no sense to me... Ok..... Can you just give an example now? haha Thank you so much, I know it might be frustrating helping a newbie but you can say you taught somebody the basics Rasp

But, I honestly do think that the problem is not that I can't understand the concepts, it's just that whenever I see them implemented in very impractical examples, they make no sense, and I find ways (which will not work later in the long run because of more complexity) to get around them!
(Apr 5, 2013 07:17 AM)easy_e Wrote: [ -> ]So, whenever I do this:

[myFraction setNumerator: 1];
[myFraction setDenominator: 3];


and it allows me to change those numbers right then and there, I am using a method within the class? Or if not what is that doing?

That is from *outside* the class in your example. What you are doing is calling accessor methods on an instance of another class to set its instance variables for the numerator and denominator. If you were calling the accessors from *within* the class, you'd be using "self" instead of "myFraction".

Here is an example program for you which has a Color class. While it appears to be simple, this is something you will do in game development or any other graphics programming. Here we need colors to be represented by three channels -- red, green, blue -- and each channel needs to be between 0.0 and 1.0. We call this a color which is clamped between 0 and 1. In fact, you will do a lot of things in game development that are clamped between 0 and 1, not just colors.

You can create this project yourself in Xcode by creating an Appliction->Command Line Tool. On the next step, where you name it, select Core Foundation in the "Type" pop-up. Then change main.c to main.m. You will also need to create Color.h and Color.m and copy-paste all this junk into the respective files. Or you can just examine the output I provided below to see what happens.

main.m:
Code:
#import <CoreFoundation/CoreFoundation.h>
#import "Color.h"

int main(int argc, const char * argv[])
{
    Color *myColor = [[Color alloc] init];
    
    printf("%0.2f %0.2f %0.2f\n", [myColor red], [myColor green], [myColor blue]);
    
    [myColor setColorWithRed:1.4f green:0.0f blue:0.3f];
    printf("%0.2f %0.2f %0.2f\n", [myColor red], [myColor green], [myColor blue]);
    
    [myColor improvedSetColorWithRed:20000.12345f green:-890.0f blue:0.12f];
    printf("%0.2f %0.2f %0.2f\n", [myColor red], [myColor green], [myColor blue]);
    
    [myColor setRed:0.23f];
    [myColor printColorDirectly];
    [myColor printColorUsingAccessors];
    
    [myColor setGreen:0.643f];
    
    printf("Green:%0.6f\n", [myColor green]);
    
    return 0;
}

Main.m is just a trivial demonstration of how to use the Color class, using printf for formatted output. Note that since main.m is not within the Color class, we can't access red, green or blue directly and must instead use the accessor methods (e.g. [myColor red] ).

Color.h:
Code:
#import <CoreFoundation/CoreFoundation.h>

@interface Color : NSObject
{
    // instance variables -- only directly accessible from within Color.m
    float    red;
    float    green;
    float    blue;
}

// accessors
- (float)red; // getter
- (void)setRed:(float)r; // setter
- (float)green;
- (void)setGreen:(float)g;
- (float)blue;
- (void)setBlue:(float)b;

// other methods
- (void)setColorWithRed:(float)r green:(float)g blue:(float)b;
- (void)improvedSetColorWithRed:(float)r green:(float)g blue:(float)b;
- (void)printColorDirectly;
- (void)printColorUsingAccessors;

@end

Color.m
Code:
#import "Color.h"

@implementation Color

- (id)init
{
    self = [super init];
    if (self)
    {
        // - default to gray
        // - no point in using accessors to set these since we are
        // setting values that we know are between 0.0 and 1.0
        red = 0.5f;
        green = 0.5f;
        blue = 0.5f;
    }
    
    return self;
}

- (float)red
{
    return red;
}

- (void)setRed:(float)r
{
    red = r;
    
    // ensure red is not greater than 1.0 or less than 0.0
    if (red > 1.0f)
        red = 1.0f;
    if (red < 0.0f)
        red = 0.0f;
}

- (float)green
{
    return green;
}

- (void)setGreen:(float)g
{
    green = g;
    
    // ensure green is not greater than 1.0 or less than 0.0
    if (green > 1.0f)
        green = 1.0f;
    if (green < 0.0f)
        green = 0.0f;
}

- (float)blue
{
    return blue;
}

- (void)setBlue:(float)b
{
    blue = b;
    
    // ensure blue is not greater than 1.0 or less than 0.0
    if (blue > 1.0f)
        blue = 1.0f;
    if (blue < 0.0f)
        blue = 0.0f;
}

- (void)setColorWithRed:(float)r green:(float)g blue:(float)b
{
    // set colors directly
    red = r;
    green = g;
    blue = b;
    
    // ensure red is not greater than 1.0 or less than 0.0
    if (red > 1.0f)
        red = 1.0f;
    if (red < 0.0f)
        red = 0.0f;
    
    // ensure green is not greater than 1.0 or less than 0.0
    if (green > 1.0f)
        green = 1.0f;
    if (green < 0.0f)
        green = 0.0f;
    
    // ensure blue is not greater than 1.0 or less than 0.0
    if (blue > 1.0f)
        blue = 1.0f;
    if (blue < 0.0f)
        blue = 0.0f;
}

- (void)improvedSetColorWithRed:(float)r green:(float)g blue:(float)b
{
    // use accessors to automatically constrain between 0.0 and 1.0
    [self setRed:r];
    [self setGreen:g];
    [self setBlue:b];
}

- (void)printColorDirectly
{
    printf("%0.2f %0.2f %0.2f\n", red, green, blue);
}

- (void)printColorUsingAccessors
{
    printf("%0.2f %0.2f %0.2f\n", [self red], [self green], [self blue]);
}

@end

Note the use of "self" when calling the accessors.

setColorWithRed and improvedSetColorWithRed do the exact same thing, but improvedSetColorWithRed uses the accessors instead of duplicating the if statements to clamp the values. Either way is technically correct, so whichever one you use is up to your coding style. Same thing for printColorDirectly and printColorUsingAccessors.

Here's the output:
Code:
0.50 0.50 0.50
1.00 0.00 0.30
1.00 0.00 0.12
0.23 0.00 0.12
0.23 0.00 0.12
Green:0.643000
(Apr 5, 2013 08:35 AM)AnotherJake Wrote: [ -> ]
(Apr 5, 2013 07:17 AM)easy_e Wrote: [ -> ]So, whenever I do this:

[myFraction setNumerator: 1];
[myFraction setDenominator: 3];


and it allows me to change those numbers right then and there, I am using a method within the class? Or if not what is that doing?

That is from *outside* the class in your example. What you are doing is calling accessor methods on an instance of another class to set its instance variables for the numerator and denominator. If you were calling the accessors from *within* the class, you'd be using "self" instead of "myFraction".

[AnotherJake mod-snipped the rest of this quote to save space and make the thread easier to read.]

Oh ok, thanks.

Anyways, I cannot right now, but I will type up that project and begin studying it for more practice.

One more question (I think I'm going to have a lot of these over time - but I will not bother you with them anymore - don't worry) but, the id data type, why do you need it? As in, what is "storing an object"? I understand you store numbers in the form of integers, floats, etc, but I do not see how/why you "store" and object. Basically - what is storing an object?

^ LAST QUESTION, I promise Rasp

Thank you very, very much. You are helping a noob in need.
(Sorry for the double post, if that is frowned upon)


Anyways, Here. Maybe this will clear it up; this is my whole fractions program:



Code:
#import <Foundation/Foundation.h>

//INTERFACE SECTION

@interface Fraction: NSObject

-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;

@end

//IMPLEMENTATION SECTION

@implementation Fraction
{
    int numerator;
    int denominator;
}
-(void) print
{
    NSLog (@"%i/%i", numerator, denominator);
}

-(void) setNumerator: (int) n
{
    numerator = n;
}

-(void) setDenominator: (int) d
{
denominator = d;
}

@end

//PROGRAM SECTION

int main (int argc, char * argv[])
{
    @autoreleasepool {
        Fraction *myFraction;
        
        //Create an instance of a Fraction class
        
        myFraction = [Fraction alloc];
        myFraction = [myFraction init];
        
        //Set fraction to 1/3
        
        [myFraction setNumerator: 1];
        [myFraction setDenominator: 3];
        
        //Display the fraction using the print method
        
        NSLog (@"The value of myFraction is:");
        [myFraction print];
    }
    return 0;

[edited by AnotherJake -- we prefer to use code tags for code for better formatting and readability. The code tags can be added with the octothorp (i.e. the "#") in the post editor]
An instance is an object. An object is an instance of a class. We're just using slightly different terminology.

Yes, setNumerator and setDenominator are accessors. You are not insane Wink As I said, from within the class you would be using "self" instead of "myFraction". If you examine the example I put up, you will see this in action where I use "self" in Color.m but "myColor" from main.m. They both use the same accessor methods though.

Quote:...the id data type, why do you need it? As in, what is "storing an object"? I understand you store numbers in the form of integers, floats, etc, but I do not see how/why you "store" and object. Basically - what is storing an object?

Ah yes, that's the big question isn't it? Wink

I don't know if you understand much C yet or not, but here is something to consider:

Code:
typedef struct
{
    float    red;
    float    green;
    float    blue;
} Color;

void main(void)
{
    Color *myColor = malloc(sizeof(Color));
}

Here I'll offer a somewhat abstract explanation of that's going on. An object is not much more than a group of variables in a block of RAM. Here I've grouped the variables of the Color "class" in a C data structure. In main, I create an "instance" of that data structure by allocating memory for it using the standard C function, malloc, which is short for memory allocate, or something like that. malloc returns the address to the first variable at the top of the structure, called the base address. We call addresses "pointers". So malloc returns a pointer to myColor, which is a block of RAM just big enough to hold the three floats in the Color struct. In this case the "myColor" pointer is what is called "strongly typed" because I specify that it has the type, Color, right before the *myColor part.

Strong typing is *hugely* helpful in avoiding programming errors. However, strong typing means that any part of the program which needs to either store or make use of Color must have access to its definition:
Code:
typedef struct
{
    float    red;
    float    green;
    float    blue;
} Color;

This is sometimes impractical, so what we *could* do instead is do this:
Code:
void *myColor = malloc(sizeof(Color));

Here I used a "void". This means the compiler will still assign the pointer to myColor, but it's now generic and so the compiler can't catch me making mistakes. However, what is convenient is that now I can pass myColor to any part of the program and whoever hangs onto the pointer doesn't need to have access to the Color definition. Remember, the pointer is just an address. BTW, an address is just an unsigned integer indicating a physical location in RAM.

I don't know if you're still following me here, but this same concept applies to Objective-C with the id type. An id is just a void pointer -- a base address to an instance of a class (i.e. an object, aka a grouping of variables in a block of RAM), but without specifying the Class type. This is useful in Objective-C so that you can pass objects around anonymously without the compiler protesting.

Pro-tip: You generally want to avoid using void or id if you can help it, but in some places you have no choice.
(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]An instance is an object. An object is an instance of a class. We're just using slightly different terminology.

Oh, I knew that! Oops.

(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]Yes, setNumerator and setDenominator are accessors. You are not insane Wink

Ok. Good. Wink

(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]I don't know if you understand much C yet or not

Rasp None

(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]I don't know if you're still following me here

erm... Kind of.

(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]Pro-tip: You generally want to avoid using void or id if you can help it, but in some places you have no choice.

That, I can understand. Besides void, the book uses void a lot...

(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ][edited by AnotherJake -- we prefer to use code tags for code for better formatting and readability. The code tags can be added with the octothorp (i.e. the "#") in the post editor]

Ah ok. I was looking for a 'spoiler' button Rasp thanks
------

Ok. You have been very, very, very helpful and I thank you very much. I might have more question immediately after typing this, but for now I understand generally what I need to at this point (besides all of id's) haha. I will continue with the book I am using the try to think... abstractly... *joy* (*sarcasm*).

Thank you again. Will I make you mad if I ask any more questions here? Rasp haha
Double-posting is generally not a problem around here.

I added code tags to your post instead of the courier font.

(Apr 5, 2013 09:56 AM)easy_e Wrote: [ -> ]
(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]I don't know if you're still following me here

erm... Kind of.

No big deal. You're kind of drinking from a fire hose right now, trying to pick up the basics. Wink

(Apr 5, 2013 09:56 AM)easy_e Wrote: [ -> ]
(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]Pro-tip: You generally want to avoid using void or id if you can help it, but in some places you have no choice.

That, I can understand. Besides void, the book uses void a lot...

Keep in mind that there is a difference between a void pointer and other uses of void.

This is a void pointer, which you should prefer to avoid:
Code:
void *myVariable;

Note the * before myVariable. That asterisk denotes that myVariable is a pointer (aka an address in RAM).

This is void used to specify that there will be no return value, which is not to be avoided at all because you have to use it!:
Code:
- (void)myMethod;

They are two completely different things. It is an idiosyncrasy of all C-style languages that the void keyword is used for multiple purposes, and it is notably unhelpful for beginners, sorry Annoyed

(Apr 5, 2013 09:56 AM)easy_e Wrote: [ -> ]Will I make you mad if I ask any more questions here?

That's what the forum is for dude, so ask away! Can't guarantee you'll always get a response.
(Apr 5, 2013 10:14 AM)AnotherJake Wrote: [ -> ]Double-posting is generally not a problem around here.

I added code tags to your post instead of the courier font.

(Apr 5, 2013 09:56 AM)easy_e Wrote: [ -> ]
(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]I don't know if you're still following me here

erm... Kind of.

No big deal. You're kind of drinking from a fire hose right now, trying to pick up the basics. Wink

(Apr 5, 2013 09:56 AM)easy_e Wrote: [ -> ]
(Apr 5, 2013 09:48 AM)AnotherJake Wrote: [ -> ]Pro-tip: You generally want to avoid using void or id if you can help it, but in some places you have no choice.

That, I can understand. Besides void, the book uses void a lot...

Keep in mind that there is a difference between a void pointer and other uses of void.

This is a void pointer, which you should prefer to avoid:
Code:
void *myVariable;

Note the * before myVariable. That asterisk denotes that myVariable is a pointer (aka an address in RAM).

This is void used to specify that there will be no return value, which is not to be avoided at all because you have to use it!:
Code:
- (void)myMethod;

They are two completely different things. It is an idiosyncrasy of all C-style languages that the void keyword is used for multiple purposes, and it is notably unhelpful for beginners, sorry Annoyed

(Apr 5, 2013 09:56 AM)easy_e Wrote: [ -> ]Will I make you mad if I ask any more questions here?

That's what the forum is for dude, so ask away! Can't guarantee you'll always get a response.


Thank you so much!

Haha ok, I was mistaken between the pointer and the use of void Rasp

Yeah, I really love this stuff when I understand it, yet hate it when I don't. Well, I love it all the time actually. But, programming games is what I want to do, so I am going to stick with it! Thanks again!
Alright. Well I do have one more question (on the same topic here) that I must ask before I continue because it is irking me.

What is the difference between the accessors "[myFraction setNumerator/Denominator = __]" and the "numerator/denominator = __"? (Btw, the setNumerator/Denominator is obviously a method that applies the number to the ivar - I assume just like the latter does, but I still do not know which one of the ways is better? heh heh Thanks.
Pages: 1 2
Reference URL's