Replace switch-case with polymorphism

wesleyy picture wesleyy · Feb 17, 2018 · Viewed 11.1k times · Source

I know there are similar questions already, but looking at them I still have some doubts about how I should design my code. I have a service that allows for User registration / login /update / delete. The thing is that the User is an abstract type, which contains the data typeOfUser based on which the actual registration / update / delete methods should be called, and right now I do that in a switch-case block. I'd like to replace that with some better design.

UserController.java

public class UserController {

    public UserDto register(UserDto user) {
        switch(user.getTypeOfUser()) {
        case DRIVER: return driverService.register(user);
        case CUSTOMER: return customerService.register(user);
        // ...
        }
    } 

    public UserDto update(UserDto user) {
        switch(user.getTypeOfUser) {
        case DRIVER: return driverService.update((DriverDto) user);
        case CUSTOMER: return customerService.update((CustomerDto) user);
        // ...
        }
    }

    public UserDto login(long userId) {
        loginService.login(userId);

        UserBO user = userService.readById(userId);

        switch(user.getTypeOfUser) {
        case DRIVER: return DriverDto.fromBO((DriverBO) user);
        case CUSTOMER: return CustomerDto.fromBO((CustomerBO) user);
        // ...
        }
    }

    // ...
}

I understand that something like Visitor pattern could be used, but would I really need to add the methods of registration / login /update / delete in the Enum itself? I don't really have a clear idea on how to do that, any help is appreciated.

Answer

CKing picture CKing · Feb 18, 2018

I'd like to replace that with some better design.

The first step towards replacing the switch statement and take advantage of Polymorphism instead is to ensure that there is a single contract (read method signature) for each of the operations regardless of the user type. The following steps will explain how to achieve this :

Step 1 : Define a common interface for performing all operations

interface UserService {
    public UserDto register(UserDto user);
    public UserDto update(UserDto user);
    public UserDto login(UserDto user)
}

Step 2 : Make UserController take a UserService as a dependency

public class UserController {

    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    public UserDto register(UserDto user) {
       userService.register(user);
    } 

    public UserDto update(UserDto user) {
        userService.update(user);

    }

    public UserDto login(long userId) {
        userService.login(user);
    }

}

Step 3 : Create subclasses to handle different types of users that take CustomerDto and CustomerBO as a dependency

class CustomerService implements UserService {
    private CustomerDto userDto;
    private CustomerBO userBO;

    public CustomerService(UserDto userDto,UserBO userBo) {
          this.userDto = (CustomerDto)userDto;
          this.userBO= (CustomerBO)userBo;
    }

    //implement register,login and update methods to operate on userDto and userBo
}

Implement the DriverService class in a similar fashion with a dependency on DriverBo and DriverDto objects respectively.

Step 4 : Implement a runtime factory that decides which service to pass to UserController :

public UserControllerFactory {
    public static void createUserController(UserDto user) {
       if(user.getTypeOfUser().equlas(CUSTOMER)) { 
           return new UserController(new CustomerService(user));
       } else if(user.getTypeOfUser().equlas(DRIVER)) {
           return new UserController(new DriverService(user));
       }
    }

}

Step 5 Call the factory to create a user controller

UserDto user = someMethodThatCreatesUserDto(();
UserController controller = UserControllerFactory.createUserController(user);
controller.register();
controller.update();
controller.login();

The advantage of the above approach is that the switch/if-else statements are moved all they way back to a single class i.e the factory.