Free Online Courses for Software Developers - MrBool
× Please, log in to give us a feedback. Click here to login
×

You must be logged to download. Click here to login

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

Java Encapsulation: Improving the code quality in Java

See in this article how to improve security and code quality through the proper use of encapsulation in Java.

What is the encapsulation? Maybe the most important thing that we should know about encapsulation is the difference between it and the information hiding, due to the great confusion that has always existed between these two definitions. The information hiding is considered part of the encapsulation, but if we do a search on the internet, we can find the following definition for encapsulation: “A mechanism of programming language to restrict access to some parts of objects, hiding the data of a class and making them only available by methods”.

In fact, the mechanism for restricting access to some components of object definition is the information hiding. Encapsulation is a concept of Object Oriented Programming where the state of objects (the variables of the class) and their behavior (class methods) are grouped into sets according to their degree of relationship.

Therefore, the purpose of encapsulation is to organize the data that are related, grouping them (encapsulating them) in objects (classes), reducing collisions of variable names (as variables with the same name are in different namespaces), and similarly, combining methods related to their properties (or class variables). This pattern helps to maintain a program with hundreds or thousands of lines of code more readable and easy to work with and maintain.

To facilitate understanding and visualization of what is encapsulation, let's see a simple example. Suppose we have a program that works with information of living beings, like man and dog. For these two living things (objects) there are characteristics (attributes) in common, such as name, age and weight, and characteristics (attributes) that are specific to each one, like identity number for the man and race for the dog. There is also common for both operations, such as walking, or specific to each, how to talk to the man and barking to the dog. One possible representation of the classes of such objects can be shown in Figure 1.

Figure 1.Classes Man and Dog.

As we can see, these classes encapsulate the data related to each object so that we can access each of them without conflict, because they are each in their domain. That is, to know the age of a man, we can ask for Man.age and, likewise, to know the age of a dog, we ask for Dog.age. The two attributes have the same name, but each has its own domain. The same can be said about the operations of each of the classes. This is encapsulation!

On the other hand, the concept of information hiding is more than hide, or hide the data, it is also the primary criteria for modularization systems, which must take into account the concealment of design decisions that are likely to change. Protecting information in this way, we remove the requirement for an intimate knowledge of the project by customers, so that they can use any module like a black box, that is, without knowing what's in there, but only what goes and what comes out of it. Thus, the client does not have to know or worry about how is the logic of certain methods; should just call them and use their results.

For example, following the previous representation, suppose we would get the BMI (Body Mass Index) to a man and a dog. For this, we would have to add one more field in each class: height for man and stature to the dog. Furthermore, both Man class as the Dog class, would have a method named calculateBMI() which would return the value of BMI for any of the objects. In the case of man, the calculateBMI() method would make the bill:BMI = weight/height²

In the case of the dog, with a suggested approach by several authors, we would have:BMI = weight/stature²

The client (who calls the method) has not BMI field available to it. This is calculated according to the values of other fields of objects. The client does not have to know how the calculation is made or, if the calculation now is done in another way (to be more precise, for example). For it, there should be no difference. It just have to know that each method will bring the value of the BMI for any of the objects (man or dog).

Visibility Modifiers

Let's see now how to change the level of access that we can give to the methods and attributes of the classes of our project to change the visibility of these to the other code artifacts (classes, attributes and methods) of the project. Methods and attributes can have modifiers such as public and private to indicate the level of access that other classes will have them. Table 1 shows that the level of access that exists for members of a class according to their access modifier.

Modifier

Class

Package

Subclass

All

public

YES

YES

YES

YES

protected

YES

YES

YES

NO

default

YES

YES

NO

NO

private

YES

NO

NO

NO

Table 1. Class members level access according to its modifier.

If we look at the first column of data (the Class column), we see that a class always has access to all its members, no matter what the access modifier of these. The second column tells us that all classes that belong to the same package from another class have access to all its members, except private. In turn, the third column tells us that the subclasses of a given class have access to public and protected members of the extended class. Finally, the last column tells us that all other classes (other than a class belonging to the same package, or a subclass) have access to all public members of any class, and to them only.

These are the visibility modifiers that exist in the Java language and that will allow us to increase the quality and safety of our code when used well, and we have better explain why.

