2015-12-02 33 views
8

如何讓非法行爲無法執行?如何確保非法行爲不可執行?

摘要:

自從開始我的旅程,學習F#,我學習型驅動設計和基於屬性的測試。因此,我愛上了讓非法國家無法代表的想法。

但我真正想做的是讓非法行爲無法執行。

我正在通過編寫一個BlackJack遊戲來學習F#。因此,我想確保當經銷商發放卡時,經銷商只能處理「初始手」或「打」。所有其他分發卡都是非法的。

在C#中,我將實現策略模式,並因此創建DealHandCommand和DealHitCommand。然後,我會硬編碼一個卡片數量的常數整數值來處理(每個策略)。

DealHandCommand = 2卡

DealHitCommand = 1卡

基於這些策略,然後,我會實現一個狀態機來表示二十一點遊戲的會話。因此,在我處理初始手牌(即DealHandCommand)後,我執行狀態轉換,其中未來交易只能執行「DealHitCommand」。

具體來說,在混合功能語言中實現狀態機是否有意義,以實現不可執行的非法行爲?

回答

9

這很容易在F#實現一個狀態機。它通常遵循三個步驟,與第三步是可選的:

  1. 用的情況下,定義區分聯合對每個國家
  2. 定義的轉換函數爲每個個案
  3. 可選:執行所有的代碼的其餘

步驟1

在這種情況下,它聽起來好像有兩種狀態:

  • 初始兩張牌
  • 一個命中一個額外的卡

這表明該Deal識別聯合:

type Deal = Hand of Card * Card | Hit of Card 

此外,定義Game是什麼:

type Game = Game of Deal list 

請注意使用單個案例的區別聯盟; there's a reason for that

步驟2

現在定義,從每一個狀態轉變到Game的功能。

事實證明,你不能從任何遊戲狀態過渡Hand情況下,由於Hand是什麼開始一個新遊戲。在另一方面(雙關語意),你需要提供是進入手:

let init c1 c2 = Game [Hand (c1, c2)] 

另一種情況是,當遊戲進行過程中,你應該只允許Hit,但不Hand,所以定義此過渡:

let hit (Game deals) card = Game (Hit card :: deals) 

正如你所看到的,hit的功能需要在現有Game通過。

步驟3

什麼防止一個客戶端從創建一個無效Game值,例如[Hand; Hit; Hand; Hit; Hit]

BlackJack.fsi:

您可以用signature file封裝上述狀態機

type Deal 
type Game 
val init : Card -> Card -> Game 
val hit : Game -> Card -> Game 
val card : Deal -> Card list 
val cards : Game -> Card list 

這裏,類型DealGame聲明,但他們的 '建設者' 不是。這意味着你不能直接創建這些類型的值。此,例如,不編譯:

let g = BlackJack.Game [] 

給出的錯誤是:

錯誤FS0039:該值,構造,命名空間或類型 '遊戲' 是沒有定義

創建Game價值的唯一方法是調用,爲您創建它的功能:

let g = 
    BlackJack.init 
     { Face = Ace; Suit = Spades } 
     { Face = King; Suit = Diamonds } 

這也使您能夠繼續遊戲:

let g' = BlackJack.hit g { Face = Two; Suit = Spades } 

你可能已經注意到,上面的簽名文件還定義了兩個函數來獲取卡出GameDeal值。這裏是實現:

let card = function 
    | Hand (c1, c2) -> [c1; c2] 
    | Hit c -> [c] 

let cards (Game deals) = List.collect card deals 

客戶端可以使用它們像這樣:

> let cs = g' |> BlackJack.cards;; 
> 

val cs : Card list = [{Suit = Spades; 
         Face = Two;}; 
         {Suit = Spades; 
         Face = Ace;}; 
         {Suit = Diamonds; 
         Face = King;}] 

注意,這種方法主要是結構;有幾個移動部件。

附錄

這些是上面所用的文件:

Cards.fs:

namespace Ploeh.StackOverflow.Q34042428.Cards 

type Suit = Diamonds | Hearts | Clubs | Spades 
type Face = 
    | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten 
    | Jack | Queen | King | Ace 

type Card = { Suit: Suit; Face: Face } 

酒杯。FSI:

module Ploeh.StackOverflow.Q34042428.Cards.BlackJack 

type Deal 
type Game 
val init : Card -> Card -> Game 
val hit : Game -> Card -> Game 
val card : Deal -> Card list 
val cards : Game -> Card list 

BlackJack.fs:

module Ploeh.StackOverflow.Q34042428.Cards.BlackJack 

open Ploeh.StackOverflow.Q34042428.Cards 

type Deal = Hand of Card * Card | Hit of Card 

type Game = Game of Deal list 

let init c1 c2 = Game [Hand (c1, c2)] 

let hit (Game deals) card = Game (Hit card :: deals) 

let card = function 
    | Hand (c1, c2) -> [c1; c2] 
    | Hit c -> [c] 

let cards (Game deals) = List.collect card deals 

Client.fs:

module Ploeh.StackOverflow.Q34042428.Cards.Client 

open Ploeh.StackOverflow.Q34042428.Cards 

let g = 
    BlackJack.init 
     { Face = Ace; Suit = Spades } 
     { Face = King; Suit = Diamonds } 
let g' = BlackJack.hit g { Face = Two; Suit = Spades } 

let cs = g' |> BlackJack.cards 
+0

感謝馬克。在fsi文件中,如果我們確實想明確地讓外部世界使用「遊戲」,我們將如何表達?它會在fsi文件中「鍵入Game()」嗎? –

+0

@ScottNimrod如果你想這樣做,最簡單的方法是刪除'.fsi'文件,因爲那麼普通模塊聲明就會生效。否則,我肯定[文檔](https://msdn.microsoft.com/en-us/library/dd233196.aspx)可以告訴你如何做到這一點。 –

1

一種可能性可以使用識別聯合:

type DealCommand = 
    | Hand of Card * Card 
    | Hit of Card 

(假設你有型Card

2

一個是一張牌嗎?

如果是的話那麼就用兩個類型

  • type HandDealt = Dealt of Card * Card
  • type Playing = Playing of Cards
  • (也許更多 - 取決於你想要什麼)。

然後,而不是命令你有簡單的功能:

  • dealHand :: Card * Card -> HandDealt
  • start :: HandDealt -> Playing
  • dealAnother :: Playing -> Card -> Playing

這種方式,你只能按照一定的行爲,它是靜態檢查。

病程的

你可能想這些類型擴展到多個球員,但我認爲你會得到什麼,我要


PS:也許你甚至想跳過HandDealt/start階段(如果你不「T需要的東西像博彩/拆分/等中間階段 - 但請記住,我沒有關於二十一點線索):

  • dealHand :: Card * Card -> Playing
  • dealAnother :: Playing -> Card -> Playing

它給你