如何重构嵌套的switch-case(Java)或when(Kotlin)?

问题描述 投票:1回答:1

需求:我需要编写一个Jo-Ken-Pô游戏的代码,这个游戏可以和多个玩家一起玩。我需要编写一个Jo-Ken-Pô游戏的代码,这个游戏可以和多个玩家一起玩,有5个角色(SPOCK、剪刀、纸、石头和蜥蜴)。有5个角色(SPOCK、剪刀、纸、石头和蜥蜴)。我可以用这两个链式的when开关来实现(下面的代码使用了when(),因为它是在Kotlin中使用的,但同样的想法也可以在Java中使用switch。

  when (u1.play) {
        PlayType.SPOCK -> when (u2.play) {
            //SPOCK WINS
            PlayType.TESOURA -> return u1
            PlayType.PEDRA -> return u1
            //SPOCK LOSES
            PlayType.PAPEL -> return u2
            PlayType.LAGARTO -> return u2
        }
        PlayType.TESOURA -> when (u2.play) {
            //TESOURA (scissors) WINS
            PlayType.PAPEL -> return u1
            PlayType.LAGARTO -> return u1
            //TESOURA (scissors) LOSES
            PlayType.SPOCK -> return u2
            PlayType.PEDRA -> return u2
        }
        PlayType.PAPEL -> when (u2.play) {
            //PAPEL (paper) WINS
            PlayType.SPOCK -> return u1
            PlayType.PEDRA -> return u1
            //PAPEL (paper) LOSES
            PlayType.TESOURA -> return u2
            PlayType.LAGARTO -> return u2
        }
        PlayType.PEDRA -> when (u2.play) {
            //PEDRA (stone) WINS
            PlayType.LAGARTO -> return u1
            PlayType.TESOURA -> return u1
            //PEDRA (stone) LOSES
            PlayType.SPOCK -> return u2
            PlayType.PAPEL -> return u2
        }
        PlayType.LAGARTO -> when (u2.play) {
            //LAGARTO (lizard) WINS
            PlayType.SPOCK -> return u1
            PlayType.PAPEL -> return u1
            //LAGARTO (lizard) LOSES
            PlayType.TESOURA -> return u2
            PlayType.PEDRA -> return u2
        }
    }

我读了好几个小时的书,试图找到如何使用lambda将这段代码变得更优雅,但我找不到任何线索。请您提供任何帮助,将是非常好的欢迎。

我将整个代码粘贴在这里。虽然你看到我至少使用lambda来调用方法,但我肯定缺少了lambda的一些强大功能,而且几乎是以Java 7< 古典的方式来编码。

所有的用户都来自一个H2数据库。这里是存储库

import com.mycomp.jokenpo.model.User
import org.springframework.data.repository.CrudRepository

interface UserRepository : CrudRepository<User, Long>

用户模式

import com.mycomp.jokenpo.enums.PlayType
import javax.persistence.*


@Entity
data class User(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        val id: Long,

        @Column(nullable = false)
        val name: String,

        @Enumerated
        @Column(nullable = false)
        val play: PlayType

)

Enum PlayType

enum class PlayType(val value: Int) {
    SPOCK(1), TESOURA(2), LAGARTO(3), PAPEL(4), PEDRA(5)
}

服务 * Here are the issues *

import com.mycomp.jokenpo.enums.PlayType
import com.mycomp.jokenpo.model.User
import com.mycomp.jokenpo.respository.UserRepository
import org.springframework.stereotype.Component

@Component
class GameService(private val userRepository: UserRepository) {

    fun returnWinnerBetweenTwoPlayers(u1: User, u2: User): User {

        when (u1.play) {
            PlayType.SPOCK -> when (u2.play) {
                //SPOCK WINS
                PlayType.TESOURA -> return u1
                PlayType.PEDRA -> return u1
                //SPOCK LOSES
                PlayType.PAPEL -> return u2
                PlayType.LAGARTO -> return u2
            }
            PlayType.TESOURA -> when (u2.play) {
                //TESOURA (scissors) WINS
                PlayType.PAPEL -> return u1
                PlayType.LAGARTO -> return u1
                //TESOURA (scissors) LOSES
                PlayType.SPOCK -> return u2
                PlayType.PEDRA -> return u2
            }
            PlayType.PAPEL -> when (u2.play) {
                //PAPEL (paper) WINS
                PlayType.SPOCK -> return u1
                PlayType.PEDRA -> return u1
                //PAPEL (paper) LOSES
                PlayType.TESOURA -> return u2
                PlayType.LAGARTO -> return u2
            }
            PlayType.PEDRA -> when (u2.play) {
                //PEDRA (stone) WINS
                PlayType.LAGARTO -> return u1
                PlayType.TESOURA -> return u1
                //PEDRA (stone) LOSES
                PlayType.SPOCK -> return u2
                PlayType.PAPEL -> return u2
            }
            PlayType.LAGARTO -> when (u2.play) {
                //LAGARTO (lizard) WINS
                PlayType.SPOCK -> return u1
                PlayType.PAPEL -> return u1
                //LAGARTO (lizard) LOSES
                PlayType.TESOURA -> return u2
                PlayType.PEDRA -> return u2
            }
        }
        return u1
    }

    fun playGameWithAll(): User? {
        val allUsers = userRepository.findAll().toList()

        val winner = allUsers.reduce { a, b ->
            returnWinnerBetweenTwoPlayers(a, b)
        }

        if (allUsers.filter { player -> player.play == winner.play }
                        .count() == 1)
            return winner
        else
            return null

    }
}

上面的代码按预期工作,但我有填充我是不好编码的两个原因。

1 - 我拆分了两个小lambda语句:减少比较对方的playtype和只是为了弄清楚是否有一个以上的赢家,我编码.count分开的

2 - 当然,可能有一个更优雅和更易读的方法来编码两个链式开关与lambda,但我可以得到甚至第一步尝试一下。

PS:代码是Kotlin的,但如果你指出任何Java的东西,我可以很容易地翻译成kotlin。任何诀窍或建议如何重现工厂将高度赞赏。

任何感兴趣的人得到游戏填写免费克隆从 https:/github.comjimisdrpcgames.git。

***在米哈伊尔的回答后编辑。

Main.java

    package poc;

    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    import java.util.Set;

    public class Main {

        public static void main(String[] args) {

            //Fake returned list from database
            List<User> usersList = List.of(
                    new User(1L, "Jimis", PlayType.LAGARTO),
                    new User(2L, "Drpc", PlayType.PAPEL));


            //User winnerUser = returnWinnerBetweenTwoPlayers(usersList.get(0), usersList.get(1));

            Optional<User> winnerUser  = usersList.stream().reduce( (a, b) ->
            returnWinnerBetweenTwoPlayers(a , b));

            System.out.print(winnerUser);

        }

        //Trying to refactoring from classical switch to some structure for using with lambda
        private final static Map<PlayType, Set<PlayType>> CONFIG = Map.of(
                PlayType.SPOCK,
                    Set.of(PlayType.TESOURA, PlayType.PEDRA), 
                PlayType.TESOURA, 
                    Set.of(PlayType.PAPEL, PlayType.LAGARTO)

        );

        private static User returnWinnerBetweenTwoPlayers(User u1, User u2) {
/// ****** Exception next line
            if (CONFIG.get(u1.getPlay()).contains(u2.getPlay())) {
                return u1;
            }
            return u2;
        }
    }

用户模式

package poc;

public class User {

    Long id;
    String name;
    PlayType play;

    public User(Long id, String name, PlayType play) {
        super();
        this.id = id;
        this.name = name;
        this.play = play;
    }

//... getters/setters removed
}

PlayType enum

package poc;

public enum PlayType {
    SPOCK,
    TESOURA, 
    PEDRA, 
    PAPEL,
    LAGARTO;
}

运行这部分时出现异常 CONFIG.get(u1.getPlay()).包含(u2.getPlay()

Exception in thread "main" java.lang.NullPointerException
    at moduleinfo/poc.Main.returnWinnerBetweenTwoPlayers(Main.java:39)
    at moduleinfo/poc.Main.lambda$0(Main.java:21)
    at java.base/java.util.stream.ReduceOps$2ReducingSink.accept(ReduceOps.java:123)
    at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:558)
    at moduleinfo/poc.Main.main(Main.java:20)

如果我尝试简化并调用没有stream().reduce()的情况下,我在相同的点得到了这个问题。

Exception in thread "main" java.lang.NullPointerException
    at moduleinfo/poc.Main.returnWinnerBetweenTwoPlayers(Main.java:49)
    at moduleinfo/poc.Main.main(Main.java:19)

*** 最终解决方案

public class Main {

    public static void main(String[] args) {

        //Fake returned list from database
        List<User> usersList = List.of(
                new User(1L, "Jogador 1", PlayType.PEDRA),
                new User(2L, "Jogador 2", PlayType.TESOURA),
                new User(3L, "Jogador 3", PlayType.TESOURA),
                new User(4L, "Jogador 4", PlayType.SPOCK)
                );


        Optional<User> winnerUser  = usersList.stream().reduce( (a, b) ->
            returnWinnerBetweenTwoPlayers(a , b));

        System.out.print(winnerUser.get().getName());

    }

    private final static Map<PlayType, Set<PlayType>> CONFIG = Map.of(
            PlayType.SPOCK,
                Set.of(PlayType.TESOURA, PlayType.PEDRA), 
            PlayType.TESOURA, 
                Set.of(PlayType.PAPEL, PlayType.LAGARTO),
            PlayType.PAPEL, 
                Set.of(PlayType.SPOCK, PlayType.PEDRA),
            PlayType.PEDRA, 
                Set.of(PlayType.LAGARTO, PlayType.TESOURA),
            PlayType.LAGARTO, 
                Set.of(PlayType.SPOCK, PlayType.PAPEL)
    );

    private static User returnWinnerBetweenTwoPlayers(User u1, User u2) {
        if (CONFIG.get(u1.getPlay()).contains(u2.getPlay())) {
            return u1;
        }
        return u2;
    }
}
java kotlin lambda functional-programming stream
1个回答
1
投票

这个 switch inside switch 构造看起来真的很难读懂,你是对的。

你可以这样想:每个PlayType从其他一些类型中胜出。而这些信息看起来就像一个配置,所以可以用一种声明式的方式来描述,就像这样。

  • SPOCK赢了TESOURA & PEDRA?
  • TESOURA对PAPEL & LAGARTO获胜

所以你可以直接定义一个 Map<PlayType, Set<PlayType>> 并验证是否 u2.play is contained by map.get(u1.play)

UPD. 代码示例(java,用记事本写的,所以可能包含一些语法错误)

class GameService {
  private final static Map<PlayType, Set<PlayType>> CONFIG = Map.of(
    PlayType.SPOCK, Set.of(PlayType.TESOURA, PlayType.PEDRA),
    PlayType.TESOURA, Set.of(PlayType.PAPEL, PlayType.LAGARTO)
    //etc
  );

  function User returnWinnerBetweenTwoPlayers(User u1, User u2){
    if (CONFIG.get(u1.getType()).contains(u2.getType()){
      return u1;
    }
    return u2;
  } 
}
© www.soinside.com 2019 - 2024. All rights reserved.