WalzoneInterview Prep
📞 Interviewing soon? Practice with a realistic AI mock phone interview — it calls you, then scores you. First 15 min FREE →

Design Patterns · Expert · question 66 of 100

Explain how the State pattern can be combined with other behavioral patterns like the Mediator or Command patterns to manage complex systems.?

📕 Buy this interview preparation book: 100 Design Patterns questions & answers — PDF + EPUB for $5

The State pattern is a behavioral software design pattern that allows an object to behave differently based on its internal state. The Mediator pattern is also a behavioral pattern that helps to centralize communication between multiple objects. The Command pattern, on the other hand, is used to encapsulate a request as an object, allowing it to be treated as a standalone entity.

The State pattern can be combined with other patterns like Mediator or Command to manage complex systems because it helps to simplify the system and manage dependencies between objects. For example, a complex system might have multiple objects with different states, and it might be difficult to manage the transitions between these states. By using the State pattern, we can encapsulate the state-specific behavior into separate objects, and use a context object to manage the state transitions.

Now let’s see how the State pattern can be combined with other patterns:

1) State pattern + Mediator pattern: In this combination, the Mediator pattern can act as a central hub, managing the communication between objects that have different states. For instance, consider a vending machine that has multiple objects such as a coin, candy dispenser, and display. By using the Mediator pattern, we can simplify the system, and by using the State pattern, we can manage the state transitions of the objects. The Mediator object can keep track of the current state of the vending machine and route the requests to the appropriate object based on its current state.

Here’s an example of this combination with Java code:

interface VendingMachineState {
    void insertCoin(int amount);
    void selectProduct(String code);
    void dispenseProduct();
}

class NoCoinState implements VendingMachineState {
    private final VendingMachine vendingMachine;
    public NoCoinState(VendingMachine vm) { this.vendingMachine = vm;}
    public void insertCoin(int amount) {
        vendingMachine.setCurrentState(vendingMachine.coinInsertedState);
        vendingMachine.addBalance(amount);
    }
    public void selectProduct(String code) {
        System.out.println("Please insert a coin first!");
    }
    public void dispenseProduct() {
        System.out.println("Please insert a coin first!");
    }
}

class CoinInsertedState implements VendingMachineState {
    private final VendingMachine vendingMachine;
    public CoinInsertedState(VendingMachine vm) { this.vendingMachine = vm;}
    public void insertCoin(int amount) {
        vendingMachine.addBalance(amount);
    }
    public void selectProduct(String code) {
        if(vendingMachine.getProduct(code) != null){
            vendingMachine.setCurrentState(vendingMachine.productSelectedState);
            vendingMachine.setSelectedProductCode(code);
        }else{
            System.out.println("Invalid product code!");
        }
    }
    public void dispenseProduct() {
        System.out.println("Please select a product first!");
    }
}

class ProductSelectedState implements VendingMachineState {
    private final VendingMachine vendingMachine;
    public ProductSelectedState(VendingMachine vm) { this.vendingMachine = vm;}
    public void insertCoin(int amount) {
        vendingMachine.addBalance(amount);
    }
    public void selectProduct(String code) {
        System.out.println("Please wait, dispensing product!");
    }
    public void dispenseProduct() {
        vendingMachine.dispenseSelectedProduct();
        vendingMachine.setCurrentState(vendingMachine.noCoinState);
    }
}

class VendingMachine {
    VendingMachineState noCoinState;
    VendingMachineState coinInsertedState;
    VendingMachineState productSelectedState;
    VendingMachineState currentState;
    int balance = 0;
    String selectedProductCode;
    Map<String, Integer> inventory = new HashMap<>();
    Map<String, Integer> prices = new HashMap<>();

    public VendingMachine() {
        noCoinState = new NoCoinState(this);
        coinInsertedState = new CoinInsertedState(this);
        productSelectedState = new ProductSelectedState(this);
        setCurrentState(noCoinState);
    }

    public void setCurrentState(VendingMachineState state) {
        this.currentState = state;
    }

    public void addBalance(int amount) {
        this.balance += amount;
        System.out.println("Balance added: " + amount);
    }

