Как выполнить рефакторинг вложенного коммутатора (Java) или когда (Kotlin)? - PullRequest
1 голос
/ 27 апреля 2020

Требование: мне нужно написать игру Jo-Ken-Pô, в которую можно играть несколькими игроками. Есть 5 ролей (SPOCK, ножницы, бумага, камень и ящерица). Я могу выполнить sh это с этими двумя цепочками когда / переключатели (код ниже использует когда (), потому что он находится в Kotlin, но ту же идею можно применить, используя переключатель в Java)

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

Я читал часы и часы, пытаясь найти, как сделать этот код более элегантным, используя лямбду, но я не могу найти никакой подсказки. Пожалуйста, любая помощь будет очень кстати.

Я вставлю весь код сюда. Хотя вы видите, что я использую лямбду, по крайней мере, для вызова метода, я определенно упускаю некоторые мощные функции лямбды и почти кодирую, как в Java 7 <классическим способом. </p>

Все пользователи происходят из базы данных 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)
}

Сервис * ЗДЕСЬ ПРОБЛЕМЫ *

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 - я разделил два маленьких лямбда-выражения: уменьшить, чтобы сравнить друг с другом тип игры и просто чтобы выяснить, есть ли более одного победителя, я закодировал .count разделенный

2 - конечно, мог бы быть более элегантный и удобный для чтения способ кодирования двухконтактных коммутаторов с лямбдой, но я могу даже сделать первый шаг, чтобы попробовать

PS: код Kotlin, но если вы укажете что-либо в Java, я легко смогу перевести его на kotlin. Любой трюк или предложение о том, как высоко ценится заводская мастерская

Любой, кто заинтересован в том, чтобы получить клон игры, может бесплатно клонировать от https://github.com/jimisdrpc/games.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;
    }
}

1 Ответ

1 голос
/ 27 апреля 2020

Эта switch inside switch конструкция выглядит действительно трудно читаемой, вы правы.

Подумайте об этом так: каждый PlayType выигрывает у некоторых других. И эта информация выглядит как конфигурация, поэтому ее можно описать декларативно, например:

  • SPOCK выигрывает у TESOURA & PEDRA
  • TESOURA выигрывает у PAPEL & LAGARTO
  • et c

Таким образом, вы можете просто определить 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;
  } 
}
...