Laichen oder nicht laichen?

Das ist hier die Frage! Ist es besser, alles in einem Prozess zu behalten oder einen separaten Prozess für jeden Zustand zu erstellen, den wir verwalten müssen? In diesem Artikel werde ich ein wenig über die Verwendung oder Nichtverwendung von Prozessen sprechen. Ich werde Ihnen auch zeigen, wie Sie komplexe Zustandslogik von Themen wie zeitlichem Verhalten und Interprozesskommunikation entkoppeln können.





Aber bevor ich anfange, möchte ich, da der Artikel lang sein wird, die wichtigsten Punkte skizzieren:





  • Verwenden Sie Funktionen und Module, um Gedankeneinheiten zu trennen.





  • Verwenden Sie Prozesse, um Laufzeitentitäten zu trennen.





  • Verwenden Sie keine Prozesse (auch keine Agenten), um Gedankeneinheiten zu trennen.





Das Konstrukt "denkende Einheiten" bezieht sich hier auf die Ideen, die wir im Kopf haben, wie "Ordnung", "Position in der Ordnung", "Produkt" usw. Wenn diese Konzepte zu komplex sind, lohnt es sich, sie umzusetzen Trennen Sie Module und Funktionen, um verschiedene Entitäten zu trennen und jeden Teil unseres Codes fokussiert und kohärent zu halten.





Die Verwendung von Prozessen (z. B. Agenten) ist ein Fehler, den Menschen häufig machen. Dieser Ansatz verfehlt die Funktionalität von Elixir erheblich und versucht stattdessen, Objekte durch Prozesse nachzuahmen. Die Implementierung ist wahrscheinlich schlechter als ein einfacher funktionaler Ansatz (oder sogar ein objektorientiertes Programmiersprachenäquivalent). Daher lohnt es sich, sich nur dann Prozessen zuzuwenden, wenn sich daraus konkrete Vorteile ergeben. Die Code-Organisation ist keiner dieser Vorteile, daher ist es kein guter Grund, Prozesse zu verwenden.





- , . , , , . - , . . , , - .





, ? . , ( ), .





- , , , . . , : () (). . 21, . ( ).





- , (2-10) , , 10. 1 11, , ( ) .





, . , . , , .





, , , , (), , , .





, , : , . - . , «» , . , , , . , . : , , , . .





, , . , . . , . : , , ( ). , , , , , .





, . . , , . , . ( ) , , ( ) ( ). , , :-) (. : , ?)





, , . , , .





, , , - . , , , , . , , , :-)





, ? , . , , , , .





, , , .





, - . 52 . , .





, , . , , . , .





. , . :





@cards (
  for suit <- [:spades, :hearts, :diamonds, :clubs],
      rank <- [2, 3, 4, 5, 6, 7, 8, 9, 10, :jack, :queen, :king, :ace],
    do: %{suit: suit, rank: rank}
)

      
      



shuffle/0



:





def shuffled(), do:
  Enum.shuffle(@cards)

      
      



, take/1



, :





def take([card | rest]), do:
  {:ok, card, rest}
def take([]), do:
  {:error, :empty}

      
      



take/1



{:ok, card_taken, rest_of_the_deck}



, {:error, :empty}



. ( ) , .





:





deck = Blackjack.Deck.shuffled()

case Blackjack.Deck.take(deck) do
  {:ok, card, transformed_deck} ->
    # do something with the card and the transform deck
  {:error, :empty} ->
    # deck is empty -> do something else
end

      
      



, « », :





  • ,





  • ,





  • ,









, - . - Deck



, Deck



. ( ), , ( , , , - . .)





, . . shuffled_deck/0



take_card/1



. , , , . , - . (. : , )





, . , .





.





. . (:ok



:busted



). Blackjack.Hand.





. new/0



, deal/2



, . :





# create a deck
deck = Blackjack.Deck.shuffled()

# create a hand
hand = Blackjack.Hand.new()

# draw one card from the deck
{:ok, card, deck} = Blackjack.Deck.take(deck)

# give the card to the hand
result = Blackjack.Hand.deal(hand, card)

      
      



deal/2



{hand_status, transformed_hand}



, hand_status



:ok



:busted



.





, Blackjack.Round, . :













  • ,





  • ( / )









  • ,





, . , . . , , , , , . , , .





, , /, GenServer



:gen_statem



. (, ) .





, , . , , , , . , (netsplits), , . , , , event sourcing - .





, , . .





, . .





. , start/1



:





{instructions, round} = Blackjack.Round.start([:player_1, :player_2])
      
      



, , - . , :

















  • . - . :





    [
    {:notify_player, :player_1, {:deal_card, %{rank: 4, suit: :hearts}}},
    {:notify_player, :player_1, {:deal_card, %{rank: 8, suit: :diamonds}}},
    {:notify_player, :player_1, :move}
    ]
    
          
          



    - , , . , , . , :





  • 1,





  • 1,





  • 1,





    . , , GenServer



    , . , , . () , Round



    .





