Itulah pertanyaannya! Apakah lebih baik menyimpan semuanya dalam satu proses, atau membuat proses terpisah untuk setiap bagian negara yang perlu kita kelola? Pada artikel ini, saya akan berbicara sedikit tentang menggunakan atau tidak menggunakan proses. Saya juga akan menunjukkan kepada Anda bagaimana memisahkan logika stateful yang kompleks dari masalah-masalah seperti perilaku temporal dan komunikasi antarproses.
Tetapi sebelum memulai, karena artikelnya akan panjang, saya ingin menguraikan poin-poin utama:
Gunakan fungsi dan modul untuk memisahkan entitas pemikiran.
Gunakan proses untuk memisahkan entitas run-time.
Jangan gunakan proses (bahkan agen) untuk memisahkan entitas pemikiran.
Konstruksi "entitas berpikir" di sini mengacu pada ide-ide yang ada di pikiran kita, seperti "urutan", "posisi dalam urutan", "produk", dll. Jika konsep ini terlalu kompleks, maka ada baiknya menerapkannya dalam pisahkan modul dan fungsi untuk memisahkan entitas yang berbeda dan pertahankan setiap bagian dari kode kami fokus dan koheren.
Menggunakan proses (misalnya agen) untuk ini adalah kesalahan yang sering dilakukan orang. Pendekatan ini secara signifikan merindukan fungsionalitas Elixir dan sebaliknya mencoba meniru objek melalui proses. Implementasinya cenderung lebih buruk daripada pendekatan fungsional sederhana (atau bahkan bahasa pemrograman berorientasi objek yang setara). Oleh karena itu, ada baiknya beralih ke proses hanya jika ada manfaat nyata darinya. Organisasi kode bukanlah salah satu dari keuntungan tersebut, jadi ini bukan alasan yang baik untuk menggunakan proses.
- , . , , , . - , . . , , - .
- , , , . . , : () (). . 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), , - .
Akhirnya, penting untuk mengingat tujuan akhirnya. Meskipun saya belum pergi ke sana (belum), saya selalu merencanakan bahwa kode ini akan dihosting di beberapa jenis server web. Jadi beberapa keputusan dibuat untuk mendukung skenario ini. Secara khusus, implementasi RoundServer
yang menerima modul panggilan balik untuk setiap pemain memungkinkan saya untuk terhubung ke berbagai jenis klien menggunakan teknologi yang berbeda. Hal ini membuat layanan blackjack tidak bergantung pada pustaka dan kerangka kerja tertentu (dengan pengecualian pustaka standar dan OTP, tentu saja) dan membuatnya sangat fleksibel.