So far we have seen what really only amounts to a kind of code-organizing convention; although we did discuss the wider goals of OOP. So now we will take things further and begin to see how we manage to achieve encapsulation with OOP.
Definition of encapsulation
Keeping the internal workings of your code safe from interference from the programs that use it while allowing only the variables and methods you choose, to be accessed. This means your code can always be updated, extended or improved without affecting the programs that uses it if the exposed parts are still made available in the same way. It also allows the code that uses your encapsulated code to be much simpler and easier to maintain because much of the complexity of the task is encapsulated in your code.
But didn't you say we don't have to know what is going on inside? So you might question what we have seen so far like this.
Reasonable OOP student question: If we are constantly setting the instance variables like this rambo.health = 100;
, isn't it possible that eventually, things could start to go wrong, perhaps like this:
rambo.soldierType = "fluffy bunny";
This is a very real danger that encapsulation can prevent.
Encapsulation protects your class/objects of your class/code from being used in a way that it wasn't meant to be. By strictly controlling the way that your code is used it can only ever do what you want it to do and with value ranges that you can control.
It can't be forced into errors or crashes. Also, you are then free to make changes to the way your code works internally, without breaking any other games or the rest of your game, that might be written using an older version of the code.
weightlifter.legstrength = 100; weightlifter.armstrength = -100; weightlifter.liftHeavyWeight(); // one typo and weightlifter will rip off his arms
We can encapsulate our classes to avoid this and here is how.
The designer of the class controls what can be seen and manipulated by any program that uses their class. Let's look at the access modifier you have probably already noticed before the class
keyword like this:
public class Soldier{
//Implementation goes here
}
There are two main access modifiers for classes in the context we have discussed so far. Let's briefly look at each in turn:
So now we can make a start at this encapsulation thing. But even at a glance, the access modifiers described are not very fine grained. We seem to be limited to:
The benefits here are easily taken advantage of, however. The idea would be to design a package that fulfills a set of tasks. Then all the complex inner workings of the package, the stuff that shouldn't be messed with by anybody but our package, should be default access (only accessible to classes within the package). We can then make available a careful selection of public classes that can be used by others (or other distinct parts of our program).
A well-designed game will probably consist of one or more packages, each containing only default or default and public classes.
In addition to class level privacy controls, Java gives us programmers more fine-grained controls, but to use these controls we have to look into variables with a little more detail.
To build on the class visibility controls, we have variable access modifiers. Here is a variable with the private
access modifier being declared.
private int myInt;
For example, here is an instance of our Soldier
class being declared, created and assigned. As you can see the access specified in this case is public.
public Soldier mySoldier = new Soldier();
Before you apply a modifier to a variable you must first consider the class visibility. If class a is not visible to class b, say because class a has default access and class b is in another package, then it doesn't make any difference what access modifiers you use on the variables in class a, class b can't see any of them anyway.
So, what can we surmise about encapsulation design so far? It makes sense to show a class to another class when necessary but only to expose the specific variables that are needed; not all of them.
Here is an explanation of the different variable access modifiers. They are more numerous and finely grained than the class access modifiers.
public
: You guessed it, any class or method from any package can see this variable. Use public only when you are sure this is what you want.protected
: This is the next least restrictive after public. Protected variables can be seen by any class and any method if they are in the same package.default
: Default doesn't sound as restrictive as protected but it is more so. A variable has default access when no access is specified. The fact that default is restrictive perhaps implies we should be thinking on the side of hiding our variables more than we should be exposing them. At this point, we need to introduce a new concept. Do you remember we briefly discussed inheritance and how we can quickly take on the attributes of a class by using the extends
keyword? Just for the record, default access variables are not visible to subclasses; that is, when we extend
a class like we did with Activity
, we cannot see it's default variables. We will look at inheritance in more detail later in the chapter.
private
: Private variables can only be seen within the class they are declared. Including, like default access, they cannot be seen by subclasses (classes that inherit from the class in question).A well-designed game will probably consist of one or more packages, each containing only default or default and public classes. Within these classes, variables will have carefully chosen and most likely varied access modifiers, chosen with a view to achieving our goal of encapsulation.
One more twist in all this access modification stuff before we get practical with it.
It makes sense because methods are the things that our classes can do. We will want to control what users of our class can and can't do.
The general idea here is that some methods will do things internally only and are, therefore, not needed by users of the class and some methods will be fundamental to how users of the class, well, use your class.
The access modifiers for methods are the same as for the class variables. This makes things easier to remember but suggests, again, that successful encapsulation is a matter of design rather than of following any specific set of rules.
As an example, this next method, provided it is in a public class, could be used by any other class that has instantiated an object of the appropriate type.
public useMeEverybody(){ // do something everyone needs to do here }
Whereas this method could only be used internally by the class that contains it.
private secretInternalTask(){ /* do something that helps the class function internally Perhaps, if it is part of the same class, useMeEverybody could use this method- On behalf of the classes outside of this class. Neat! */ }
And this next method with no access specified has default visibility. It can be used only by other classes in the same package. If we extend
the class containing this default access method it will not have access to this method.
fairlySecretTask(){ // allow just the classes in the package // Not for external use }
As the last example before we move on, here is a protected
method, only visible to the package, but usable by our classes that extend it, just like onCreate
.
protected packageTask(){ // Allow just the classes in the package // And you can use me, if you extend me, too }
Method access should be chosen to best enforce the principles we have already discussed. It should provide the users of your class with just the access they need and preferably nothing more. Thereby we achieve our encapsulation goals, like keeping the internal workings of your code safe from interference from the programs that use it, for all the reasons we have discussed.
Now we need to consider, if it is best practice to hide our variables away as private, how do we allow access to them, where necessary, without spoiling our encapsulation? What if an object of class Hospital
wanted access to the health
member variable from an object of type Soldier
, so it could increase it? The health variable should be private because we don't want just any piece of code changing it.
To be able to make as many member variables as possible private and yet still allow limited access to some of them, we use getters and setters. Getters and setters are just methods that get and set variable values.
Getters and setters are not some special new Java thing we have to learn. It is just a convention for using what we already know. Let's have a look at getters and setters using our Soldier
and Hospital
class example.
In this example, each of our two classes is created in their own file but the same package. First, here is our Hospital
class.
class Hospital{ private void healSoldier(Soldier soldierToHeal){ int health = soldierToHeal.getHealth(); health = health + 10; soldierToHeal.setHealth(health); } }
Our implementation of the Hospital
class has just one method, healSoldier
. It receives a reference to a Soldier
object as a parameter. So, this method will work on whichever Soldier
object is passed in, vassily
, wellington
, rambo
or whoever.
It also has a local health
variable which it uses to temporarily hold and increase the soldier's health. In the same line, it initializes the health
variable to the Soldier
object's current health. The Soldier
object's health is private so the public getter method getHealth
, is used instead.
Then health
is increased by 10 and the setHealth
setter method loads up the new revived health value, back to the Soldier
object. Perhaps the value 10 could be changed to a member variable of Hospital
. Then, in the RTS, as the hospital is upgraded, the amount the health is restored by could increase.
The key here is that although a Hospital
object can change a Soldier
object's health; it only does so within the bounds of the getter and setter methods. The getter and setter methods can be written to control and check for potentially erroneous or harmful values.
Next look at our hypothetical Soldier
class with the simplest implementation possible of its getter and setter methods.
public class Soldier{ private int health; public int getHealth(){ return health; } public void setHealth(int newHealth){ // Check for stupid values of newHealth health = newHealth; } }
We have one instance variable called health
and it is private
. Private means it can only be accessed by methods of the Soldier
class. We then have a public getHealth
method which unsurprisingly returns the value held in the private health
int variable. As this method is public, any code with access to an object of type Soldier
can use it.
Next, the setHealth
method is implemented. Again, it is public but this time it takes an int
as a parameter and assigns whatever is passed in, to the private health
variable.
Now we declare, create, and assign to make an object of each of our two new classes and see how our getters and setters work.
Soldier mySoldier = new Soldier(); // mySoldier.health = 100;// Does not work, private // we can use the public setter setHealth() mySoldier.setHealth(100);// That's better Hospital militaryHospital = new Hospital(); // Oh no mySoldier has been wounded mySoldier.setHealth(10); /* Take him to the hospital. But my health variable is private And Hospital won't be able to access it I'm doomed - tell Laura I love her No wait- what about my public getters and setters? We can use the public getters and setters from another class */ militaryHospital.healSoldier(mySoldier); // mySoldier's private variable, health has been increased by 10 // I'm feeling much better thanks!
We see that we can call our public setHealth
and getHealth
methods directly on our object of type Soldier
. Not only that, we can call the healSoldier
method of the Hospital
object, passing in a reference to the Soldier
object, which too can use the public getters and setters to manipulate the private health
variable.
We see that the private health
variable is simply accessible, yet totally within the control of the designer of the Soldier
class.
Yet again our example and the explanation is probably raising more questions than it answers. That's good.
By using encapsulation features (like access control) it is kind of like agreeing on an important deal/contract about how to use and access a class, its methods, and variables. The contract is not just an agreement about now, but an implied guarantee for the future. We will see that as we proceed through this chapter, there are more ways that we refine and strengthen this contract.
Use encapsulation where it is needed or, of course, if you are being paid to use it by an employer. Often encapsulation is overkill on small learning projects, like some of the examples in this book. Except, of course, when the topic you are learning is encapsulation itself.
We are learning this Java OOP stuff under the assumption that you will one day want to write much more complex games, whether on Android or some other platform which uses OOP. In addition, we will be using classes from the Android API that use it extensively and it will help us understand what is happening then as well. Typically, throughout this book, we will use encapsulation when implementing full game projects and often overlook it when showing small code samples to demonstrate a single idea or topic.
With all these private variables and their getters and setters, does it mean that we need a getter and a setter for every private variable? What about a class with lots of variables that need initializing at the start. Think about this code:
mySoldier.name mysoldier.type mySoldier.weapon mySoldier.regiment ...
Some of these variables might need getters and setters, but what if we just want to set things up when the object is first created, to make the object function correctly?
Surely, we don't need two methods (a getter and a setter) for each?
Fortunately, this is unnecessary. For solving this potential problem there is a special method called a constructor. We briefly mentioned the existence of a constructor when we discussed instantiating an object from a class. Let's take a look again. Here we create an object of type Soldier
and assign it to an object called mySoldier
.
Soldier mySoldier = new Soldier();
Nothing new here but look at the last part of that line of code.
...Soldier();
This looks suspiciously like a method call.
All along, we have been calling a special method called a constructor that has been created, behind the scenes, automatically, by the compiler.
However, and this is getting to the point now, like a method, we can override it which means we can do useful things to set up our new object before it is used. This next code shows how we could do this.
public Soldier(){ // Someone is creating a new Soldier object health = 200; // more setup here }
The constructor has a lot of syntactical similarities to a method. It can however only be called with the use of the new
keyword and it is created for us automatically by the compiler- unless we create our own like in the previous code.
Constructors have the following attributes:
One more piece of Java syntax which is useful to introduce at this point is the Java this
keyword.
The this
keyword is used when we want to be explicit about exactly which variables we are referring to. Look at this example constructor, again for a hypothetical variation of the Soldier
class.
public class Soldier{ String name; String type; int health; public Soldier(String name, String type, int health){ // Someone is creating a new Soldier object this.name = name; this.type = type; this.health = health; // more setup here } }
This time the constructor has a parameter for each of the variables we want to initialize.
Let's discuss this
a bit more.
When we use this
as we do in the previous lines of code we are referring to the instance of the class itself. By using the this
keyword it is clear when we mean the member variable or the parameter.
As another common example, many things in Android require a reference to an instance of Activity
to do its job. We will fairly regularly, throughout this book pass in this
(a reference to the Activity) in order to help another class/object from the Android API do its work. We will also write classes that need this
as an argument in one or more of its methods. So, we will see how to handle this
when it is passed in as well.
There are more twists and turns to be learned about variables and this
and they make much more sense when applied to a practical project. In the next mini-app, we will explore all we have learned so far in this chapter and some more new ideas too.
First a bit more OOP.
We know quite allot about classes; how to turn them into objects and use their methods and variables. But something isn't quite right? Since the very start of the book, we have been using a class that doesn't conform to everything we have learned in this chapter- so far. We have used Log
to output to the logcat window, but have not instantiated it once! How can this be?
The static methods of classes can be used, without first instantiating an object of the class.
And as you have probably realized by now, Log
contains static methods. To be clear: Log
contains static methods but Log
is still a class.
Classes can have both static and regular methods as well, but the regular methods would need to be used in a regular way, via an instance of the class.
Take another look at Log.d
in action.
Log.d("Debugging","In newGame");
Here, d
is the method being statically accessed and the method takes two parameters, both of type String
.
Static methods are often provided in classes which have uses that are so generic it doesn't make sense to have to create an object of the class. Another useful class with static methods is Math
. This class is a part of the Java API, not the Android API.
Want to write a calculator app? It's easier than you think with the static methods of the Math
class. You can look at them here: http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html
If you try this out you will need to import the Math
class, the same way you imported all the other classes we have used.
13.58.212.170