Reputation: 2735
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.
Upvotes: 13
Views: 23535
Reputation: 15212
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().equals(CUSTOMER)) {
return new UserController(new CustomerService(user));
} else if(user.getTypeOfUser().equals(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.
Upvotes: 24
Reputation: 21
If you really need that TypeOfUser
-enum in your UserDTO, then you could extend your enum with a serivce. So you create a TypeOfUserService interface. CustomerSerivce and DriverService will inherit from that service:
public interface TypeOfUserService {
public void register(UserDTO user);
// ...
}
public class CustomerService implements TypeOfUserService {
@Override
public void register(UserDTO user) {
// ...
}
}
public class DriverService implements TypeOfUserService {
@Override
public void register(UserDTO user) {
// ...
}
}
Then you create your register, update, etc. methods in your TypeOfUser enum:
public enum TypeOfUser {
DRIVER(new DriverService()),
CUSTOMER(new CustomerService());
private TypeOfUserService typeOfUserService;
TypeOfUser(TypeOfUserService typeOfUserService) {
this.typeOfUserService = typeOfUserService;
}
public static void register(String typeOfUser, UserDTO user) {
TypeOfUser.valueOf(typeOfUser).typeOfUserService.register(user);
}
// ...
}
You could then call the register method via:
class UserController() {
public UserDto register(UserDto user) {
TypeOfUser.register(user.getTypeOfUser, user);
}
}
Upvotes: 1
Reputation: 375
Very simple example (only for different login logic for DriverDto and CustomerDto) - I've resigned from field typeOfUser (because it is not necessary in my solution) - I'm not sure that this is possible in your solution:
public abstract class UserDto {
// put some generic data & methods here
}
public class CustomerDto extends UserDto {
private String customerName;
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
}
public class DriverDto extends UserDto {
private String driverName;
public String getDriverName() {
return driverName;
}
public void setDriverName(String driverName) {
this.driverName = driverName;
}
}
public class ThisIsServiceOrDelegateToOtherServices {
public void login(CustomerDto customer) {
String name = customer.getCustomerName();
System.out.println(name);
// work on name here
}
public void login(DriverDto customer) {
String name = customer.getDriverName();
System.out.println(name);
// work on name here
}
}
Usage:
public static void main(String... args) {
//demo data
CustomerDto customer = new CustomerDto();
customer.setCustomerName("customerName");
DriverDto driver = new DriverDto();
driver.setDriverName("driverName");
// usage
ThisIsServiceOrDelegateToOtherServices service = new ThisIsServiceOrDelegateToOtherServices();
service.login(customer);
service.login(driver);
}
Upvotes: 0
Reputation: 2616
You'd want something like that:
public abstract class User {
abstract void register();
abstract void update();
abstract void login();
// maybe some more common non-abstract methods
}
Any type of User will have a class that extends this abstract class and therefore must implement all its abstract methods, like this:
public class Driver extends User {
public void register() {
// do whatever a driver does when register...
}
public void update() {
// do whatever a driver does when update...
}
public void login() {
// do whatever a driver does when login...
}
}
public class Customer extends User {
public void register() {
// do whatever a customer does when register...
}
public void update() {
// do whatever a customer does when update...
}
public void login() {
// do whatever a customer does when login...
}
}
This way, you're avoiding any switch case code. For instance, you can have an array of User
s, each one them will be instantiated using new Driver()
or new Customer()
. Then, for example, if you're iterating over this array and executing all the User
s login()
method, each user's login() will be called according to its specific type ==> no switch-case needed, no casting needed!
Upvotes: 4