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.
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.