Java Scenario-Based Interview Question 3: Solving the Mars Rover Kata with SOLID Design Principles…
The Mars Rover Kata is a classic coding exercise that challenges developers to think critically about design, modularity, and…
Java Scenario-Based Interview Question 3: Solving the Mars Rover Kata with SOLID Design Principles and Design Patterns.
The Mars Rover Kata is a classic coding exercise that challenges developers to think critically about design, modularity, and extensibility. In this post, we’ll explore a refactored solution to the problem, focusing on how SOLID principles and design patterns can be applied to create clean, maintainable, and extensible code.
By the end of this post, you’ll have a clear understanding of:
- The problem statement and requirements.
- How the solution is structured and implemented.
- The SOLID principles and design patterns used.
- Why these design choices make the code robust and extensible.

Problem Statement
The Mars Rover Kata simulates the movement of a rover on a grid. The rover:
- Starts at a specific position
(x, y)and faces one of the four cardinal directions (N,E,S,W). - Executes a sequence of commands:
F: Move forward.B: Move backward.L: Turn left.R: Turn right.
- Operates on two types of grids:
- Torus Grid: Wraps around edges like a donut.
- Polar Grid: Simulates a spherical surface.
- Detects obstacles and halts further commands upon encountering one.
You can find problem statement here: https://kata-log.rocks/mars-rover-kata
You can find code here: https://github.com/raju4789/code-katas/blob/main/src/main/java/com/raju/codekatas/marsrover/refactor/MarsRoverRefactored.java
Code Walkthrough
1. High-Level Architecture
The solution is designed with modularity and extensibility in mind. Here’s an overview of the key components:
- MarsRoverRefactored: The main class that orchestrates the rover’s behavior.
- Commands: Encapsulate actions like moving forward or turning.
- Direction: Represents the rover’s orientation (
N,E,S,W). - Movement Strategies: Handle grid-specific movement logic (e.g., Torus or Polar grids).
- Validators: Ensure movements are valid (e.g., no obstacles).
- Factories: Dynamically create commands and directions.
2. Core Classes
MarsRoverRefactored
This is the central class that coordinates the rover’s behavior. It uses:
- A
MovementStrategyfor grid-specific logic. - A
MovementValidatorto check for obstacles. - A
CommandFactoryto map commands to actions.
Code:
public class MarsRoverRefactored {
private final MovementValidator movementValidator;
private final MovementStrategy movementStrategy;
private final int stepLength;
private Coordinate position;
private Direction direction;
private boolean obstacleEncountered = false;
public MarsRoverRefactored(MovementValidator movementValidator, MovementStrategy movementStrategy, Coordinate initialPosition, String initialDirection, int stepLength) {
this.movementValidator = movementValidator;
this.movementStrategy = movementStrategy;
this.position = initialPosition;
this.direction = DirectionFactory.getDirection(initialDirection);
this.stepLength = stepLength;
}
public void move(String commands) {
if (commands == null || commands.isEmpty()) {
throw new InvalidCommandException("Commands cannot be null or empty");
}
CommandFactory commandFactory = new CommandFactory(this);
for (char commandChar : commands.toCharArray()) {
if (obstacleEncountered) break;
try {
RoverCommand command = commandFactory.getCommand(commandChar);
command.execute();
} catch (ObstacleException e) {
obstacleEncountered = true;
break;
}
}
}
}
Commands
Each command (MoveForwardCommand, TurnLeftCommand, etc.) implements the RoverCommand interface. This adheres to the Command Pattern, making it easy to add new commands.
Code:
public interface RoverCommand {
void execute();
}
public class MoveForwardCommand implements RoverCommand {
private final MarsRoverRefactored rover;
public MoveForwardCommand(MarsRoverRefactored rover) {
this.rover = rover;
}
@Override
public void execute() {
Coordinate newPosition = rover.getMovementStrategy().moveForward(
rover.getPosition(), rover.getDirection(), rover.getStepLength()
);
if (!rover.getMovementValidator().isMovementValid(newPosition)) {
throw new ObstacleException("Obstacle detected at " + newPosition);
}
rover.setPosition(newPosition);
}
}
Direction
The Direction interface and its implementations (North, East, etc.) encapsulate turning logic. The Factory Pattern is used to create direction objects dynamically.
Code:
public interface Direction {
Direction turnLeft();
Direction turnRight();
DirectionEnum getDirection();
}
public class North implements Direction {
@Override
public Direction turnLeft() {
return new West();
}
@Override
public Direction turnRight() {
return new East();
}
@Override
public DirectionEnum getDirection() {
return DirectionEnum.NORTH;
}
}
Movement Strategies
Movement strategies handle grid-specific logic. For example:
- TorusMovementStrategy wraps coordinates around grid edges.
- PolarMovementStrategy adjusts coordinates for a spherical grid.
Code:
public class TorusMovementStrategy implements MovementStrategy {
@Override
public Coordinate moveForward(Coordinate currentPosition, Direction currentDirection, int stepSize) {
return calculateNewPosition(currentPosition, currentDirection, stepSize);
}
@Override
public Coordinate moveBackward(Coordinate currentPosition, Direction currentDirection, int stepSize) {
return calculateNewPosition(currentPosition, currentDirection, -stepSize);
}
private Coordinate calculateNewPosition(Coordinate currentPosition, Direction currentDirection, int stepSize) {
int newX = currentPosition.getX();
int newY = currentPosition.getY();
switch (currentDirection.getDirection()) {
case NORTH:
newY = wrapCoordinate(newY + stepSize, MAX_Y);
break;
case EAST:
newX = wrapCoordinate(newX + stepSize, MAX_X);
break;
case SOUTH:
newY = wrapCoordinate(newY - stepSize, MAX_Y);
break;
case WEST:
newX = wrapCoordinate(newX - stepSize, MAX_X);
break;
default:
throw new InvalidCommandException("Invalid direction: " + currentDirection.getDirection());
}
return new Coordinate(newX, newY);
}
private int wrapCoordinate(int value, int max) {
return (value % max + max) % max; // Ensures the value wraps correctly for both positive and negative values
}
Validators
The ObstacleMovementValidator checks if a movement is valid by consulting the ObstacleDetector.
Code:
public class ObstacleMovementValidator implements MovementValidator {
private final ObstacleDetector obstacleDetector;
public ObstacleMovementValidator(ObstacleDetector obstacleDetector) {
this.obstacleDetector = obstacleDetector;
}
@Override
public boolean isMovementValid(Coordinate coordinate) {
return !obstacleDetector.isObstacle(coordinate);
}
}
Applying SOLID Principles
1. Single Responsibility Principle (SRP)
Each class has a single responsibility:
CommandFactorymaps commands to actions.ObstacleDetectordetects obstacles.TorusMovementStrategyhandles toroidal grid logic.
2. Open/Closed Principle (OCP)
The system is open for extension but closed for modification. For example:
- Adding a new command (e.g., diagonal movement) only requires creating a new
RoverCommandimplementation. - Adding a new grid type only requires implementing a new
MovementStrategy.
3. Liskov Substitution Principle (LSP)
All Direction and MovementStrategy implementations can be used interchangeably without altering the behavior of MarsRoverRefactored.
4. Interface Segregation Principle (ISP)
Interfaces like RoverCommand and MovementStrategy are small and focused, ensuring classes only implement what they need.
5. Dependency Inversion Principle (DIP)
High-level modules (MarsRoverRefactored) depend on abstractions (MovementStrategy, MovementValidator), not concrete implementations.
Design Patterns in Action
1. Command Pattern
Encapsulates commands as objects, making the system extensible and decoupled.
2. Factory Pattern
Used in CommandFactory and DirectionFactory to create objects dynamically.
3. Strategy Pattern
Encapsulates movement logic in TorusMovementStrategy and PolarMovementStrategy.
Conclusion
The Mars Rover Kata is a great exercise for practicing clean code principles and design patterns. By adhering to SOLID principles and leveraging patterns like Command and Strategy, we created a solution that is functional, extensible, and maintainable.
👋 Let’s Connect!
If you found this post insightful, here’s how you can help spread the knowledge:
👏 Clap if you enjoyed it — your claps motivate me to keep sharing more Java tricks and insights! 🔗 Share this post with your network so others can learn too. 💬 Ask your questions or share your experiences in the comments. Have you encountered this situation? Let’s discuss!
🚀 Follow me for more deep dives into Java, design patterns, and programming gotchas! Let’s learn and grow together. 🌟
메타데이터
- post_id
- 08a498fb0fbe
- slug
- a-step-by-step-guide-to-solving-the-mars-rover-kata-with-solid-design-principles-and-design-08a498fb0fbe
- url
- https://medium.com/@narasimha4789/a-step-by-step-guide-to-solving-the-mars-rover-kata-with-solid-design-principles-and-design-08a498fb0fbe
- canonical_url
- https://medium.com/@narasimha4789/a-step-by-step-guide-to-solving-the-mars-rover-kata-with-solid-design-principles-and-design-08a498fb0fbe
- author_url
- https://medium.com/@narasimha4789
- status
- ok
- fetched_at
- 2026-08-06 07:53:32