Encapsulation is the ability to hide and protect data stored in Java
objects. You may ask, "Who are the bad guys who want to illegally
access my data?" It's not about bad guys. When a developer creates a Java class, he or she plans for a certain use pattern of this code by other classes. For example, the variable grossIncome
should not be modified directly, but via a method that performs some
validation procedures to ensure that the value to be assigned meets
application-specific rules.
Encapsulation mechanisms enable the programmer to group data and the subroutines that operate on them together in one place, and to hide irrelevant details from the users of an abstraction
Encapsulation mechanisms enable the programmer to group data and the subroutines that operate on them together in one place, and to hide irrelevant details from the users of an abstraction
Encapsulation and Access Control
An OOP principle, encapsulation
is a mechanism that protects parts of an object that need to be secure
and exposes only parts that are safe to be exposed. A television is a
good example of encapsulation.
Inside it are thousands of electronic components that together form the
parts that can receive signals and decode them into images and sound.
These components are not to be exposed to users, however, so Sony and
other manufacturers wrap them in a strong metallic cover that does not
break easily. For a television to be easy to use, it exposes buttons
that the user can touch to turn on and off the set, adjust brightness,
turn up and down the volume, and so on.
Back to encapsulation in OOP, let's take as an example a class that can encode and decode messages. The class exposes two methods called encode and decode,
that users of the class can access. Internally, there are dozens of
variables used to store temporary values and other methods that perform
supporting tasks. The author of the class hides these variables and
other methods because allowing access to them may compromise the
security of the encoding/decoding algorithms. Besides, exposing too many
things makes the class harder to use. As you can see later, encapsulation is a powerful feature.
Java supports encapsulation through access control. Access control is governed by access control modifiers. There are four access control modifiers in Java: public, protected, private,
and the default access level. Access control modifiers can be applied
to classes or class members. We'll look at them in the following
subsections.
Tight Encapsulation
Encapsulation refers to the combining of fields and
methods together in a class such that the methods operate on the data, as opposed to users of the class accessing the fields
directly. The term tight encapsulation refers to using
encapsulation every time on all the fields of a class, and only providing access to the fields via methods. With tight
encapsulation, no fields of an object can be modified or accessed directly; you can only access the fields through a method
call.
To implement tight encapsulation, make the fields of a class
private and provide
public accessor (“getter”) and mutator (“setter”) methods. Because a mutator or accessor
method must be invoked to access the fields of the object, tight encapsulation has several key benefits:
-
You can monitor and validate all changes to a field.
-
Similarly, you can monitor and format all access to a field.
-
The actual data type of a field can be hidden from the user, allowing you to change the data type without affecting the code that uses the object, as long as you do not alter the signatures of the corresponding accessor and mutator method.
To demonstrate, let's first look at a class that does not implement tight encapsulation. The following class, named
Student1, represents a student with fields for the year (Freshman, Sophomore, Junior, Senior)
and percentage grade of a student. The fields of
Student1 are
public and can be accessed directly:
public class Student1 { public String year; public double grade; }
Because the class does not implement tight encapsulation, the fields of a Student1 object can take on any values. The
following code is valid, although from an application point of view the values do not make sense:
Student1 s = new Student1(); s.year = "Memphis, TN"; s.grade = -24.5;
The string
“Memphis, TN” is not a valid year, and we can assume that a student's grade should never be
negative. With tight encapsulation, these issues can easily be avoided because users of the class cannot access its fields
directly. By forcing a method call to change a value, you can validate any changes to the fields of the object.
The following
Student2 class is similar to
Student1 but implements tight encapsulation. It is not possible for
year to be an invalid value or
grade to be negative or greater than
105.0:
1. public class Student2 { 2. private String year; 3. private double grade; 4. 5. public void setYear(String year) { 6. if(!year.equals("Freshman") && 7. !year.equals("Sophomore") && 8. !year.equals("Junior") && 9. !year.equals("Senior")) { 10. throw new IllegalArgumentException( 11. year + " not a valid year"); 12. } else { 13. this.year = year; 14. } 15. } 16. 17. public String getYear() { 18. return year; 19. } 20. 21. public void setGrade(double grade) { 22. if(grade < 0.0 || grade > 105.0) { 23. throw new IllegalArgumentException( 24. grade + " is out of range"); 25. } else { 26. this.grade = grade; 27. } 28. } 29. 30. public double getGrade() { 31. return grade; 32. } 33. }
See if you can determine the result of the following statements:
Student2 s2 = new Student2(); s2.setYear("Junior"); s2.setGrade(-24.5);
Invoking
setYear with the argument
“Junior” changes the year field to
“Junior”. Invoking
setGrade with the argument −24.5 causes an
IllegalArgumentException to be thrown on line 23. Due to tight encapsulation, it is not
possible for the values of
Student2 to contain invalid values.
The benefits of encapsulation outweigh any overhead of the additional method calls, and any good OO design uses tight
encapsulation in all classes. The next section discusses another important object‐oriented
design concept: loose coupling.
In the preceding section, you learned that you should hide instance variables by making them private. Why would a programmer want to hide something? In this section we discuss the benefits of information hiding.
In the preceding section, you learned that you should hide instance variables by making them private. Why would a programmer want to hide something? In this section we discuss the benefits of information hiding.
The strategy of information hiding is not
unique to computer programming—it is used in many engineering
disciplines. Consider the electronic control module that is present in
every modern car. It is a device that controls the timing of the spark
plugs and the flow of gasoline into the motor. If you ask your mechanic
what is inside the electronic control module, you will likely get a
shrug.
The module is a black box,
something that magically does its thing. A car mechanic would never
open the control module—it contains electronic parts that can only be
serviced at the factory. In general, engineers use the term "black box"
to describe any device whose inner workings are hidden. Note that a
black box is not totally
mysterious. Its interface with the outside world is well-defined. For
example, the car mechanic understands how the electronic control module
must be connected with sensors and engine parts.
The process of hiding implementation details while publishing an interface is called encapsulation. In Java, the class construct provides encapsulation. The public methods of a class are the interface through which the private implementation is manipulated.
Why do car manufacturers put black boxes into
cars? The black box greatly simplifies the work of the car mechanic.
Before engine control modules were invented, gasoline flow was regulated
by a mechanical device called a carburetor, and car mechanics had to
know how to adjust the springs and latches inside. Nowadays, a mechanic
no longer needs to know what is inside the module.
Similarly, a programmer using a class is not burdened by unnecessary detail, as you know from your own experience. In Chapter 2, you used classes for strings, streams, and windows without worrying how these classes are implemented.
Encapsulation
also helps with diagnosing errors. A large program may consist of
hundreds of classes and thousands of methods, but if there is an error
with the internal data of an object, you only need to look at the
methods of one class. Finally, encapsulation makes it possible to change the implementation of a class without having to tell the programmers who use the class.
INTERFACES VERSUS ABSTRACT CLASSES
The next question is when should you
use interfaces and when should you use abstract classes. If two or more
classes have lots of common functionality, but some methods should be
implemented differently, you can create a common abstract ancestor and
as many subclasses inheriting this common behavior as needed. Declare in
the superclass as abstract those methods that subclasses should
implement differently, and implement these methods in subclasses.
If several classes don't have common
functionality but need to exhibit some common behavior, do not create a
common ancestor, but have them implement an interface that declares the
required behavior. This scenario was not presented in the "Interfaces" section of Lesson 6, but it's going to be a part of the hands-on exercise in the Try It section of this lesson.
Interfaces and abstract classes are similar
in that they ensure that required methods will be implemented according
to required method signatures. But they differ in how the program is
designed. While abstract classes require you to provide a common
ancestor for the classes, interfaces don't.
Interfaces could be your only option if a class already has an ancestor that cannot be changed. Java doesn't support multiple inheritance — a class can have only one ancestor. For example, to write Java applets you must inherit your class from the class Applet, or in the case of Swing applets, from JApplet. Here using your own abstract ancestor is not an option.
While using abstract classes, interfaces, and polymorphism is not a must, it certainly improves the design of Java code by making it more readable and understandable to others who may need to work on programs written by you.
No comments:
Post a Comment