6 min read

In this article by Mohamed Sanaulla and Nick Samoylov, the authors of Java 9 Cookbook, we will cover the following recipe:

  • Implementing object-oriented design using classes

(For more resources related to this topic, see here.)

Implementing object-oriented design using classes

In this recipe, you will learn about the first two OOD concepts–object/class and encapsulation.

Getting ready

Object is the coupling of data and procedures that can be applied to them. Neither data nor procedures are required, but one of them is–and typically, both are–always present. The data is called object properties, while the procedures are called methods. Properties capture the state of the object. Methods describe the object’s behavior. An object has a type, which is defined by its class (see the information box). Object is said to be an instance of a class.

Class is a collection of definitions of properties and methods that will be present in each of its instances–the objects created based on this class. Encapsulation is the hiding of object properties and methods that should not be accessible by other objects.

Encapsulation is achieved by Java keywords private or protected in the declaration of the properties and methods.

How to do it…

    1. Create an Engine class with a horsePower property, a setHorsePower() method, which sets this property’s value, and the getSpeedMph() method, which calculates the speed of a vehicle based on the time when the vehicle started moving, the vehicle weight, and the engine power:
      public class Engine {
       private int horsePower;
       public void setHorsePower(int horsePower) {
       this.horsePower = horsePower;
       }
       public double getSpeedMph(double timeSec, int weightPounds) {
       double v = 2.0*this.horsePower*746;
       v = v*timeSec*32.17/weightPounds;
       return Math.round(Math.sqrt(v)*0.68);
       }
       }
      
    2. Create the Vehicle class:
      public class Vehicle {
       private int weightPounds;
       private Engine engine;
       public Vehicle(int weightPounds, Engine engine) {
       this.weightPounds = weightPounds;
       this.engine = engine;
       }
       public double getSpeedMph(double timeSec){
       return this.engine.getSpeedMph(timeSec, weightPounds);
       }
       }
      
    3. Create the application that uses these classes:
      public static void main(String... arg) {
       double timeSec = 10.0;
       int horsePower = 246;
       int vehicleWeight = 4000;
       Engine engine = new Engine();
       engine.setHorsePower(horsePower);
      FastTrack to OOP - Classes and Interfaces
      [ 3 ]
       Vehicle vehicle = new Vehicle(vehicleWeight, engine);
       System.out.println("Vehicle speed (" + timeSec
       + " sec)=" + vehicle.getSpeedMph(timeSec) + " mph");
       }

How it works…

The preceding application yields the following output:

As you can see, an engine object was created by invoking the default constructor of the Engine class, without parameters, and with the Java keyword, new, which allocated memory for the newly created object on the heap.

The second object, vehicle, was created with the explicitly defined constructor of the Vehicle class with two parameters. The second parameter of the constructor is the engine object, which carries the horsePower property with 246 set as its value using the setHorsePower() method. It also contains the getSpeedMph() method, which can be called by any object with access to engine, as is done in the getSpeedMph() method of the Vehicle class.

It’s worth noticing that the getSpeedMph() method of the Vehicle class relies on the presence of a value assigned to the engine property. The object of the Vehicle class delegates speed calculation to the object of the Engine class. If the latter is not set (null passed in the Vehicle() constructor, for example), we will get an unpleasant NullPointerException at runtime. To avoid it, we can place a check for the presence of this value, either in the Vehicle() constructor or in the getSpeedMph() method of the Vehicle class. Here’s the check that we can place in Vehicle():

if(engine == null){
 throw new RuntimeException("Engine" + " is required parameter.");
}

Here is the check that you can place in the getSpeedMph() method of the Vehicle class:

if(getEngine() == null){
 throw new RuntimeException("Engine value is required.");
}

This way, we avoid the ambiguity of NullPointerException and tell the user exactly what the source of the problem is.

As you probably noticed, the getSpeedMph() method can be removed from the Engine class and fully implemented in the Vehicle class:

public double getSpeedMph(double timeSec){
 double v = 2.0 * this.engine.getHorsePower() * 746;
 v = v * timeSec * 32.174 / this.weightPounds;
 return Math.round(Math.sqrt(v) * 0.68);
}

To do so, we would need to add a public getHorsePower() method to the Engine class in order to be available for usage by the getSpeedMph() method in Vehicle. For now, we leave the getSpeedMph() method in the Engine class.

This is one of the design decisions you need to make. If you think that an object of the Engine class is going to be passed around to the objects of different classes (not only Vehicle), then you would keep the getSpeedMph() method in the Engine class. Otherwise, if you think that the Vehicle class is going to be responsible for the speed calculation (which makes sense, since it is the speed of a vehicle, not of an engine), then you should implement the method inside Vehicle.

There’s more…

Java provides the capability to extend a class and to allow its subclass to access all the functionalities of the base class. For example, you can decide that every object that can be asked about its speed belongs to a subclass that is derived from the Vehicle class. In such a case, the Car class may look as follows:

 private int passengersCount;
 public Car(int passengersCount, int weightPounds, Engine engine){
 super(weightPounds, engine);
 this.passengersCount = passengersCount;
 }
 public int getPassengersCount() {
 return this.passengersCount;
 }
}

Now, we can change our test code by replacing Vehicle with Car:

public static void main(String... arg) {
 double timeSec = 10.0;
FastTrack to OOP - Classes and Interfaces
[ 5 ]
 int horsePower = 246;
 int vehicleWeight = 4000;
 Engine engine = new Engine();
 engine.setHorsePower(horsePower);
 Vehicle vehicle = new Car(4, vehicleWeight, engine);
 System.out.println("Car speed (" + timeSec + " sec) = "
 + vehicle.getSpeedMph(timeSec) + " mph");
}

When we run the preceding code, we get the same value as with an object of the Vehicle class:

Because of polymorphism, a reference to an object of Car can be assigned to the reference of the base class, Vehicle. The object of Car has two types–its own type (Car) and the type of the base class (Vehicle).

There are usually many ways to design the same functionality. It all depends on the needs of your project and the style adopted by the development team. But in any context, clarity of design helps to communicate the intent. A good design contributes to the quality and longevity of your code.

Summary

This article gives you a quick introduction to the components of object-oriented programming and covers the new enhancements to these components in Java 8 and Java 9. We have also tried to share good OOD practices wherever applicable.

Throughout the recipe, we used the new enhancements (introduced in Java 8 and Java 9), defined and demonstrated the concepts of OOD in specific code examples, and presented new capabilities for better code documentation.

One can spend many hours reading articles and practical advice on OOD in books and on the internet. Some of these activities can be beneficial for some people. But, in our experience, the fastest way to get hold of OOD is to try its principles early on in your own code. This was exactly the goal of this article–to give you a chance to see and use OOD principles so that the formal definition makes sense immediately.

Resources for Article:

 


Further resources on this subject:

  • [article]
  • [article]
  • [article]


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required