【问题标题】:Replace switch-case with polymorphism用多态性替换 switch-case
【发布时间】:2018-07-28 03:09:12
【问题描述】:

我知道已经有类似的问题,但是看着它们我仍然对如何设计我的代码有一些疑问。我有一项服务允许User 注册/登录/更新/删除。问题是 User 是一个抽象类型,它包含数据 typeOfUser,基于该数据应该调用实际的 registration / update / delete 方法,现在我在switch-case 块。我想用更好的设计来代替它。

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

    // ...
}

我知道可以使用Visitor 之类的模式,但我真的需要在Enum 本身中添加注册/登录/更新/删除的方法吗?我真的不清楚如何做到这一点,感谢任何帮助。

【问题讨论】:

  • 诀窍是有一个单一的通用服务,然后只需调用一次用户作为输入,即调用一次service.register(user),而不是使用switch 语句。
  • @TimBiegeleisen 是的,但是您将如何实现“更新(用户)”?因为更新适用于客户端或驱动程序特定字段。所以它不能真正使用通用服务,它必须使用特定的。
  • 我指出了多态性可以帮助您的一种方式。您可能在某处需要这样的 switch 语句,尽管它不必在这里。
  • @wesleyy Visitor 模式解决了一个非常具体的问题,并且在大多数情况下(如果不是全部)几乎总是矫枉过正。请参阅我的答案,了解用简单的 Polymorhism.. 解决这个问题的方法

标签: java design-patterns polymorphism visitor-pattern


【解决方案1】:

我想用更好的设计来代替它。

替换switch 语句并利用多态性 的第一步是确保每个操作都有一个合同(读取方法签名),而不管用户类型如何.以下步骤将解释如何实现这一点:

第 1 步:定义一个通用接口来执行所有操作

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

第 2 步:让 UserController 将 UserService 作为依赖项

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

第 3 步:创建子类来处理以 CustomerDto 和 CustomerBO 作为依赖项的不同类型的用户

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
}

以类似的方式实现DriverService 类,分别依赖于DriverBoDriverDto 对象。

第 4 步:实现一个运行时工厂,决定将哪个服务传递给 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));
       }
    }
 
}

第五步调用工厂创建用户控制器

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

上述方法的优点是 switch/if-else 语句被完全移回单个类,即工厂。

【讨论】:

  • 可能是我在网上看到的对多态性最好的解释。
  • @Liga 谢谢!!这是我多年来为 StackOverflow 做出贡献时得到的最好的赞美;)我有更多关于这个主题的答案 1)Polymorphism vs the Strategy Pattern 2)What can Polymorphism do that Inheritance can't? 3)What design pattern does Collections.sort use?
  • 只是一个简短的说明,您认为将工厂类中的条件替换为一组键会更好吗?像return new UserController(services[user.getTypeOfUser()]) 这样的键/值对将匹配类实例?
  • @Liga 是的,这是有道理的。我也有关于这个主题的answer :)。我选择在我的问题中显示“if-else”条件的原因是为了强调这样一个事实,即即使我们利用 多态性,也需要条件。唯一的区别是条件将位于对象连接在一起的单个位置。在现实世界中,将使用 Spring 等 IoC 框架将对象连接在一起。
【解决方案2】:

你会想要这样的:

public abstract class User {
    abstract void register();
    abstract void update();
    abstract void login();

    // maybe some more common non-abstract methods
}

任何类型的 User 都会有一个扩展此抽象类的类,因此必须实现其所有抽象方法,如下所示:

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

这样,您就可以避免使用任何 switch case 代码。例如,您可以有一个Users 数组,它们中的每一个都将使用new Driver()new Customer() 进行实例化。然后,例如,如果您正在迭代这个数组并执行所有 Users login() 方法,每个用户的 login() 将根据其特定类型被调用 ==> 不需要 switch-case,不需要强制转换需要!

【讨论】:

  • 这里的问题是“register(UserDto user)”方法只能根据“typeOfUser”字段来决定注册的用户类型。我认为为每种类型的用户创建一个单独的注册端点是不可扩展的,你认为我错了吗?
  • @wesleyy 在我的实现中你根本不需要UserDto user 参数,这正是它的好处,关于多态性。该实现是特定于类型的,因此类Driver 将有自己的注册、更新和登录方式,客户也是如此。这正是它可扩展的原因,一旦你有了一种新的User 类型,你就创建了一个扩展User 的新类,实现了抽象方法,你就可以开始了,你不必接触任何其他的现有的类。
  • 好的,但是当我通过网络向您发送一个用户对象 json 时,您将如何确定您将调用谁的 .register) 方法?您必须调用具体类型的方法,并且类型信息仅存储在json中的“typeOfUser”字段中
  • @wesleyy JSON 是另一回事,它与业务逻辑层无关。但是,如果我理解正确,根据 JSON 中的 typeOfUser 字段,您将实例化正确的用户。所以如果是typeOfUser: 'driver',你会去User u1 = new Driver()。然后,当你调用u1.register()时,右边的register()将根据u1的运行时类型被调用,在这种情况下为Driver
  • @wesleyy 当您解组 JSON 时,您必须处理使对象成为正确类型的问题。如果您使用的是 JAXB,这可能很有用。 stackoverflow.com/questions/33369790/…
【解决方案3】:

非常简单的示例(仅适用于 DriverDto 和 CustomerDto 的不同登录逻辑)-我已从字段 typeOfUser 辞职(因为在我的解决方案中没有必要)-我不确定这在您的解决方案中是否可行:

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
    }

}

用法:

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

【讨论】:

    【解决方案4】:

    如果您确实需要 UserDTO 中的 TypeOfUser-enum,那么您可以使用服务扩展您的枚举。所以你创建了一个 TypeOfUserService 接口。 CustomerService 和 DriverService 将从该服务继承:

        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) {
             // ...
           }
        }
    

    然后你在你的 TypeOfUser 枚举中创建你的注册、更新等方法:

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

    然后您可以通过以下方式调用注册方法:

        class UserController() {
          public UserDto register(UserDto user) { 
            TypeOfUser.register(user.getTypeOfUser, user);
          }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多