    public void dispenseSelectedProduct() {
        int price = prices.get(selectedProductCode);
        inventory.put(selectedProductCode, inventory.get(selectedProductCode) - 1);
        System.out.println("Product Dispensed: " + selectedProductCode);
        System.out.println("Balance returned: " + (balance - price));
        balance = 0;
    }

    public void selectProduct(String code) {
        currentState.selectProduct(code);
    }

    public void insertCoin(int amount) {
        currentState.insertCoin(amount);
    }
    
    public void dispenseProduct() {
        currentState.dispenseProduct();
    }
    
    public void addProduct(String code, int count, int price) {
        inventory.put(code, count);
        prices.put(code, price);
    }

    public int getProduct(String code) {
        if (inventory.get(code) > 0) {
            return prices.get(code);
        } else {
            return 0;
        }
    }

}

In this code, we have implemented a Vending Machine using the State pattern. We have three states i.e NoCoinState, CoinInsertedState, ProductSelectedState. We also have defined the Mediator class VendingMachine, that manages the communication between different objects i.e coin dispenser, product dispenser, etc. We can see that the Mediator manages the state transitions of objects by calling their corresponding methods based on the current state.

2) State pattern + Command pattern: In this combination, we can use the Command pattern to encapsulate a state transition as an object. For instance, consider a door with multiple states, and we want to create a log of every state transition. By using the State pattern, we can define each state as an object, and by using the Command pattern, we can encapsulate the state transitions as Command objects.

Here’s an example of this combination with Java code:

interface DoorState {
    void open();
    void close();
    String getName();
}

class OpenState implements DoorState {
    private final Door door;
    OpenState(Door door) { this.door = door; }
    public void open() {
        System.out.println("Door is already open!");
    }
    public void close() {
        door.setCurrentState(door.getCloseState());
        System.out.println("Door is closed!");
    }
    public String getName() {
        return "Open";
    }
}

class CloseState implements DoorState {
    private final Door door;
    CloseState(Door door) { this.door = door; }
    public void open() {
        door.setCurrentState(door.getOpenState());
        System.out.println("Door is open!");
    }
    public void close() {
        System.out.println("Door is already closed!");
    }
    public String getName() {
        return "Close";
    }
}

class Door {
    private DoorState openState = new OpenState(this);
    private DoorState closeState = new CloseState(this);
    private DoorState currentState = closeState;
    private List<DoorCommand> commands = new ArrayList<>();
    public void setCurrentState(DoorState state) {
        currentState = state;
    }
    public void open() {
        commands.add(new DoorOpenCommand(currentState));
        currentState.open();
    }
    public void close() {
        commands.add(new DoorCloseCommand(currentState));
        currentState.close();
    }
    public DoorState getOpenState() {
        return openState;
    }
    public DoorState getCloseState() {
        return closeState;
    }
    public void printCommands(){
        System.out.println("List of commands executed:");
        for (DoorCommand command : commands) {
            System.out.println(command.getName());
        }
    }
}

interface DoorCommand {
    void execute();
    String getName();
}

class DoorOpenCommand implements DoorCommand {
    private final DoorState state;
    DoorOpenCommand(DoorState state) { this.state = state; }
    public void execute() {
        state.open();
    }
    public String getName() {
        return state.getName() + " command executed: Door is open.";
    }
}

class DoorCloseCommand implements DoorCommand {
    private final DoorState state;
    DoorCloseCommand(DoorState state) { this.state = state; }
    public void execute() {
        state.close();
    }
    public String getName() {
        return state.getName() + " command executed: Door is closed.";
    }
}

In this code, we have implemented a Door and defined its two states i.e OpenState and CloseState using the State pattern. We have also defined the Command pattern, which encapsulates the state transitions as Command objects. We maintain the list of executed commands using the list of DoorCommand objects.

In conclusion, we can say that the State pattern is a powerful tool for managing complex systems, and by combining it with other behavioral patterns such as Mediator or Command, we can further simplify the system and make it more manageable.

Reading is step one. Saying it out loud is the interview. Our AI interviewer calls your phone and runs a realistic Design Patterns interview — then scores it.
📞 Practice Design Patterns — free 15 min
📕 Buy this interview preparation book: 100 Design Patterns questions & answers — PDF + EPUB for $5

All 100 Design Patterns questions · All topics