An important tip to help you choose what level of access to use is: be as restrictive as possible! That is, always use the private modifier to the members of a class, unless there is a good reason for not doing so. Remember that for us to implement encapsulation in our code (relying on the protection of information hiding), we have to have all members of the class with private access, giving external access to these through public methods. This tip is not only valid for the constants. As the values of constants cannot be changed, be allowed to have your public access does not cause any security problem to the code.

Potential problems with private attributes

What does it mean telling we have the attributes of a class as public? It means, as we saw in Table 1, that any class of any software package, you can change the values of the public attributes of this class freely without any restriction! This implies that a client who does not know the business rules of a program module can enter invalid values, causing runtime errors. Let's look at a small example to illustrate some of the potential problems that we can introduce in our code when we have all the attributes with public visibility. The example shown in Listing 1 simulates very simplistic way the operation of an elevator.

Listing 1.Code example with public attributes.

  public class Elevator {
                 public boolean doorOpen = false;
                 public int currentFloor = 0;
                 public int weight = 0;
                 public static final int TOP_FLOOR = 20;
                 public static final int BOTTOM_FLOOR = 0;
                 public static final int WEIGHT_CAPACITY = 400;
  }
  class TestElevator {
                 public static void main(String[] args) { 
                                 // Create one elevator instance.
                                 Elevator elevator = new Elevator();
                                 // Opens the door for passenger entrance.
                                 elevator.doorOpen = true;
                                 // Bolt the door after passenger entry.
                                 elevator.doorOpen = false;
                                 // Go down one floor (the elevator was on the floor 0 and there is no floors below this).
                                 elevator.currentFloor--;
                                System.out.println("Current floor: " + elevator.currentFloor);
                                 // Go up one floor.
                                 elevator.currentFloor++;
                                 System.out.println("Current floor: " + elevator.currentFloor);
                                 // Jump to the 27th floor (the building has only 20 floors).
                                 elevator.currentFloor = 27;
                                 System.out.println("Current floor: " + elevator.currentFloor);
                 }
  }
  

In the code we have the Elevator class, which is our elevator. We can open and close the elevator door by doorOpen variable, know what floor the elevator is through currentFloor variable and know the elevator weight by weight variable. We also have some constants as WEIGHT_CAPACITY, which tells us that the maximum weight capacity that the elevator supports, TOP_FLOOR to know which is the highest floor of the building and BOTTOM_FLOOR, to know which is the lowest floor.

As the Elevator class does not implement encapsulation, you can change the values of its attributes freely and in undesirable ways. In the code of TestElevator test class, we can see (with the help of the comments) that actions were taken in the elevator. The door was opened for passenger entry, and then closed for the elevator could walk. Then the elevator received the command to walk one floor down, despite already being on the lower floor of the building, that is, was caused an error. He was later appointed to the elevator up one floor and "jump" up to the 27th floor without going through all the floors which are among the current floor and the 27th floor of the building, even though the highest floor of the building is the 20th, causing another error.

As we can see, the program of this elevator is unsafe. What does assure us that with this user interface, a user does not open the door and take the elevator floor door open? Nothing! Also, there is no guarantee that the elevator is sent only to valid levels, or that the weight inside the elevator is less than or equal to the maximum weight set when in operation.

The private modifier

The private modifier allows the attributes and operations of a certain class to become inaccessible to other classes. To make private an attribute or method of a class, insert the private modifier in front of the attribute or method, which we intend to change the visibility. However, if we make the members of a private class, we have to keep in mind that we will have no way to change the state of an instance of this class unless we create methods visible to the outside of this that allow us to do this, that is, we have to create a public interface of use.