, round



, . , . , round



. , , instruction



.





, 1:





{instructions, round} = Blackjack.Round.move(round, :player_1, :hit)
      
      



, , . , , .





, :





[
  {:notify_player, :player_1, {:deal_card, %{rank: 10, suit: :spades}}},
  {:notify_player, :player_1, :busted},
  {:notify_player, :player_2, {:deal_card, %{rank: :ace, suit: :spades}}},
  {:notify_player, :player_2, {:deal_card, %{rank: :jack, suit: :spades}}},
  {:notify_player, :player_2, :move}
]
      
      



, 1 . 4 8 , , . 2 , , .





2:





{instructions, round} = Blackjack.Round.move(round, :player_2, :stand)

# instructions:
[
  {:notify_player, :player_1, {:winners, [:player_2]}}
  {:notify_player, :player_2, {:winners, [:player_2]}}
]

      
      



2 , . .





, Round



Deck



Hand



. Round



:





defp deal(round) do
  {:ok, card, deck} =
    with {:error, :empty} <- Blackjack.Deck.take(round.deck), do:
      Blackjack.Deck.take(Blackjack.Deck.shuffled())

  {hand_status, hand} = Hand.deal(round.current_hand, card)

  round =
    %Round{round | deck: deck, current_hand: hand}
    |> notify_player(round.current_player_id, {:deal_card, card})

  {hand_status, round}
end

      
      



, , . , , (:ok



:busted



) . :-)





notify_player



- , . (, GenServer Phoenix). - , . , , .





, , Round



. notify_player



. , , take_instructions



Round



, , .





, . , . - . , .





. , . , , . , , .





Blackjack.RoundServer, GenServer



. Agent



, , GenServer



. , , :-)





, start_playing/2



. start_link



, start_link



. , start_playing



- , .





: . - , . .





, :





@type player :: %{id: Round.player_id, callback_mod: module, callback_arg: any}
      
      



, . . , , callback_mod.some_function (some_arguments)



, some_arguments



, , callback_arg



, .





callback_mod



, :





  • , HTTP





  • , TCP





  • iex





  • ()





    . , .





, , :





@callback deal_card(RoundServer.callback_arg, Round.player_id,
  Blackjack.Deck.card) :: any
@callback move(RoundServer.callback_arg, Round.player_id) :: any
@callback busted(RoundServer.callback_arg, Round.player_id) :: any
@callback winners(RoundServer.callback_arg, Round.player_id, [Round.player_id])
  :: any
@callback unauthorized_move(RoundServer.callback_arg, Round.player_id) :: any
      
      



, . , . . , , , , .





- , . . asserting/refuting , RoundServer.move/3



, .





Round



, , .





. , . - , . , , . , , . , .





Blackjack.PlayerNotifier, GenServer



, - . start_playing/2



, .





, . , //(M/F/A) .





, , (, , ). , . :





[
  {:notify_player, :player_1, {:deal_card, %{rank: 10, suit: :spades}}},
  {:notify_player, :player_1, :busted},
  {:notify_player, :player_2, {:deal_card, %{rank: :ace, suit: :spades}}},
  {:notify_player, :player_2, {:deal_card, %{rank: :jack, suit: :spades}}},
  {:notify_player, :player_2, :move}
]
      
      



, player_2



, player_1



, . , . , , , , .





, : Round



, . .





OTP :blackjack



( Blackjack). , : Registry



( ) :simple_one_for_one



, .





, . . Phoenix, Cowboy, Ranch ( TCP), elli , . , .





Demo, , , GenServer, , :





$ iex -S mix
iex(1)> Demo.run

player_1: 4 of spades
player_1: 3 of hearts
player_1: thinking ...
player_1: hit
player_1: 8 of spades
player_1: thinking ...
player_1: stand

player_2: 10 of diamonds
player_2: 3 of spades
player_2: thinking ...
player_2: hit
player_2: 3 of diamonds
player_2: thinking ...
player_2: hit
player_2: king of spades
player_2: busted

...

      
      



, , :





, ? , ! , Deck



and Hand



, .





, . , . , . . , .





/ , . , , ( ), - . , . - , , . (netsplits), , - .





Schließlich lohnt es sich, sich an das Endziel zu erinnern. Obwohl ich (noch) nicht dorthin gegangen bin, habe ich immer geplant, dass dieser Code auf einer Art Webserver gehostet wird. Daher werden einige Entscheidungen getroffen, um dieses Szenario zu unterstützen. Insbesondere eine Implementierung RoundServer



, die ein Rückrufmodul für jeden Player akzeptiert, ermöglicht es mir, mithilfe verschiedener Technologien eine Verbindung zu verschiedenen Arten von Clients herzustellen. Dies macht den Blackjack-Dienst unabhängig von bestimmten Bibliotheken und Frameworks (mit Ausnahme der Standardbibliotheken und natürlich des OTP) und macht ihn vollständig flexibel.








All Articles