Model as an Interface or abstract class ?
Model as an interface instead of an abstract class
In Java, we would model something as an interface instead of an abstract class in a situation where we need to define a contract that multiple, potentially unrelated classes can implement. Here are a few specific scenarios:
1. Multiple Inheritance of Type
Java supports single inheritance for classes but allows a class to implement multiple interfaces. If we foresee that a class might need to implement multiple types, using an interface is the way to go. For example, a class FlyingCar
might need to implement both Vehicle
and Flyable
interfaces:
public interface Vehicle {
void drive();
}
public interface Flyable {
void fly();
}
public class FlyingCar implements Vehicle, Flyable {
@Override
public void drive() {
// implementation
}
@Override
public void fly() {
// implementation
}
}
2. Defining Capabilities Without Implementation
Interfaces are ideal for defining capabilities that can be shared across different classes without any concern for how these capabilities are implemented. For example, if we have different classes like Dog
, Bird
, and Fish
, and we want to define a capability Moveable
, we would use an interface:
public interface Moveable {
void move();
}
public class Dog implements Moveable {
@Override
public void move() {
// Dog-specific movement
}
}
public class Bird implements Moveable {
@Override
public void move() {
// Bird-specific movement
}
}
public class Fish implements Moveable {
@Override
public void move() {
// Fish-specific movement
}
}
3. Contract for Service Providers
When creating a service provider framework, interfaces are commonly used to define the contract that service providers must adhere to. This allows us to switch implementations easily without changing the client code. For instance, a PaymentProcessor
interface could have different implementations like PayPalProcessor
and StripeProcessor
:
public interface PaymentProcessor {
void processPayment(double amount);
}
public class PayPalProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
// PayPal-specific payment processing
}
}
public class StripeProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
// Stripe-specific payment processing
}
}
In summary, we would choose an interface over an abstract class when we need to define a set of methods that can be implemented by any class, regardless of its place in the class hierarchy, and especially when we need to leverage Java's capability to implement multiple interfaces.
Model as an abstract instead of an interface class
Modeling something as an abstract class instead of an interface is appropriate in situations where we want to provide a common base with shared code, default implementations, or shared state while still allowing for some methods to be overridden. Here are specific scenarios where an abstract class is preferred:
1. Providing Common Behavior
If we need to provide some common behavior that multiple classes share, use an abstract class. For example, if several types of animals share some behavior like eat
and sleep
, but each has a different makeSound
method, we could use an abstract class:
public abstract class Animal {
public void eat() {
// common eating behavior
}
public void sleep() {
// common sleeping behavior
}
public abstract void makeSound();
}
public class Dog extends Animal {
@Override
public void makeSound() {
// Dog-specific sound
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
// Cat-specific sound
}
}
2. Sharing State
If we need to share some common state or fields among different subclasses, an abstract class is the right choice. For example, we might have a Shape
abstract class that has a color
field:
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public abstract double area();
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double length;
private double width;
public Rectangle(String color, double length, double width) {
super(color);
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
3. Providing Partial Implementation
If we want to provide a partial implementation that other classes can build upon, an abstract class is suitable. This allows us to enforce certain methods to be implemented while providing default implementations for others. For example, in a template method pattern:
public abstract class DataProcessor {
public void process() {
readData();
processData();
writeData();
}
protected abstract void readData();
protected abstract void processData();
protected void writeData() {
// default implementation for writing data
System.out.println("Writing data to file");
}
}
public class CSVDataProcessor extends DataProcessor {
@Override
protected void readData() {
// CSV-specific data reading
System.out.println("Reading CSV data");
}
@Override
protected void processData() {
// CSV-specific data processing
System.out.println("Processing CSV data");
}
}
public class JSONDataProcessor extends DataProcessor {
@Override
protected void readData() {
// JSON-specific data reading
System.out.println("Reading JSON data");
}
@Override
protected void processData() {
// JSON-specific data processing
System.out.println("Processing JSON data");
}
}
Last updated
Was this helpful?