Replacing Type Code With Subclasses
Tags: programming, refactoring, switchcase
Published: 2024-02-04 10:57:36

---
title: Replacing Type Code With Subclasses
published: true
tags:
  - programming
  - refactoring
  - switchcase
series: Switch-Case
canonical_url: https://rockit.zone/post/switch-case/replacing-type-code-with-subclasses/
cover_image: https://rockit.zone/post/switch-case/replacing-type-code-with-subclasses/cover.jpg
---

We've previously dived into the complexities and maintenance challenges of `switch`-`case` and saw a refactoring technique for simple scenarios.

Today, we'll raise the bar a bit and focus on situations when our type code affects behavior. We'll use pets to demonstrate the concepts.

<!--more-->

## Starting Point

We want to model different pets: dogs, cats, and birds. Initially, we might represent this with a type code:

<!-- embedme snippets/1/1.1.java -->

```java
class Pet {
  static final int DOG = 0;
  static final int CAT = 1;
  static final int BIRD = 2;

  int type;

  Pet(int type) {
    this.type = type;
  }

  String communicate() {
    switch (type) {
      case DOG:
        return "woof";

      case CAT:
        return "meow";

      case BIRD:
        return "chirp";

      default:
        throw new IllegalArgumentException("Unknown pet type");
    }
  }

  String move() {
    switch (type) {
      case DOG:
        return "run";

      case CAT:
        return "climb";

      case BIRD:
        return "fly";

      default:
        throw new IllegalArgumentException("Unknown pet type");
    }
  }
}

```

And its usage:

<!-- embedme snippets/1/1.2.java -->

```java
Pet buddy = new Pet(Pet.DOG);
System.out.println(buddy.communicate());

```

We already saw that `switch`-`case`, has several downsides[^more-first]. It's not only repetitive but also violates the Open/Closed Principle. Adding a new pet type means editing multiple parts of the class.

## Refactoring: Using an Enum

As we learned in the previous post[^more-second], we can refactor this code into an enum:

<!-- embedme snippets/2/2.1.java -->

```java
enum Pet {
  DOG("woof", "run"),
  CAT("meow", "climb"),
  BIRD("chirp", "fly");

  String sound;
  String movement;

  Pet(String sound, String movement) {
    this.sound = sound;
    this.movement = movement;
  }

  String communicate() {
    return sound;
  }

  String move() {
    return movement;
  }
}

```

Its usage is even simpler than the previous:

<!-- embedme snippets/2/2.2.java -->

```java
Pet buddy = Pet.DOG;
System.out.println(buddy.communicate());

```

So far so good. But while pizzas had only data[^more-second], pets are more dynamic and they have behavior, too. For example, they can interact with objects, like balls:

<!-- embedme snippets/2/2.3.java -->

```java
class Ball {
  void fetch() {}
  void bat() {}
  void ignore() {}
}

enum Pet {
  DOG("woof", "run"),
  CAT("meow", "climb"),
  BIRD("chirp", "fly");

  String sound;
  String movement;

  Pet(String sound, String movement) {
    this.sound = sound;
    this.movement = movement;
  }

  String communicate() {
    return sound;
  }

  String move() {
    return movement;
  }

  void interact(Ball ball) {
    switch (this) {
      case DOG:
        ball.fetch();
        break;
      case CAT:
        ball.bat();
        break;
      case BIRD:
        ball.ignore();
        break;
    }
  }
}

```

Despite previous efforts, `switch`-`case` have returned to the code. Fortunately, we can get rid of it.

## Operation as Data

The first possibility is to treat operations like data. There are programming languages, where functions are first-class-citizens and we can store them in variables. Such languages are C and JavaScript. A possible solution in JS can look like the following:

<!-- embedme snippets/3/3.1.js -->

```js
class Ball {
  fetch() {}
  bat() {}
  ignore() {}
}

class Pet {
  static DOG = new Pet('woof', 'run');
  static CAT = new Pet('meow', 'climb');
  static BIRD = new Pet('chirp', 'fly');

  constructor(sound, movement, ballInteraction) {
    this.sound = sound;
    this.movement = movement;
    this.ballInteraction = ballInteraction;
  }

  communicate() {
    return this.sound;
  }

  move() {
    return this.movement;
  }

  interact(ball) {
    this.ballInteraction(ball);
  }
}
```

