【译】重构Java switch语句的七种方法

1,258 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

众所周知,switch语句常常对SOLID原则中的单一责任原则和开放-封闭原则很不友好,一般情况下,我们可以选择比switch更好的替代方案,尤其当我们处理以下情况时:

  • 嵌套在大型方法中的switch
  • 相互依赖的switch
  • 大型switch(在case下有许多指令并且/或者有许多case分支) 在这篇文章中,我们将选取一些switch的例子,并尝试提供一些消除或隐藏switch语句的替代方案。

1. 通过枚举实现策略模式

示例代码:SwitchToStrategyEnum

让我们来看一些典型的使用了Java枚举以及一个(或多个)依赖该枚举的switch语句的案例。假设我们有以下简单的Java枚举:

public enum PlayerTypes { TENNIS,
   FOOTBALL, SNOOKER
}

以及,下面这个用来创建不同类型球员的swith语句:

public class PlayerCreator {
   public Player createPlayer(PlayerTypes playerType)
         { switch (playerType) {
      case TENNIS:
         return new TennisPlayer();
      case FOOTBALL:
         return new FootballPlayer();
      case SNOOKER:
         return new SnookerPlayer();

      default:
         throw new IllegalArgumentException("Invalid player type: "
            + playerType);
      }
   }
}

现在让我们创建一个网球运动员, PlayerTypes.TENNIS:

PlayerCreator playerCreator = new PlayerCreator();
Player tennisPlayer =
   playerCreator.createPlayer(PlayerTypes.TENNIS);

基于枚举特定常量方法(Enum Type with Constant-specific Method Implementation)

当然,有一种更好的方式可以将行为与每个枚举常量关联起来。这种方式被称为"具有常量特定方法实现的枚举类型(enum type with constant-specific method implementation)",它在Joshua Bloch的《Effective Java》第二版中有描述.参照这个想法,我们可以把一个抽象方法添加到我们的PlayerTypes,对于每个值,我们提供一个实现,如下所示:

public enum PlayerTypes { TENNIS {
   @Override
   public Player createPlayer() {
      return new TennisPlayer();
   }
},
FOOTBALL {
   @Override
   public Player createPlayer() {
      return new FootballPlayer();
   }
},
SNOOKER {
   @Override
      public Player createPlayer() {
         return new SnookerPlayer();
      }
   };

   public abstract Player createPlayer();
}

现在我们可以这样创建一个足球运动员对象,PlayerTypes.FOOTBALL:

Player footballPlayer =
   PlayerTypes.valueOf("FOOTBALL").createPlayer();

2. 实现命令模式

示例代码: SwitchToCommand

这一次,让我们基于字符串编写同样的switch逻辑。在Java 7+,我们可以在switch语句的表达式中使用String对象。值得一提的是,大多数Java编译器能对这种实现产生比if-else-if链更有效的字节码。所以,这并不是很糟糕 🙂 不过,由于使用了switch,仍然拥有同样的缺点。

public class PlayerCreator {

   public Player createPlayer(String playerType) {
      switch (playerType) {
         case "TENNIS":
            return new TennisPlayer();
         case "FOOTBALL":
            return new FootballPlayer();
         case "SNOOKER":
            return new SnookerPlayer();

         default:
            throw new IllegalArgumentException
               ("Invalid player type: " + playerType);
      }
   }

}

创建一个网球运动员对象, "TENNIS":

PlayerCreator playerCreator = new PlayerCreator();
Player tennisPlayer = playerCreator.createPlayer("TENNIS");

实现命令模式

我们可以通过"命令 "模式来重构以上代码。该设计模式需要两个步奏实现。第一步,我们定义一个接口:

public interface Command {

   Player create();
}

第二步,让每种运动员类型实现该接口:

public class CreatePlayerCommand {

   private static final Map<String, Command> PLAYERS;

   static {
      final Map<String, Command> players = new HashMap<>();
      players.put("TENNIS", new Command() {
         @Override
         public Player create() {
            return new TennisPlayer();
         }
      });

      players.put("FOOTBALL", new Command() {
         @Override
         public Player create() {
            return new FootballPlayer();
         }
      });

      players.put("SNOOKER", new Command() {
         @Override
         public Player create() {
            return new SnookerPlayer();
         }
      });

      PLAYERS = Collections.unmodifiableMap(players);
   }

   public Player createPlayer(String playerType) {
      Command command = PLAYERS.get(playerType);

      if (command == null) {
         throw new IllegalArgumentException("Invalid player type: "
            + playerType);
      }

      return command.create();
   }

}

创建一个斯诺克运动员对象:

CreatePlayerCommand createCommand = new CreatePlayerCommand();
Player snookerPlayer = createCommand.createPlayer("SNOOKER");