To successfully apply the concept of encapsulation in our code, not enough to make the attributes of private class and simply create public methods to return and change directly these same values (so called getters and setters that we'll talk later), we have to add logic to our code in order to make secure transactions, ensuring that it is not possible to attribute undue values to our variables.

That said, let's continue with our example and change the visibility modifiers of our variables of the Elevator class to private, but leaving the public constants. Thus, we have the result shown by Listing 2.

Listing 2.Changing modifiers visibility of Elevator class variables to private.

  public class Elevator {
                 private boolean doorOpen = false;
                 private int currentFloor = 0;
                 private int weight = 0;
                 public static final int TOP_FLOOR = 20;
                 public static final int BOTTOM_FLOOR = 0;
                 public static final int WEIGHT_CAPACITY = 400;
  }
  

By implementing these changes to TestElevator class, it fails to compile, because it attempts to change private attribute values of Elevator class. Under these conditions the Elevator class is not very useful, because there are ways to change the values of its attributes.

Implementing a public interface

So what can we do to make our class Elevator useful? The answer is simple: creating a public interface for it. But what is the public interface of a class? The variables and public methods of a class are commonly referred like the class of interface, because they are the only elements that other classes can use to change the state of objects instantiated from it. And one of the goals of object-oriented languages is to encourage us to create this public interface, so that the implementation of the methods may change without affecting the interface. Therefore, who calls the methods of a class only need to know what the method does. How the method does its job is not important. This allows the methods of the classes can be modified to improve its performance, safety, introducing calls to new databases, introducing new algorithms, etc., and the call to it continue being made exactly the same way.

Getters e Setters

When we turn private the attributes of a class, we have to create a way to be able to access them if we think they should be obtained and/or altered by other customer’s classes of this class (classes that call methods in this class). One way to do this is by creating public methods that allow us to return (get) or define (set) the values of these attributes, the getters and setters. They are so used that there are shortcuts in the IDE to create these methods automatically.

Let's change our code so that it comes back to be buildable. To achieve this goal, implement the getters and setters of the Elevator class. To not have to write all the methods manually, we will take the help of Eclipse and generate methods automatically, with Alt + Shift + S shortcut and then selecting the menu option “Generate Getters and Setters....”. Figure 2 shows this window.

Figure 2.Generating getters and setters methods in Eclipse.

In this window we can choose the variables for which we want to create the public methods. In this example we will select all private variables. After we click on the OK button we’d generated methods to return and assign the values of private variables of our class.

Although we have created a public interface for use of our class, we still have to make some changes to the code of TestElevator test class. Instead of directly accessing the variables of Elevator class, let's change such access in order to use the methods we just created. Listing 3 shows the end result of these changes.

Listing3.Generating getters and setters to the Elevator class

  public class Elevator {
                 public boolean doorOpen = false;
                 public int currentFloor = 0;
                 public int weight = 0;
                 public static final int TOP_FLOOR = 20;
                 public static final int BOTTOM_FLOOR = 0;
                 public static final int WEIGHT_CAPACITY = 400;
   
                 public boolean isDoorOpen() {
                                 return doorOpen;
                 }
   
                 public void setDoorOpen(boolean doorOpen) {
                                 this.doorOpen = doorOpen;
                 }
   
                 public int getCurrentFloor() {
                                 return currentFloor;
                 }
   
                 public void setCurrentFloor(int currentFloor) {
                                 this.currentFloor = currentFloor;
                 }
   
                 public int getWeight() {
                                 return weight;
                 }
   
                 public void setWeight(int weight) {
                                 this.weight = weight;
                 }
  }
   
  class TestElevator {
                 public static void main(String[] args) {
                                 // Create one elevator instance.
                                 Elevator elevator = new Elevator();
                                 // Opens the door for passenger entrance.
                                 elevator.doorOpen = true;
                                 // Bolt the door after passenger entry.
                                 elevator.doorOpen = false;
                                 // Go down one floor (the elevator was on the floor 0 and there is no
                                 // floors below this).
                                 elevator.currentFloor--;
                                 System.out.println("Current floor: " + elevator.currentFloor);
                                 // Go up one floor.
                                 elevator.currentFloor++;
                                 System.out.println("Current floor: " + elevator.currentFloor);
                                 // Jump to the 27th floor (the building has only 20 floors).
                                 elevator.currentFloor = 27;
                                 System.out.println("Current floor: " + elevator.currentFloor);
                 }
  }
  

As we can see, the IDE automatically generates the name of the methods with a "get" or "set" in front of the class variable name, except for methods that return Boolean variables, where instead of "get" is used "is".

In Listing 3 we can see that the public methods were generated so that we can make the same mistakes we committed already when the variables were public, except we are using methods instead of changing the values of the class variables directly. The generated methods do not have "intelligence" that is, have no logic/validation related to the implementation of business rules for the return or assign the values of the class variables, simply return and attribute values. As mentioned, we have yet to introduce logic to our code in order to make secure transactions, as validations to ensure that the correct values are assigned.

Implementing the encapsulation

Now that we've changed the visibility of attributes and created a public interface, we will analyze one case at a time to add security in the code that can ensure proper operation of the elevator. For example, what we can validate when a client tries to close or open the elevator door? A first validation may be to see if the port is already in the same position as the command sent; that is, if the client asks to close the door and the door is already closed, the elevator should not do anything (the same for the door open). Another validation we can do is to check if the maximum weight supported by the elevator was not exceeded when someone tries to close the door on it. If so, the elevator must keep the door open until the weight decrease to within the limits. Finally, an improvement in readability of the code we can do is make the setDoorOpen() method (method that takes a boolean) private and create two public methods that are more open about their functionality: openDoor() to open the door and closeDoor() to close the door. Listing 4 shows the code changes relating to the introduction of logic in the methods of opening and closing the elevator door.

Listing 4.Logic behind the opening and closing operations of the elevator doors.

  public boolean isDoorOpen() {
                                 return _doorOpen;
                 }
   
                 public boolean openDoor() {
                                 return setDoorOpen(true);
                 }
   
                 public boolean closeDoor() {
                                 return setDoorOpen(false);
                 }
   
                 private boolean setDoorOpen(boolean doorOpen) {
                                 String doorPosition = doorOpen ? "opened" : "closed";
                                 // Checks if the door position is not the same as the current position.
                                 if (_doorOpen == doorOpen) {
                                                System.out.println(" – Door is already open " + doorPosition);
                                                return true;
                                 }
                                 // If to close the door, ensure that the elevator weight does not exceed the maximum weight allowed.
                                 if (!doorOpen && _weight > WEIGHT_CAPACITY) {
                                                System.out.println(" – Elevator has more weight than allowed!");
                                                return false;
                                 }
                                 _doorOpen = doorOpen;
                                 System.out.println(" – Door " + doorPosition);
                                 return true;
                 }
  

As we can see from the code, which have a public interface to the customer, there are three operations:


  • Ask if the door is open;
  • Opening the door operation;
  • The operation to close the door.

As the door opens or closes, and there is no validation that these operations should be totally transparent to the client. He did not need to know how these operations are done, just want them to be made successfully and safely, and that is what we are providing with our code.

Now, what can we do about the elevator movement, when we press the floor number of the button to where we want to go? Obviously, if a building has 20 floors, the elevator will not have the 21 buttons, 22 or 23 floors (security introduced by hardware). However, who assures us that whoever programmed the buttons of the elevators did not make a mistake and made the 3rd button, instead of sending the elevator to the 3rd floor, sends him to the 30th floor? Nobody! So, it is best to validate that we are always within our limits. Moreover, what should happen when we press the button on the same floor where we have met? Nothing. But we can only guarantee this behavior is to program the elevator to do nothing in these conditions. Otherwise, the elevator could, for example, closing the doors and then validate that is already on the same floor for which it has been commanded to go, and only afterwards to open the door again.

We can also validate that the door is closed before the elevator get moving, otherwise we would put at risk the safety of people inside. Moreover, a further improvement of the code is to ensure that the elevator floor walk a time, so that it can (later) be added to the logic where to stop if someone is calling from outside. The code in these validations can be seen in Listing 5.

Listing 5. Logic behind the elevator movement operations.

  public int getCurrentFloor() {
                                 return _currentFloor;
                 }
   
                 private void goUp() {
                                 _currentFloor++;
                                 System.out.println(" - Floor: " + _currentFloor);
                 }
   
                 private void goDown() {
                                 _currentFloor--;
                                 System.out.println(" - Floor: " + _currentFloor);
                 }
   
                 public void setCurrentFloor(int currentFloor) {
                                 // Checks whether the specified floor is not less than the lowest floor of the building.
                                 if (currentFloor < BOTTOM_FLOOR) {
                                                System.out.println(" - Number of wrong floor! The minimum floor is "
                                                                              + BOTTOM_FLOOR);
                                                return;
                                 }
                                 // Checks whether the specified floor is not greater than the highest floor of the building.
                                 if (currentFloor > TOP_FLOOR) {
                                                System.out
                                                                              .println(" - Number of wrong floor! The top floor is "
                                                                                                             + TOP_FLOOR);
                                                return;
                                 }
                                 // Checks whether the specified floor is not the same as the current floor.
                                 if (currentFloor == _currentFloor) {
                                                System.out
                                                                              .println(" - The elevator is already at the indicated level.");
                                                return;
                                 }
                                 // Checks if the door is closed before you get moving.
                                 if (_doorOpen) {
                                                System.out.println(" - Closing the door...");
                                                if (!closeDoor()) {
                                                               System.out
                                                                                              .println(" - The elevator can't walk with door open!");
                                                               return;
                                                }
                                 }
                                 // Walk one floor at a time.
                                 if (_currentFloor < currentFloor) {
                                                System.out.println(" - Up...");
                                                while (_currentFloor != currentFloor) {
                                                               goUp();
                                                }
                                 } else {
                                                System.out.println(" - Down...");
                                                while (_currentFloor != currentFloor) {
                                                               goDown();
                                                }
                                 }
                 }
  

In this case, to the customer, there are only two operations: know what floor the elevator is (to be able to put this information into an electronic scoreboard, for example), and say where the elevator should go. Again, it should be noted that all the logic behind the implementation of the methods should be transparent to the customer information that it does not need to know.

Finally, we have the methods related to weight control that is in the elevator. Such methods have been encoded in a simple way. For security reasons, it is important to know if the elevator capacity is being respected. Thus, we can ask what the elevator weight through the getWeight() method, and say how much weight is in it, through setWeight() method. In a real scenario this data would be measured by sensors (in our case we enter the data manually). Listing 6 shows the code for these methods.

Listing 6.Getter and Setter of the elevator weight.

                 public int getWeight() {
                                 return _weight;
                 }
   
                 public void setWeight(int weight) {
                                 _weight = weight;
                                 System.out.println(" - Elevador weight: " + _weight + " Kg.");
                 }
  

Now we can test the security of our elevator. For this, we will send some valid commands and other invalid and see how the elevator behaves. Listing 7 shows our test class, where we tried to go up and down with the elevator to nonexistent levels, riding the elevator with more weight than allowed and riding the elevator with the door open.

Listing 7.Code of the elevator’s test class.

  class TestElevator {
                 public static void main(String[] args) {
                                 // Create one elevator instance.
                                 Elevator elevator = new Elevator();
                                 System.out.println("Opening the door for passenger entrance.");
                                 elevator.openDoor();
                                 /*
                                  * The passenger entry increases the weight of the elevator (elevator sensor
                                  * would be in charge of sending this information).
                                  */
                                 System.out.println("People entered the elevator.");
                                 elevator.setWeight(elevator.getWeight() + 300);
                                 // Bolt the door after passenger entry by pressing the button to close
                                 // the door.
                                 System.out.println("The close door button was triggered.");
                                 elevator.closeDoor();
                                 /*
                                  * Go down one floor (the elevator was on the floor 0 and there is no
                                  * floors below this). The button down one floor there is the elevator,
                                  * but we are simulating sending a wrong command.
                                  */
                                 System.out.println("Sent command to descend one floor.");
                                 elevator.setCurrentFloor(elevator.getCurrentFloor() - 1);
                                 // Go up one floor.
                                 System.out.println("Sent command to go up one floor.");
                                 elevator.setCurrentFloor(elevator.getCurrentFloor() + 1);
                                 /*
                                  * Jump to the 27th floor (the building has only 20 floors). Again, the
                                  * 27th floor button does not exist in the elevator, but we must make sure
                                  * that’s a bad command sent does not affect the correct functioning.
                                  */
                                 System.out.println("Command sent to climb to the 27th floor.");
                                 elevator.setCurrentFloor(27);
                                 // Climb to the 10th floor.
                                 System.out.println("Command sent to climb to the 10th floor.");
                                 elevator.setCurrentFloor(10);
                                 // Opens the elevetator door.
                                 System.out.println("Command sent to open the door.");
                                 elevator.openDoor();
                                 System.out.println("Command sent to open the door.");
                                 elevator.openDoor();
                                 // The passenger entrance increases the weight elevator.
                                 System.out.println("People entered the elevator.");
                                 elevator.setWeight(elevator.getWeight() + 280);
                                 /*
                                  * Go down to the 7th floor of the building (the door is open but closes
                                  * automatically, as in the elevators we know).
                                  */
                                 System.out.println("Sent command to go to the 7th floor.");
                                 elevator.setCurrentFloor(7);
                                 // Passengers out of the elevator.
                                 System.out.println("People left the elevator.");
                                 elevator.setWeight(elevator.getWeight() - 180);
                                 // Go down to the 7th floor of the building (the door is open).
                                 System.out.println("Sent command to go to the 7th floor.");
                                 elevator.setCurrentFloor(7);
                                 // Opens the door for people to step off.
                                 System.out.println("Command sent to open the door.");
                                 elevator.openDoor();
                 }
  }
  

Completed the run test, we will analyze the results generated by the Listing 7 code and see how the program behaved. The output of the Elevator class was configured with a hyphen in blue at the beginning of the line to differentiate the output caused by the test class. Listing 8 shows the result.

Listing 8.Output generated by the tests in class TestElevator.

  Opening the door for passenger entrance.
  - Door open people entered the elevator.
  . - Elevator weight: 300 kg was triggered to close the door button.
  - Closed door Posted command to descend one floor.
  - Number of wrong floor! The minimum level is 0
  Command sent to go up one floor.
  - Going up ...
  - Floor: 1
  Command sent to climb to the 27th floor.
  - Number of wrong floor!
  The top floor is 20
  Command sent to climb to the 10th floor.
  - Going up ...
  - Floor: 2
  - Floor: 3
  - Floor: 4
  - Floor: 5
  - Floor: 6
  - Floor: 7
  - Floor: 8
  - Floor: 9
  - Floor: 10
  Command sent to open the door.
  - Door open
  Command sent to open the door.
  - The door is already open
  People entered the elevator.
  - Elevator weight: 580 kg.
  Sent command to go to the 7th floor.
  - Closing the door ...
  - Elevator with more weight than allowed!
  - The elevator can not walk with the door open!
  People left the elevator.
  - Elevator weight: 400 kg.
  Sent command to go to the 7th floor.
  - Closing the door ...
  - Closed door
  - Down ...
  - Floor: 9
  - Floor: 8
  - Floor: 7
  Command sent to open the door.
  - Door open
  

As we can see, we can move the elevator with a plenty of security validations, having successfully implemented the encapsulation of our class. In addition, we’ve created a simple public interface, accessible to all and with a fully logical location (i.e. validations are made on the class itself) using private variables (protected against undesirable changes).

A class that successfully implement encapsulation cannot allow the created object perform invalid operations. To achieve this goal we can revoke the direct access to class attributes and some of its methods using the private modifier and create public methods (the public interface) containing the logic necessary to make all possible operations to be performed may be invoked safely.

That's what we saw with our case study of the elevator. From it, we could easily add more logic to create the behavior of a real elevator in our program, for example, cause the door to open automatically when it reaches its destination floor. Try!

After reading this article remains the question: your code implements successfully encapsulation? With this content, you acquired the necessary knowledge to implement encapsulation and make your code even more secure. So, get to work and if necessary, good refactoring!



Freelancer Software Developer. Have knowledge in Java, Android, HTML, CSS and Javascript. He has also knowledge in Agile Development

What did you think of this post?
Services
[Close]
To have full access to this post (or download the associated files) you must have MrBool Credits.

  See the prices for this post in Mr.Bool Credits System below:

Individually – in this case the price for this post is US$ 0,00 (Buy it now)
in this case you will buy only this video by paying the full price with no discount.

Package of 10 credits - in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download few videos. In this plan you will receive a discount of 50% in each video. Subscribe for this package!

Package of 50 credits – in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download several videos. In this plan you will receive a discount of 83% in each video. Subscribe for this package!


> More info about MrBool Credits
[Close]
You must be logged to download.

Click here to login