But in Java, we can't store functions in variables (obviously 😒). But since Java 8, we can use function-like constructs: functional interfaces, lambda expressions (similar to JS arrow functions), and method references (which is a syntactic sugar for lambdas):

<!-- embedme snippets/3/3.2.java -->

```java
enum Pet {
  DOG("woof", "run", (ball) -> ball.fetch()),  // lambda
  CAT("meow", "climb", b -> b.bat()),          // lambda
  BIRD("chirp", "fly", Ball::ignore);          // method reference

  String sound;
  String movement;
  Consumer<Ball> ballInteraction;              // functional interface expecting a single Ball argument

  Pet(String sound, String movement, Consumer<Ball> ballInteraction) {
    this.sound = sound;
    this.movement = movement;
    this.ballInteraction = ballInteraction;
  }

  String communicate() {
    return sound;
  }

  String move() {
    return movement;
  }

  void interact(Ball ball) {
    ballInteraction.accept(ball);
  }
}

```

Despite that it works, this solution has multiple downsides:

1. **Violating Open/Closed Principle**: We still have to modify the `Pet` enum to introduce new pet types.
2. **One instance per type**: We can have only one instance per pet type. For example, we can't have two dogs with different names.
3. **Readability**: The instantiation starts to become long and unreadable, especially with many arguments and more complex interactions.

## Introducing Subclasses

Since Java is an object-oriented language, let's apply some polymorphism. Refactor our `Pet` enum to a base class and subclasses for each pet type. This way, we encapsulate the behavior specific to each pet type within its subclass:

<!-- embedme snippets/4/4.1.java -->

```java
abstract class Pet {
    abstract String communicate();
    abstract String move();
    abstract void interact(Ball ball);
}

class Dog extends Pet {
  @Override
  String communicate() {
    return "woof";
  }

  @Override
  String move() {
    return "run";
  }

  @Override
  void interact(Ball ball) {
    ball.fetch();
  }
}

class Cat extends Pet {
  @Override
  String communicate() {
    return "meow";
  }

  @Override
  String move() {
    return "climb";
  }

  @Override
  void interact(Ball ball) {
    ball.bat();
  }
}

class Bird extends Pet {
  @Override
  String communicate() {
    return "chirp";
  }

  @Override
  String move() {
    return "fly";
  }

  @Override
  void interact(Ball ball) {
    ball.ignore();
  }
}

```

Note: in this example, `Pet` could have been an interface of a class. This detail doesn't matter regarding the approach.

And its usage:

<!-- embedme snippets/4/4.2.java -->

```java
Pet buddy = new Dog();
System.out.println(buddy.communicate());

```

The advantages of this approach:

1. **Encapsulation of Behavior**: Each subclass defines its unique behavior, making the code more organized and readable.
2. **Open/Closed Principle**: Our `Pet` class is now open for extension but closed for modification. Adding a new pet type becomes much easier.
3. **Elimination of `switch`-`case`**: We've removed the cumbersome `switch`-`case` structures, leading to cleaner and more maintainable code.
4. **Dynamicity**: Subclasses can have as many instances as we need. Also, they can have their own fields and behaviors that other pet types don't.

This refactoring's name is (not surprisingly) [replace type code with subclasses](https://refactoring.guru/replace-type-code-with-subclasses).

## Conclusion

When the type code affects behavior, refactoring type codes to subclasses offers numerous benefits in terms of maintainability and scalability. It aligns with the principles of good object-oriented design and results in cleaner, more modular code.

But for simple cases, a good old enum with some function references can be a good choice, too.

[^more-first]: The downsides of switch case in the [first part](https://dev.to/fanatixan/the-dark-side-of-switch-case-kia) of the series

[^more-second]: [Replacing Type Code with Enum](https://dev.to/fanatixan/replacing-type-code-with-class-1c5i)