5. 使用抽象工厂

示例代码: SwitchToAbstractFactory, SwitchToPolymorphism 在本节中,我们会按照Robert C. Martin的《Clean Code》的内容来做。我们从一个可以通过抽象工厂模式"隐藏"的switch开始。

public class ClassicPlayer {

   private final Type type;
   private final int delta;

   public ClassicPlayer(Type type, int delta) {
      this.type = type;
      this.delta = delta;
   }

   public Type getType() {
      return type;
   }

   public int getDelta() {
      return delta;
   }

}

public class Statistics {

   public int playerEndurance(ClassicPlayer player) {

      int delta = player.getDelta();

      switch (player.getType()) {
         case TENNIS:
            return ComputeEnduranceAlgorithm.basicEndurance(delta)
               + ComputeEnduranceAlgorithm.hardEndurance(delta);
         case FOOTBALL:
            return ComputeEnduranceAlgorithm.hardEndurance(delta)
               * ComputeEnduranceAlgorithm.factorEndurance(delta);
         case SNOOKER:
            return ComputeEnduranceAlgorithm.basicEndurance(delta);

         default:
            throw new IllegalArgumentException
               ("Invalid player type: " + player.getType());
      }
   }

}

计算网球运动员的耐力:

Statistics statistics = new Statistics();

ClassicPlayer player = new ClassicPlayer(Type.TENNIS, 54);
int tennisPlayerEndurance = statistics.playerEndurance(player);

好吧,这其实是一个混乱的实现! 想象一下,你需要添加一个新的方法来计算一个玩家的反应速度。这将需要添加另一个switch语句,或者有可能让你在每个case语句下嵌套更多的if并将上述方法重命名为playerStatistics。而且每次需要添加一个新方法时,这段代码都需要做相应的调整。

实现抽象工厂模式

我们可以通过使用多态和抽象工厂设计模式来重写这段代码。首先,我们去掉ClasssicPlayer类,创建一个抽象的Player,如下所示:

public abstract class Player {

   private final Type type;
   private final int delta;

   public Player(Type type, int delta) {
      this.type = type;
      this.delta = delta;
   }

   public Type getType() {
      return type;
   }

   public int getDelta() {
      return delta;
   }

   public abstract int playerEndurance();

   // More similar methods
}

现在,我们可以实现足球、网球和斯诺克球员。举例来说,我们可以像下面这样创建一个SnookerPlayer(斯诺克球员):

public class SnookerPlayer extends Player {

   public SnookerPlayer(Type type, int delta) {
      super(type, delta);
   }

   @Override
   public int playerEndurance() {
      return ComputeEnduranceAlgorithm.basicEndurance
         (this.getDelta());
   }
}

然后,我们定义一个PlayerFactory接口:

public interface AbstractPlayerFactory {

   public Player createPlayer(Type type, int delta);
}

最后,我们把switch "埋" 在这个接口的实现中,如下:

public class PlayerFactory implements AbstractPlayerFactory {

   @Override
   public Player createPlayer(Type type, int delta) {
      switch (type) {
         case TENNIS:
            return new TennisPlayer(type, delta);
         case FOOTBALL:
            return new FootballPlayer(type, delta);
         case SNOOKER:
            return new SnookerPlayer(type, delta);

         default:
         throw new IllegalArgumentException("Invalid player type: "
            + type);
      }
   }

}

计算一个斯诺克球员的耐力:

PlayerFactory playerFactory = new PlayerFactory();
Player snookerPlayer = playerFactory.createPlayer(Type.SNOOKER, 8);
int snookerPlayerEndurance = snookerPlayer.playerEndurance();

或者,我们可以直接实例化想要的Player类,并且去掉了switch:

SnookerPlayer snookerPlayer = new SnookerPlayer(7);
int snookerPlayerEndurance  = snookerPlayer.playerEndurance();

6. 实现状态模式

示例代码: SwitchToState

假设我们有以下两个相互依赖的switch:

public class ClassicPlayer {

   private int state;

   public void register() {
      switch (state) {
         case 0:
         state = 1; System.out.println("Registering ...");
         break;
      default:
         System.out.println("Aready Registered ...");
      }
   }

   public void unregister() {
      switch (state) {
         case 1:
            state = 0;
            System.out.println("Un-registering ...");
            break;
         default:
            System.out.println("Aready Unregistered ...");
      }
   }
}

为了简洁起见,在这个例子中,我们只有两个相互依赖的switch结构,我们可以按照下面的例子来调用它们:

ClassicPlayer classicPlayer = new ClassicPlayer();
classicPlayer.register();
classicPlayer.unregister();
// Causes "Already Unregistered ..." message
classicPlayer.unregister();

实现状态模式

进一步,让我们使用状态设计模式来消除这些switch语句。我们从一个简单的接口开始--该接口旨在为我们的状态(动作)定义好契约,即注册和取消注册:

public interface PlayerState {

   void register();
   void unregister();
}

然后,我们定义一个实现了PlayerState接口的Player类:

public class Player implements PlayerState {

   private PlayerState registered;
   private PlayerState unregistered;

   private PlayerState state;

   public Player() {
      this.registered = new PlayerRegister(this);
      this.unregistered = new PlayerUnregister(this);

      this.state = this.unregistered;
   }

   @Override
   public void register() {
      state.register();
   }

   @Override
   public void unregister() {
      state.unregister();
   }

   public PlayerState getRegistered() {
      return registered;
   }

   public void setRegistered(PlayerState registered) {
      this.registered = registered;
   }

   public PlayerState getUnregistered() {
      return unregistered;
   }

   public void setUnregistered(PlayerState unregistered) {
      this.unregistered = unregistered;
   }

   public PlayerState getState() {
      return state;
   }

   public void setState(PlayerState state) {
      this.state = state;
   }

}

PlayerRegister and PlayerUnregister代码列举如下:

public class PlayerRegister implements PlayerState {

   private final Player player;

   public PlayerRegister(Player player) {
      this.player = player;
   }

   @Override
   public void register() {
      System.out.println("Already Registered ...");
   }

   @Override
   public void unregister() {
      System.out.println("Unregistering ...");
      player.setState(player.getUnregistered());
   }

}

public class PlayerUnregister implements PlayerState {

   private final Player player;

   public PlayerUnregister(Player player) {
      this.player = player;
   }

   @Override
   public void register() {
      System.out.println("Registering ...");
      player.setState(player.getRegistered());
   }

   @Override
   public void unregister() {
      System.out.println("Already Unregistered ...");
   }

}

现在,我们可以创建一个Player对象并和"玩"下它的状态:

Player player = new Player();
player.register();
player.unregister();
// 打印出"Already Unregistered ..."消息
player.unregister();

7.通过Predict接口实现

示例代码: SwitchToPredicate

在最后一个例子中,让我们设想一个在case中包含if分支的switch,如下所示:

public class PlayerCreator {

   public Player createPlayer(String playerType, int rank) {
      switch (playerType) {
         case "TENNIS":
            if (rank == 1) {
               return new TennisPlayer("Rafael Nadal");
            }
            if (rank > 1 && rank < 5) {
               return new TennisPlayer("Roger Federer");
            }
            if (rank >= 5 && rank <= 10) {
               return new TennisPlayer("Andy Murray");
            }

         case "FOOTBALL":
            if (rank == 1 || rank == 2) {
               return new FootballPlayer("Lionel Messi");
            }
            if (rank > 2 && rank <= 10) {
               return new FootballPlayer("Cristiano Ronaldo");
            }

         case "SNOOKER":
            if (rank == 1) {
               return new SnookerPlayer("Ronnie O'Sullivan");
            }
            if (rank == 2) {
               return new SnookerPlayer("Mark Selby");
            }
            if (rank > 3 && rank < 7) {
               return new SnookerPlayer("John Higgins");
            }
            if (rank >= 7 && rank <= 10) {
               return new SnookerPlayer("Neil Robertson");
            }

         default:
            throw new IllegalArgumentException
               ("Invalid player type: " + playerType);
      }
   }

}

(如下代码)得到输出:Tennis player: Andy Murray:

PlayerCreator playerCreator = new PlayerCreator();
Player tennisPlayer = playerCreator.createPlayer("TENNIS", 5);

如果我们认同if语句可以被认为是Predicate<Integer>,那么我们可以在一个如下所示的工具类中重构它:

public final class PlayerSupplier {

   private PlayerSupplier() {
      throw new AssertionError();
   }

   private static final Map<String,
      Map<Predicate<Integer>,
      Supplier<Player>>> PLAYER_CREATOR;

   static {
      final Map<String, Map<Predicate<Integer>,
         Supplier<Player>>> playerCreator = new HashMap<>();

      final Map<Predicate<Integer>,
         Supplier<Player>> tennisPlayers = new HashMap<>();
      tennisPlayers.put(rank -> rank == 1, () ->
         new TennisPlayer("Rafael Nadal"));
      tennisPlayers.put(rank -> rank > 1 && rank < 5, () ->
         new TennisPlayer("Roger Federer"));
      tennisPlayers.put(rank -> rank >= 5 && rank <= 10, () ->
         new TennisPlayer("Andy Murray"));

      final Map<Predicate<Integer>, Supplier<Player>>
         footballPlayers = new HashMap<>();
      footballPlayers.put(rank -> rank == 1 || rank == 2,()->
         new TennisPlayer("Lionel Messsi"));
      footballPlayers.put(rank -> rank > 2 && rank <= 10,()->
         new TennisPlayer("Cristiano Ronaldo"));

      final Map<Predicate<Integer>, Supplier<Player>>
         snookerPlayers = new HashMap<>();
      snookerPlayers.put(rank -> rank == 1, () ->
         new TennisPlayer("Ronnie O'Sullivan"));
      snookerPlayers.put(rank -> rank == 2, () ->
         new TennisPlayer("Mark Selby"));
      snookerPlayers.put(rank -> rank > 3 && rank < 7, () ->
         new TennisPlayer("John Higgins"));
      snookerPlayers.put(rank -> rank >= 7 && rank <= 10, () ->
         new TennisPlayer("Neil Robertson"));

      playerCreator.put("TENNIS", tennisPlayers);
      playerCreator.put("FOOTBALL", footballPlayers);
      playerCreator.put("SNOOKER", snookerPlayers);

      PLAYER_CREATOR = Collections.unmodifiableMap(playerCreator);
   }

   public static final Player supplyPlayer(String playerType,
         int rank) {

      if (rank < 1 || rank > 10) {

         throw new IllegalArgumentException("Invalid rank: " +
            rank);
      }

      if (!PLAYER_CREATOR.containsKey(playerType)) {
         throw new IllegalArgumentException("Invalid player type: "
            + playerType);
      }

      Map<Predicate<Integer>, Supplier<Player>> players =
         PLAYER_CREATOR.get(playerType);
      for (Entry<Predicate<Integer>, Supplier<Player>>
            entry : players.entrySet()) {
         if (entry.getKey().test(rank)) {
            return entry.getValue().get();
         }
      }

      throw new IllegalStateException("The players map is
         corrupted");
   }

}

创建Football player: Cristiano Ronaldo的用法:

Player footballPlayer = PlayerSupplier.supplyPlayer("FOOTBALL", 6);

另一种方法是使用枚举enum,如下所示:

public enum PlayerTypes {

   TENNIS(Collections.unmodifiableList(Arrays.asList(
      () -> new TennisPlayer("Rafael Nadal"),
      () -> new TennisPlayer("Roger Federer"),
      () -> new TennisPlayer("Andy Murray"))
   ),
   Collections.unmodifiableList(Arrays.asList(
      rank -> rank == 1, rank -> rank > 1 &&
      rank < 5, rank -> rank >= 5 && rank <= 10))
   ),
   FOOTBALL(Collections.unmodifiableList(Arrays.asList(
      () -> new FootballPlayer("Lionel Messi"),
      () -> new FootballPlayer("Cristiano Ronaldo"))
   ),
   Collections.unmodifiableList(Arrays.asList(
      rank -> rank == 1 || rank == 2,
      rank -> rank > 2 && rank <= 10))
   ),
   SNOOKER(Collections.unmodifiableList(Arrays.asList(
      () -> new SnookerPlayer("Ronnie O'Sullivan"),
      () -> new SnookerPlayer("Mark Selby"),
      () -> new SnookerPlayer("John Higgins"),
      () -> new SnookerPlayer("Neil Robertson"))
   ),
   Collections.unmodifiableList(Arrays.asList(
      rank -> rank == 1, rank -> rank == 2,
      rank -> rank > 3 && rank < 7,
      rank -> rank >= 7 && rank <= 10))
   );

   private final List<Supplier<Player>> names;
   private final List<Predicate<Integer>> conditions;

   private PlayerTypes(List<Supplier<Player>> names,
         List<Predicate<Integer>> conditions) {
      this.names = names;
      this.conditions = conditions;
   }

   public static final Player supplyPlayer(String playerType,
         int rank) {

      if (rank < 1 || rank > 10) {
         throw new IllegalArgumentException("Invalid rank: " +
            rank);
      }

      List<Predicate<Integer>> selectors =
         PlayerTypes.valueOf(playerType).conditions;

      for (int i = 0; i < selectors.size(); i++) {
         if (selectors.get(i).test(rank)) {
            return PlayerTypes.valueOf(playerType)
               .names.get(i).get();
         }
      }

      throw new IllegalStateException("The enum is corrupted");
   }

}

(使用如下代码)获得Snooker player: Neil Robertson的输出:

Player snookerPlayer = PlayerTypes.supplyPlayer("SNOOKER", 10);

总结

在这篇文章中, 你看到了如下七种处理switch结构的方法:

  1. 通过Java Enum实现策略模式
  2. 实现命令模式
  3. 使用Java 8+ Supplier
  4. 定义一个自定义的函数式接口
  5. 通过抽象工厂
  6. 实现状态模式
  7. 通过Predicate接口实现