CodeGym /Courses /JAVA 25 SELF /Implementing Abstractions and Hierarchies

Implementing Abstractions and Hierarchies

JAVA 25 SELF
Level 19, Lesson 2
Available

1. Building a hierarchy: from abstraction to details

Implementing abstractions and hierarchies is a way to structure code from general rules to concrete details. First, we describe what all objects must be able to do (the abstraction), and then we specify how exactly each concrete class does it.

In programming, as in life, everything starts with questions. For example: “What do a circle and a rectangle have in common?” The answer: they are both shapes. And what do shapes have in common? Usually they have an area, and they can be drawn.

In Java this is expressed via an abstract class:

public abstract class Shape {
    public abstract double area();
    public abstract void draw();
}

What we are saying here:

  • Every shape must be able to compute its area (area()).
  • Every shape must be able to draw itself (draw()).
  • How exactly it does that is not our concern (for now).

Now let’s create concrete shapes:

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle with radius " + radius);
    }
}

public class Rectangle extends Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a rectangle " + width + "x" + height);
    }
}

What did we do?

  • Extracted the common parts into an abstract class.
  • Refined the behavior in subclasses.

Schematic:


.          Shape
         /     \
     Circle   Rectangle

Table: what is implemented where

Class area() draw() Fields
Shape
abstract
abstract
-
Circle implemented implemented
radius
Rectangle implemented implemented
width, height

2. Why is this convenient? (and why it works at all)

A single interface for working with different objects

Suppose you have a collection of shapes:

Shape[] shapes = {
    new Circle(5),
    new Rectangle(3, 4),
    new Circle(2.5)
};

You can iterate over them the same way without thinking about the type:

for (Shape shape : shapes) {
    shape.draw();
    System.out.println("Area: " + shape.area());
}

Let the JVM figure out which one is a circle and which is a rectangle! This is polymorphism (we have already talked about it, and we will discuss it in more detail—in the next block).

Ease of extension

Want to add a triangle? Just write (new type—no changes to the old code): Triangle extends Shape.

public class Triangle extends Shape {
    private double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a triangle: base " + base + ", height " + height);
    }
}

All the rest of the code (for example, iterating over the list of shapes) does not need to change.

Avoiding code duplication

If all shapes get a common property (for example, color), it is convenient to move it into the abstract class:

public abstract class Shape {
    private String color = "black";

    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }

    public abstract double area();
    public abstract void draw();
}

Now any subclass—be it a circle or a triangle—will inherit the color.

3. Practice: building a mini graphics editor

Let’s try to put it all together. Imagine you are building a simple graphics editor.

Abstract class Figure

public abstract class Figure {
    private String color = "black";

    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }

    public abstract void draw();
    public abstract void resize(double factor);
}

Concrete shapes

public class Line extends Figure {
    private double length;

    public Line(double length) {
        this.length = length;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a line of length " + length + " in color " + getColor());
    }

    @Override
    public void resize(double factor) {
        length *= factor;
        System.out.println("New line length: " + length);
    }
}

public class Ellipse extends Figure {
    private double a, b;

    public Ellipse(double a, double b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void draw() {
        System.out.println("Drawing an ellipse with axes " + a + " and " + b + " in color " + getColor());
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("New ellipse axes: " + a + ", " + b);
    }
}

Want to add a new tool, such as Polygon? Just create a new class—the entire editor works through the abstract Figure.

Usage in code

Figure[] figures = {
    new Line(10),
    new Ellipse(5, 3)
};

for (Figure figure : figures) {
    figure.setColor("red");
    figure.draw();
    figure.resize(1.5);
}

Output:

Drawing a line of length 10.0 in color red
New line length: 15.0
Drawing an ellipse with axes 5.0 and 3.0 in color red
New ellipse axes: 7.5, 4.5

Visualization of the hierarchy


.            Figure
             /    \
          Line   Ellipse

4. How to avoid duplication: common fields and methods

Sometimes all subclasses share not only common methods but also common fields (for example, the coordinates of the center). The abstract class is the perfect place for this:

public abstract class Figure {
    private double x, y; // center coordinates

