简体   繁体   中英

Replace switch-case with polymorphism

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 userDto = 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 ie the factory.

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!

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);
}

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);
      }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM