Writing on Science, Technology, and Engineering.

All of my long-form thoughts on programming, leadership, product design, and more, collected in chronological order.

Building a Future-Ready Factory with SOLID Principles

3 min read
Building a Future-Ready Factory with SOLID Principles

Introduction

Whenever we talk about future primarily there are few common things which come to our mind mainly are Humans with exhoskeletons, smart robots, drones, flying vehicles, etc.

Let's say you own a traditional factory and we want to make it ready for this kind of future. We want this factory to be flexible to accomodate these kind of changes. In order to make sure smoothe functioning of this factory we need to make it scalable, maintainable, and adaptable to changes. To achieve this we can make use of SOLID Principles. These are basically, a set of design guidelines that help developers create robust, flexible, and scalable systems.

Let’s explore these principles through the story of a factory revolutionizing its operations with humans and robots.

Story Plot

Once upon a time, there was a bustling factory where two kinds of workers co-existed.

  • Humans: Adaptable and Creative
  • Robots: Precise and Tireless

As the factory grew, a new challenge emerged:

How to manage and scale the workforce efficiently without disrupting operations.

The factory’s CTO decided to redesign the system following the SOLID principles.

Single Responsibility Principle

Initially, the factory had a single Worker class to manage both human and robot workers. However, this class handled too many responsibilities:

  • Scheduling human shifts
  • Maintaining robot firmware
  • Logging tasks, etc.

For example,

class Worker {
    // Handles task performance for both humans and robots
    void performTask(String workerType) {
        if (workerType.equals("Human")) {
            System.out.println("Human is performing the task.");
        } else if (workerType.equals("Robot")) {
            System.out.println("Robot is performing the task.");
        } else {
            System.out.println("Unknown worker type.");
        }
    }

    // Handles scheduling for human workers
    void scheduleShift(String workerType) {
        if (workerType.equals("Human")) {
            System.out.println("Human shift scheduled.");
        } else {
            System.out.println("Shifts are not applicable for robots.");
        }
    }

    // Handles maintenance for robot workers
    void performMaintenance(String workerType) {
        if (workerType.equals("Robot")) {
            System.out.println("Robot maintenance completed.");
        } else {
            System.out.println("Maintenance is not applicable for humans.");
        }
    }
}

This approach made the code fragile and error-prone.

To solve this, the CTO split the Worker class into two distinct classes: HumanWorker and RobotWorker. Each class was responsible only for its specific type of worker.

class HumanWorker {
    void performTask() {
        System.out.println("Human is performing the task.");
    }
}

class RobotWorker {
    void performTask() {
        System.out.println("Robot is performing the task.");
    }
}

Now, any changes to human-specific or robot-specific logic would not affect the other, adhering to SRP.

Open/Closed Principle

After few months of operations, the management decided to introduce the monitoring. Fot this they decided to go ahead with Drones. Now, modifying the existing Worker class to accommodate drones would disrupt the entire system.

Instead, the CTO designed an interface Worker that could be extended by any new worker type.

interface Worker {
    void performTask();
}

class HumanWorker implements Worker {
    public void performTask() {
        System.out.println("Human is performing the task.");
    }
}

class RobotWorker implements Worker {
    public void performTask() {
        System.out.println("Robot is performing the task.");
    }
}

class DroneWorker implements Worker {
    public void performTask() {
        System.out.println("Drone is performing the task.");
    }
}

This way made system extendable, now, whenever they wanted to introduce some new workforce it is just the matter of introducing an extension of Worker.

Liskov Substitution Principle

In the new system, the factory’s assignTask method was designed to work with any Worker. This ensured that all derived classes (like HumanWorker, RobotWorker, and DroneWorker) could be used interchangeably without altering the correctness of the program.

class Factory {
    void assignTask(Worker worker) {
        worker.performTask();
    }
}

This ensured that the factory could assign tasks to any worker without worrying about their specific implementation.

Interface Segregation Principle

Initially, a single interface defined all possible worker behaviors, forcing robot workers to implement methods like attendMeeting, which made no sense for them. To resolve this, the CTO split the interface into smaller, more specific ones.

interface Worker {
    void performTask();
}

interface HumanSpecificWorker extends Worker {
    void attendMeeting();
}

class HumanWorker implements HumanSpecificWorker {
    public void performTask() {
        System.out.println("Human is performing the task.");
    }

    public void attendMeeting() {
        System.out.println("Human is attending the meeting.");
    }
}

class RobotWorker implements Worker {
    public void performTask() {
        System.out.println("Robot is performing the task.");
    }
}

Now, robot workers were no longer forced to implement irrelevant methods.

Dependency Inversion Principle

The factory’s main control system initially depended directly on specific worker implementations, making it rigid and difficult to test. By introducing abstractions, the CTO ensured that the system depended only on the Worker interface.

class Factory {
    private final Worker worker;

    public Factory(Worker worker) {
        this.worker = worker;
    }

    void startWork() {
        worker.performTask();
    }
}

The factory could now easily switch between different worker types, enabling greater flexibility and scalability.

Conclusion
By applying the SOLID principles, the factory not only solved its immediate challenges but also built a scalable and maintainable system ready for future expansion. Whether it was adding new worker types or enhancing existing functionalities, the factory’s codebase remained clean, robust, and adaptable.

In the world of software development, these principles act as a guiding light, ensuring that systems are designed to meet today’s needs while being prepared for tomorrow’s challenges. Just like the factory, you too can revolutionize your projects by embracing SOLID principles.