    public Figure(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public void moveTo(double newX, double newY) {
        x = newX;
        y = newY;
        System.out.println("Figure moved to point (" + x + ", " + y + ")");
    }

    public abstract void draw();
}

Now any Line or Ellipse can move without reimplementing this method.

5. Another example: payment systems

Abstraction isn’t just about shapes! Imagine you are writing a payment processing system.

Abstract class Payment

public abstract class Payment {
    public abstract void process();
}

Concrete implementations

public class CreditCardPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Processing a credit card payment");
    }
}

public class PaypalPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Processing a payment via PayPal");
    }
}

Usage

Payment[] payments = {
    new CreditCardPayment(),
    new PaypalPayment()
};

for (Payment payment : payments) {
    payment.process();
}

Output:

Processing a credit card payment
Processing a payment via PayPal

6. Advantages of this approach

  • Unified interface: you can work with different objects in the same way.
  • Extensibility: adding new kinds of objects does not require rewriting existing code.
  • Minimal duplication: common parts are extracted into the base abstract class.
  • Flexibility: you can use collections of abstract types without worrying about details.

7. Real-world example: transport

Abstraction is not only found in textbooks. For example, if you are designing a system to manage transport:

public abstract class Transport {
    public abstract void move();
    public abstract void fuelUp();
}

Concrete types of transport implement the details:

public class Car extends Transport {
    @Override
    public void move() {
        System.out.println("The car drives on the road");
    }

    @Override
    public void fuelUp() {
        System.out.println("Refuel with gasoline");
    }
}

public class Bicycle extends Transport {
    @Override
    public void move() {
        System.out.println("The bicycle moves by pedaling");
    }

    @Override
    public void fuelUp() {
        System.out.println("The bicycle doesn't need fuel, only sandwiches for the cyclist!");
    }
}

8. Helpful diagram: how to build an abstraction hierarchy

[Abstract class]
        |
   [Concrete subclass]
        |
   [Even more concrete subclass] (if needed)

- Put everything common at the top!
- Put everything unique at the bottom!

9. Common mistakes when implementing abstractions and hierarchies

Error #1: Code duplication in subclasses.
If you notice that in every subclass you write the same fields or methods, that’s a signal to move them to the abstract class. Don’t be afraid to make the abstraction “broader” if it reduces duplication.

Error #2: Violating the principle “from general to specific.”
Sometimes beginners start building a hierarchy “from the details,” forgetting the commonality. As a result, odd classes like RedCircleWithShadow appear, which fit poorly into the overall structure. Always identify the abstraction first, then the details.

Error #3: Hierarchies that are too deep.
If your inheritance chain is longer than 3–4 levels, consider whether it’s time to use composition or interfaces instead of inheritance.

Error #4: Forcing the implementation of irrelevant methods.
If an abstract class contains too many abstract methods that are irrelevant for some subclasses, you may need to reconsider the structure. For example, not all vehicles need a fuelUp() method (a bicycle doesn’t need it).

Error #5: Confusing an abstract class with an interface.
Use an abstract class when there is shared state and/or a partial implementation. Use an interface when you only need to “promise” the presence of methods, but do not store data and do not implement behavior. Do not mix these approaches unless necessary.

1
Task
JAVA 25 SELF, level 19, lesson 2
Locked
Transport Simulator: Car Is Driving 🛣️
Transport Simulator: Car Is Driving 🛣️
1
Task
JAVA 25 SELF, level 19, lesson 2
Locked
Geometric Shapes Library 🔵⬛
Geometric Shapes Library 🔵⬛
1
Task
JAVA 25 SELF, level 19, lesson 2
Locked
Basic Personnel Management System 🧑‍💼
Basic Personnel Management System 🧑‍💼
1
Task
JAVA 25 SELF, level 19, lesson 2
Locked
Flexible payment system in a store 💰
Flexible payment system in a store 💰
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION