我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
- 原文地址 : Seven Ways to Refactor Java switch Statements
- 原文作者 : Leonard Anghel
众所周知,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
结构的方法:
- 通过Java Enum实现策略模式
- 实现命令模式
- 使用Java 8+ Supplier
- 定义一个自定义的函数式接口
- 通过抽象工厂
- 实现状态模式
- 通过Predicate接口实现