Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Abstraction Concepts in Java

DZone's Guide to

Abstraction Concepts in Java

Want to learn more about abstraction and how it differs from encapsulation in Java? Check out this post where we explore these ideas further with sample code!

· Java Zone ·
Free Resource

Java-based (JDBC) data connectivity to SaaS, NoSQL, and Big Data. Download Now.

Introduction

At the beginning of all software development projects, the sky is blue. However, there are lots of interconnections and surprises as time progresses and features are added and modified. The developers become entangled by their own code. Concepts, in which lots of time was invested, become muddled, and intentions of methods and services become vague. Eventually, the application will evolve into a complex system.

Ideally, we can recognize the source of complexity and separate different concerns by breaking the program into modules; this is sort of a "divide and conquer" approach. Each module may include business logic, a database layer or GUI, and be developed or tested by different developers, teams, or servers. Also, we gain tons of other advantages — they only depend on an interface in each other and a well-defined contract, where the details of how they are implemented are not something a user should count on. So, it gives a clean separation of what I can count on and how the other details will be implemented. As long as these separations and contracts remain, the code will not have any side effects. The key motivator to this approach is abstraction.

What Is Abstraction?

I believe that most concepts pertaining to abstraction are about ignoring the inessential details; as Ross et al said:

The essence of abstraction is to extract essential properties while omitting inessential details.

But why should we omit inessential details? The reason is to prevent the risk of change by putting aside certain details in order to treat many practical circumstances at the same. We have two abstraction mechanisms: abstraction by parametrization and abstraction by specification. In parameterization, we seek generality by allowing the same mechanism to be adapted to many different contexts by providing it with the information on that context in a form of parameters. But, in specification, we ignore implementation details and agree to treat any implementation that adheres to specification or a certain contact as acceptable.

For the separation of interface from implementation facilitate modularity, the details are hidden here, which are the differences between implementation. They are different, but they can make the same interface. An interface is a separate contract that makes promises between different implementations. So, no matter how it is implemented, what it promises to do is the key point.

There are several advantages of abstraction by specification. Advantages of separation include the implementation. By this, I mean the ability to build one piece without worrying about the details of another. For example, if a customer (method or class) calls a booking action, the customer is not supposed to know every detail about booking. All it needs to know is the interface. The other advantages of abstraction by specification is changing pieces of code without breaking another part of the code and also some other benefit of code reuse.

Despite advantages, as I mentioned earlier, the main goal of abstraction is protection against unwanted changes. By protection, I mean the ability to change in isolation without breaking the rest of the program. Of course, in every system, there are lots of unanticipated and anticipated changes, such as data we put in a configuration file or using methods. Imagine we need to calculate a tax in different parts of our system. So, if the formula changes, we should go and search through code find and replace it. How can we make sure we found an exact match and make sure that the replacement is correct? In this case, the best solution would be abstraction by parameterization. Yes, we create a separate function and get values from parameters; we just need to change the function instead of searching through the whole system.

At first, sight seems like a good idea, but what if we need to have a different formula in different situations? As we have in the design pattern book, we should program to an interface, not an implementation. But the question is: how should we define our interfaces? Are all interfaces abstractions?

Knowing the name or type of something we use is necessary, but it is insufficient. We are seeking a contract, a promise. We need something like preconditions or post conditions. We need to know exactly what to be expected. The explicit definition of these conditions forces the designer to think deeply about what is a requirement of the specification and what is an artifact of a particular implementation.

Also, there are other ways an interface can turn out to be a poor abstraction. Violating subtyping is a pretty obvious sign that the interface in use is a poor abstraction. Subtyping refers to the compatibility of interfaces. By compatibility, I mean behavior, the behavior should be capable. Subtypes have to maintain a guarantee that someone could reasonably infer from the super type. The main idea of subtyping come from type safety issue, which was first introduced by Barbara Liskov. Subtypes must follow the Liskov substitution principle.

In case of violation of subtyping, we need to perform extra operations, such as throwing an exception or validating data before entry. The problem stems from the fact that the operations have side effects. Invoking one operation changes the state of a seemingly unrelated piece of data. Since a higher number of members increases the risk of unexpected side effects and temporal coupling, it should come as no surprise that interfaces mechanically extracted from all members of a concrete class are poor abstractions. By the way, in most cases, value objects offer easier response to demonstrate safe subtyping; as they are pure and immutable, there would be a lack of concern about side effect-driven dependencies between methods. Of course, they are method-call dependencies, but they are more obvious.

Going forward, subclassing is a risk for a good abstraction. Because it implies subtyping, first, we should take care of the promises we make; exactly. This includes the subclass or how we use the subclass or what may inadvertently deviate from our promises. We have to make sure do not break the superclass methods that we inherit and not depend on something that may change out of our control. Indeed, if it changes, the other object(s) can be impacted (as there are immutable). Also, the coupling between objects measures their degree of dependency. Subclassing is tight coupling and is the main reason for the ripple effect. We need a guarantee, something that makes a subclassing safe type. The best way to implement safe subtyping is to avoid it. By avoiding subclassing, we make our abstraction risk free. Instead, we can use delegation or composition.

Is Abstraction Same as Encapsulation?

You might consider abstraction to be the same as encapsulation or even the same as data (information) hiding. In fact, they are not the same, but related, concepts in object-oriented programming. As I mentioned earlier, abstraction is a technique that helps us identify which information is essential and which information should be hidden. Encapsulation is then the technique for enclosing information in such a way to hide details and implementation details of an object.

Let me describe it a bit more with an example of an md5 function (or equivalent class). It is responsible for hashing a string. From outside, the client code only knows the function name and its behavior. This is actually what abstraction guidelines are. Abstraction says that the client should pass a string to md5 and the result must be a hashed string. What is going inside of the function and how it should be implemented is not part of the business of abstraction. In other words, encapsulation is when you start to implement actual code. You write the md5 function and declare such things private and so on. This is actually encapsulation — a realization of your desired abstraction. Also, encapsulation is not the same as data hiding, because even though data may be encapsulated (within structures and arrays), this data is usually not hidden.

The following would be a good example of how there are separated yet interdependent:

class Restaurant {

    public const int STATUS_OPEN = 0;

    public const int STATUS_CLOSED = 1;

    public int status = STATUS_OPEN;

 }


In the code above, there is no example of encapsulation or data hiding. Everything is accessible and clear from the outside.

 class Restaurant {

    public const int STATUS_OPEN = 0;

    public const int STATUS_CLOSED = 1;

    private int status = STATUS_OPEN;

    public int getStatus() {

        return status;

    }

 }


Now, the Restaurant class status is encapsulated but not hidden. Let's see how it should be hidden:

 class Restaurant {

    private const int STATUS_OPEN = 0;

    private const int STATUS_CLOSED = 1;

    private int status = STATUS_OPEN;

    private int getStatus() {

        return status;

    }

    public boolean isOpen() {

        return getStatus() == STATUS_OPEN;

    }

 }


Lastly, the Restaurant status is encapsulated and hidden. Considering that we cannot have data hiding without encapsulation, as you see in the code examples above, encapsulation allows you to check access to your own innards and provide specific methods of performing access. It does not specifically address leaking implementation details. But, in data hiding, it allows you to keep users of the class from knowing too much about the innards of the class, like when you use interfaces.

Hope this helps! Happy coding!

Connect any Java based application to your SaaS data.  Over 100+ Java-based data source connectors.

Topics:
object oriented programming ,abstraction

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}