다소 오랜 질문이므로 제발 나와 함께하십시오.이 데이터 구문 분석 문제를 어떻게 푸시겠습니까?
이 동시에 개발되는 하드웨어 에뮬레이터를 구현하고 있습니다. 아이디어는 자신의 클라이언트 소프트웨어를 테스트하고 하드웨어 개발자에게 펌웨어를 구현하기위한 참조 포인트를 제공하는 소프트웨어 솔루션을 제 3 자에게 제공하는 것입니다.
하드웨어 용 프로토콜을 작성한 사람은 INCA_XDR이라는 SUN XDR 버전을 사용했습니다. 그것은 직렬화하고 메시지를 역 직렬화하는 도구입니다. 그것은 C로 작성되었으므로 네이티브 코드를 피하기 위해 프로토콜 데이터를 수동으로 구문 분석합니다.
프로토콜은 특성상 다소 복잡하고 데이터 패킷 는 다양한 구조를 가질 수 있지만, 항상 같은 글로벌 구조 가지고
[HEAD] [INTRO] DATA [TAIL]
을
[HEAD] =
byte sync 0x03
byte length X [MSB] X = length of [HEADER] + [INTRO] + [DATA]
byte length X [LSB] X = length of [HEADER] + [INTRO] + [DATA]
byte check X [MSB] X = crc of [INTRO] [DATA]
byte check X [LSB] X = crc of [INTRO] [DATA]
byte headercheck X X = XOR over [SYNC] [LENGTH] [CHECK]
[INTRO]
byte version 0x03
byte address X X = 0 for point-to-point, 1-254 for specific controller, 255 = broadcast
byte sequence X X = sequence number
byte group X [MSB] X = The category of the message
byte group X [LSB] X = The category of the message
byte type X [MSB] X = The id of the message
byte type X [LSB] X = The id of the message
[DATA] =
The actuall data for the specified message,
this format really differs a lot.
It always starts with a DRCode which is one byte.
It more or less specifies the general structure of
the data, but even within the same structure the data
can mean many different things and have different lenghts.
(I think this is an artifact of the INCA_XDR tool)
[TAIL] =
byte 0x0D
당신이 오버 헤드 많은 양의 데이터가 볼 수 있지만 프로토콜이 모두 RS232 (포인트 투 멀티 포인트) 및 TCP/IP (P2P)와 함께 작동해야하기 때문에이 때문이다.
name size value
drcode 1 1
name 8 contains a name that can be used as a file name (only alphanumeric characters allowed)
timestamp 14 yyyymmddhhmmss contains timestamp of bitmap library
size 4 size of bitmap library to be loaded
options 1 currently no options
아니면 완전히 다른 구조를 가지고 있습니다
name size value
drcode 1 2
lastblock 1 0 - 1 1 indicates last block. Firmware can be stored
blocknumber 2 Indicates block of firmware
blocksize 2 N size of block to load
blockdata N data of block of firmware
때때로 그냥 DRCode 및 추가 데이터입니다.
그룹 및 유형 필드에 따라 에뮬레이터 은 특정 작업을 수행해야합니다. 그래서 먼저 우리는 그 두 필드를 두 개 살펴본 결과를 기반으로 데이터 의 예상 내용을 알고 적절히 구문 분석해야합니다.
그런 다음 응답 데이터를 생성해야합니다.이 응답 데이터에는 많은 데이터 구조가 있습니다. 일부 메시지는 단순히 ACK 또는 NACK 메시지를 생성하는 반면 다른 메시지는 데이터로 실제 응답을 생성합니다.
우리는 작은 조각으로 물건을 부수기로 결정했습니다.
우선 IDataProcessor가 있습니다.
이 인터페이스를 구현하는 클래스는 원시 데이터의 유효성을 검사하고 Message 클래스의 인스턴스를 생성하기 위해 을 담당합니다. 그들은 통신에 책임이 없으며 간단히 바이트를 전달합니다 []
원시 데이터 유효성 검사는 헤더에 체크섬, crc 및 길이 오류가 있는지 확인하는 것을 의미합니다.
결과 메시지가 IMessageProcessor를 구현하는 클래스로 전달됩니다. IDataProcessor에 응답 메시지 또는 기타 메시지의 의미가 이 아니므로 원시 데이터가 유효하지 않다고 판단되는 경우에도 원시 데이터의 유효성 검사 만 수행됩니다.
bool nakError = false;
bool tailError = false;
bool crcError = false;
bool headerError = false;
bool lengthError = false;
그들은 프로토콜 관련 만 IMessageProcessor 인 IMessageProcessor
존재하지 않습니다 :
오류에 대한 IMessageProcessor 통보하려면 몇 가지 추가 속성은 메시지 클래스에 추가되었습니다 실제 작업이 완료된 곳입니다. 모든 다른 메시지 그룹 및 유형의 나는 패턴 일치하기 때문에 IMessageProcessor 인터페이스를 구현 사용 F 번호로 결정 때문에 경우/다른 사람과 계급 문 중첩을 많이 방지 할 수있는 좋은 방법처럼 보였다. 는
IMessageProcessor 데이터를 분석하고 그것이 IHardwareController에 를 호출해야하는지 방법 결정 (I는 F # 또는 LINQ 이외도 함수형 언어와 SQL에 대한 사전 경험이 없음). IHardwareController, 을 가지고 있어도 과장된 것처럼 보일 수도 있지만, 다른 구현물 인 으로 교체 할 수 있기를 원하며 F #을 사용하지 않아도됩니다. 현재 구현은 WPF 윈도우 인 이지만 Cocoa # 창 또는 단순히 예를 들어 콘솔 일 수 있습니다.
개발자가 사용자 인터페이스를 통해 하드웨어 매개 변수와 오류를 조작 할 수 있어야하므로 IHardwareController도 상태 관리를 담당합니다.
그래서 IMessageProcessor가 IHardwareController에서 올바른 메서드를 호출하면 MEssage 응답을 생성해야합니다. 다시 ...이 응답 메시지의 데이터 은 여러 가지 구조를 가질 수 있습니다.
결국 IDataFactory는 메시지를 원시 프로토콜 데이터 으로 변환하여 통신을 담당하는 클래스로 보낼 준비가 된 것입니다.
이이 코드를 작성하는 방법에 대한 "하드"아무것도 없다 (데이터의 추가 캡슐 예를 들어해야 할 수도 있습니다)하지만, 모든 다른 명령과 데이터 구조는 많은 코드를 많이 필요로하고 몇 것들이 우리가 있습니다 재사용 할 수 있습니다. 이것은 내가 F 번호를 사용하는 것은 이번이 처음이다
(적어도 지금까지의 내가 지금 볼 수있는, 희망 누군가가 나에게 잘못을 증명할 수), 내가 가서 실제로 배우는 중이에요. 아래의 코드는 끝내야 만합니다. 아마 거대한 혼란처럼 보입니다. 그것은 오직 프로토콜 에있는 모든 메시지의 handfull을 구현하고 나는 그들 중 많은 것들을 많이 말할 수 있습니다. 그래서이 파일은 커질 것입니다!
아는 것이 중요합니다 : 바이트 순서가 와이어를 통해 반전 (역사적인 이유로)
module Arendee.Hardware.MessageProcessors
open System;
open System.Collections
open Arendee.Hardware.Extenders
open Arendee.Hardware.Interfaces
open System.ComponentModel.Composition
open System.Threading
open System.Text
let VPL_NOERROR = (uint16)0
let VPL_CHECKSUM = (uint16)1
let VPL_FRAMELENGTH = (uint16)2
let VPL_OUTOFSEQUENCE = (uint16)3
let VPL_GROUPNOTSUPPORTED = (uint16)4
let VPL_REQUESTNOTSUPPORTED = (uint16)5
let VPL_EXISTS = (uint16)6
let VPL_INVALID = (uint16)7
let VPL_TYPERROR = (uint16)8
let VPL_NOTLOADING = (uint16)9
let VPL_NOTFOUND = (uint16)10
let VPL_OUTOFMEM = (uint16)11
let VPL_INUSE = (uint16)12
let VPL_SIZE = (uint16)13
let VPL_BUSY = (uint16)14
let SYNC_BYTE = (byte)0xE3
let TAIL_BYTE = (byte)0x0D
let MESSAGE_GROUP_VERSION = 3uy
let MESSAGE_GROUP = 701us
[<Export(typeof<IMessageProcessor>)>]
type public StandardMessageProcessor() = class
let mutable controller : IHardwareController = null
interface IMessageProcessor with
member this.ProcessMessage m : Message =
printfn "%A" controller.Status
controller.Status <- ControllerStatusExtender.DisableBit(controller.Status,ControllerStatus.Nak)
match m with
| m when m.LengthError -> this.nakResponse(m,VPL_FRAMELENGTH)
| m when m.CrcError -> this.nakResponse(m,VPL_CHECKSUM)
| m when m.HeaderError -> this.nakResponse(m,VPL_CHECKSUM)
| m -> this.processValidMessage m
| _ -> null
member public x.HardwareController
with get() = controller
and set y = controller <- y
end
member private this.processValidMessage (m : Message) =
match m.Intro.MessageGroup with
| 701us -> this.processDefaultGroupMessage(m);
| _ -> this.nakResponse(m, VPL_GROUPNOTSUPPORTED);
member private this.processDefaultGroupMessage(m : Message) =
match m.Intro.MessageType with
| (1us) -> this.firmwareVersionListResponse(m) //ListFirmwareVersions 0
| (2us) -> this.StartLoadingFirmwareVersion(m) //StartLoadingFirmwareVersion 1
| (3us) -> this.LoadFirmwareVersionBlock(m) //LoadFirmwareVersionBlock 2
| (4us) -> this.nakResponse(m, VPL_FRAMELENGTH) //RemoveFirmwareVersion 3
| (5us) -> this.nakResponse(m, VPL_FRAMELENGTH) //ActivateFirmwareVersion 3
| (12us) -> this.nakResponse(m,VPL_FRAMELENGTH) //StartLoadingBitmapLibrary 2
| (13us) -> this.nakResponse(m,VPL_FRAMELENGTH) //LoadBitmapLibraryBlock 2
| (21us) -> this.nakResponse(m, VPL_FRAMELENGTH) //ListFonts 0
| (22us) -> this.nakResponse(m, VPL_FRAMELENGTH) //LoadFont 4
| (23us) -> this.nakResponse(m, VPL_FRAMELENGTH) //RemoveFont 3
| (24us) -> this.nakResponse(m, VPL_FRAMELENGTH) //SetDefaultFont 3
| (31us) -> this.nakResponse(m, VPL_FRAMELENGTH) //ListParameterSets 0
| (32us) -> this.nakResponse(m, VPL_FRAMELENGTH) //LoadParameterSets 4
| (33us) -> this.nakResponse(m, VPL_FRAMELENGTH) //RemoveParameterSet 3
| (34us) -> this.nakResponse(m, VPL_FRAMELENGTH) //ActivateParameterSet 3
| (35us) -> this.nakResponse(m, VPL_FRAMELENGTH) //GetParameterSet 3
| (41us) -> this.nakResponse(m, VPL_FRAMELENGTH) //StartSelfTest 0
| (42us) -> this.returnStatus(m) //GetStatus 0
| (43us) -> this.nakResponse(m, VPL_FRAMELENGTH) //GetStatusDetail 0
| (44us) -> this.ResetStatus(m) //ResetStatus 5
| (45us) -> this.nakResponse(m, VPL_FRAMELENGTH) //SetDateTime 6
| (46us) -> this.nakResponse(m, VPL_FRAMELENGTH) //GetDateTime 0
| _ -> this.nakResponse(m, VPL_REQUESTNOTSUPPORTED)
(* The various responses follow *)
//Generate a NAK response
member private this.nakResponse (message : Message , error) =
controller.Status <- controller.Status ||| ControllerStatus.Nak
let intro = new MessageIntro()
intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION
intro.Address <- message.Intro.Address
intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber)
intro.MessageGroup <- MESSAGE_GROUP
intro.MessageType <- 130us
let errorBytes = UShortExtender.ToIntelOrderedByteArray(error)
let data = Array.zero_create(5)
let x = this.getStatusBytes
let y = this.getStatusBytes
data.[0] <- 7uy
data.[1..2] <- this.getStatusBytes
data.[3..4] <- errorBytes
let header = this.buildHeader intro data
let message = new Message()
message.Header <- header
message.Intro <- intro
message.Tail <- TAIL_BYTE
message.Data <- data
message
//Generate an ACK response
member private this.ackResponse (message : Message) =
let intro = new MessageIntro()
intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION
intro.Address <- message.Intro.Address
intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber)
intro.MessageGroup <- MESSAGE_GROUP
intro.MessageType <- 129us
let data = Array.zero_create(3);
data.[0] <- 0x05uy
data.[1..2] <- this.getStatusBytes
let header = this.buildHeader intro data
message.Header <- header
message.Intro <- intro
message.Tail <- TAIL_BYTE
message.Data <- data
message
//Generate a ReturnFirmwareVersionList
member private this.firmwareVersionListResponse (message : Message) =
//Validation
if message.Data.[0] <> 0x00uy then
this.nakResponse(message,VPL_INVALID)
else
let intro = new MessageIntro()
intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION
intro.Address <- message.Intro.Address
intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber)
intro.MessageGroup <- MESSAGE_GROUP
intro.MessageType <- 132us
let firmwareVersions = controller.ReturnFirmwareVersionList();
let firmwareVersionBytes = BitConverter.GetBytes((uint16)firmwareVersions.Count) |> Array.rev
//Create the data
let data = Array.zero_create(3 + (int)firmwareVersions.Count * 27)
data.[0] <- 0x09uy //drcode
data.[1..2] <- firmwareVersionBytes //Number of firmware versions
let mutable index = 0
let loops = firmwareVersions.Count - 1
for i = 0 to loops do
let nameBytes = ASCIIEncoding.ASCII.GetBytes(firmwareVersions.[i].Name) |> Array.rev
let timestampBytes = this.getTimeStampBytes firmwareVersions.[i].Timestamp |> Array.rev
let sizeBytes = BitConverter.GetBytes(firmwareVersions.[i].Size) |> Array.rev
data.[index + 3 .. index + 10] <- nameBytes
data.[index + 11 .. index + 24] <- timestampBytes
data.[index + 25 .. index + 28] <- sizeBytes
data.[index + 29] <- firmwareVersions.[i].Status
index <- index + 27
let header = this.buildHeader intro data
message.Header <- header
message.Intro <- intro
message.Data <- data
message.Tail <- TAIL_BYTE
message
//Generate ReturnStatus
member private this.returnStatus (message : Message) =
//Validation
if message.Data.[0] <> 0x00uy then
this.nakResponse(message,VPL_INVALID)
else
let intro = new MessageIntro()
intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION
intro.Address <- message.Intro.Address
intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber)
intro.MessageGroup <- MESSAGE_GROUP
intro.MessageType <- 131us
let statusDetails = controller.ReturnStatus();
let sizeBytes = BitConverter.GetBytes((uint16)statusDetails.Length) |> Array.rev
let detailBytes = ASCIIEncoding.ASCII.GetBytes(statusDetails) |> Array.rev
let data = Array.zero_create(statusDetails.Length + 5)
data.[0] <- 0x08uy
data.[1..2] <- this.getStatusBytes
data.[3..4] <- sizeBytes //Details size
data.[5..5 + statusDetails.Length - 1] <- detailBytes
let header = this.buildHeader intro data
message.Header <- header
message.Intro <- intro
message.Data <- data
message.Tail <- TAIL_BYTE
message
//Reset some status bytes
member private this.ResetStatus (message : Message) =
if message.Data.[0] <> 0x05uy then
this.nakResponse(message, VPL_INVALID)
else
let flagBytes = message.Data.[1..2] |> Array.rev
let flags = Enum.ToObject(typeof<ControllerStatus>,BitConverter.ToInt16(flagBytes,0)) :?> ControllerStatus
let retVal = controller.ResetStatus flags
if retVal <> 0x00us then
this.nakResponse(message,retVal)
else
this.ackResponse(message)
//StartLoadingFirmwareVersion (Ack/Nak)
member private this.StartLoadingFirmwareVersion (message : Message) =
if (message.Data.[0] <> 0x01uy) then
this.nakResponse(message, VPL_INVALID)
else
//Analyze the data
let name = message.Data.[1..8] |> Array.rev |> ASCIIEncoding.ASCII.GetString
let text = message.Data.[9..22] |> Array.rev |> Seq.map(fun x -> ASCIIEncoding.ASCII.GetBytes(x.ToString()).[0]) |> Seq.to_array |> ASCIIEncoding.ASCII.GetString
let timestamp = DateTime.ParseExact(text,"yyyyMMddHHmmss",Thread.CurrentThread.CurrentCulture)
let size = BitConverter.ToUInt32(message.Data.[23..26] |> Array.rev,0)
let overwrite =
match message.Data.[27] with
| 0x00uy -> false
| _ -> true
//Create a FirmwareVersion instance
let firmware = new FirmwareVersion();
firmware.Name <- name
firmware.Timestamp <- timestamp
firmware.Size <- size
let retVal = controller.StartLoadingFirmwareVersion(firmware,overwrite)
if retVal <> 0x00us then
this.nakResponse(message, retVal) //The controller denied the request
else
this.ackResponse(message);
//LoadFirmwareVersionBlock (ACK/NAK)
member private this.LoadFirmwareVersionBlock (message : Message) =
if message.Data.[0] <> 0x02uy then
this.nakResponse(message, VPL_INVALID)
else
//Analyze the data
let lastBlock =
match message.Data.[1] with
| 0x00uy -> false
| _true -> true
let blockNumber = BitConverter.ToUInt16(message.Data.[2..3] |> Array.rev,0)
let blockSize = BitConverter.ToUInt16(message.Data.[4..5] |> Array.rev,0)
let blockData = message.Data.[6..6 + (int)blockSize - 1] |> Array.rev
let retVal = controller.LoadFirmwareVersionBlock(lastBlock, blockNumber, blockSize, blockData)
if retVal <> 0x00us then
this.nakResponse(message, retVal)
else
this.ackResponse(message)
(* Helper methods *)
//We need to convert the DateTime instance to a byte[] understood by the device "yyyymmddhhmmss"
member private this.getTimeStampBytes (date : DateTime) =
let stringNumberToByte s = Byte.Parse(s.ToString()) //Casting to (byte) would give different results
let yearString = date.Year.ToString("0000")
let monthString = date.Month.ToString("00")
let dayString = date.Day.ToString("00")
let hourString = date.Hour.ToString("00")
let minuteString = date.Minute.ToString("00")
let secondsString = date.Second.ToString("00")
let y1 = stringNumberToByte yearString.[0]
let y2 = stringNumberToByte yearString.[1]
let y3 = stringNumberToByte yearString.[2]
let y4 = stringNumberToByte yearString.[3]
let m1 = stringNumberToByte monthString.[0]
let m2 = stringNumberToByte monthString.[1]
let d1 = stringNumberToByte dayString.[0]
let d2 = stringNumberToByte dayString.[1]
let h1 = stringNumberToByte hourString.[0]
let h2 = stringNumberToByte hourString.[1]
let min1 = stringNumberToByte minuteString.[0]
let min2 = stringNumberToByte minuteString.[1]
let s1 = stringNumberToByte secondsString.[0]
let s2 = stringNumberToByte secondsString.[1]
[| y1 ; y2 ; y3 ; y4 ; m1 ; m2 ; d1 ; d2 ; h1 ; h2 ; min1 ; min2 ; s1; s2 |]
//Sets the high bit of a byte to 1
member private this.setHigh (b : byte) : byte =
let array = new BitArray([| b |])
array.[7] <- true
let mutable converted = [| 0 |]
array.CopyTo(converted, 0);
(byte)converted.[0]
//Build the header of a Message based on Intro + Data
member private this.buildHeader (intro : MessageIntro) (data : byte[]) =
let headerLength = 7;
let introLength = 7;
let length = (uint16)(headerLength + introLength + data.Length)
let crcData = ByteArrayExtender.Concat(intro.GetRawData(),data)
let crcValue = ByteArrayExtender.CalculateCRC16(crcData)
let lengthBytes = UShortExtender.ToIntelOrderedByteArray(length);
let crcValueBytes = UShortExtender.ToIntelOrderedByteArray(crcValue);
let headerChecksum = (byte)(SYNC_BYTE ^^^ lengthBytes.[0] ^^^ lengthBytes.[1] ^^^ crcValueBytes.[0] ^^^ crcValueBytes.[1])
let header = new MessageHeader();
header.Sync <- SYNC_BYTE
header.Length <- length
header.HeaderChecksum <- headerChecksum
header.DataChecksum <- crcValue
header
member private this.getStatusBytes =
let l = controller.Status
let status = (uint16)controller.Status
let statusBytes = BitConverter.GetBytes(status);
statusBytes |> Array.rev
end
(실제 소스, 클래스가 "하드웨어"보다 더 구체적 다른 이름을 가지고 있습니다)
를나는 코드 나 문제를 처리하는 경우에도 다른 방법을 개선하기 위해 제안, 방법을 바라고 있어요. 예를 들어, IronPython의 같은 동적 언어의 사용이 일을 더 쉽게 만들 것 , 내가 모두 함께 잘못된 방향으로 가고있다. 이 같은 문제에 대한 사용자의 경험은 무엇, 변경 될지, 등, 피 ....
업데이트 : 브라이언하여 답변에 따라이
, 내가 적어 다음
type DrCode9Item = {Name : string ; Timestamp : DateTime ; Size : uint32; Status : byte}
type DrCode11Item = {Id : byte ; X : uint16 ; Y : uint16 ; SizeX : uint16 ; SizeY : uint16
Font : string ; Alignment : byte ; Scroll : byte ; Flash : byte}
type DrCode12Item = {Id : byte ; X : uint16 ; Y : uint16 ; SizeX : uint16 ; SizeY : uint16}
type DrCode14Item = {X : byte ; Y : byte}
type DRType =
| DrCode0 of byte
| DrCode1 of byte * string * DateTime * uint32 * byte
| DrCode2 of byte * byte * uint16 * uint16 * array<byte>
| DrCode3 of byte * string
| DrCode4 of byte * string * DateTime * byte * uint16 * array<byte>
| DrCode5 of byte * uint16
| DrCode6 of byte * DateTime
| DrCode7 of byte * uint16 * uint16
| DrCode8 of byte * uint16 * uint16 * uint16 * array<byte>
| DrCode9 of byte * uint16 * array<DrCode9Item>
| DrCode10 of byte * string * DateTime * uint32 * byte * array<byte>
| DrCode11 of byte * array<DrCode11Item>
| DrCode12 of byte * array<DrCode12Item>
| DrCode13 of byte * uint16 * byte * uint16 * uint16 * string * byte * byte
| DrCode14 of byte * array<DrCode14Item>
내가, (꽤) 모든 DR 유형 을이 일을 계속할 수 있지만, 나는 그것이 내가 어떻게 도움이 될지 아직 이해하지 못한다. 나는 그것에 대해 을 Wikibooks와 F #의 기초에서 읽었지만 무언가는 아직 내 머리를 클릭하지 못하고있다.
업데이트 그래서 2
, 나는 내가 할 수있는 이해 다음
let execute dr =
match dr with
| DrCode0(drCode) -> printfn "Do something"
| DrCode1(drCode, name, timestamp, size, options) -> printfn "Show the size %A" size
| _ ->()
let date = DateTime.Now
let x = DrCode1(1uy,"blabla", date, 100ul, 0uy)
을하지만 메시지가 IMessageProcessor에 올 때, 의 choise는 바로 거기에 만든 메시지의 종류 그 이고 적절한 함수가 호출됩니다. 위의 코드는 단지 코드가 될 것입니다. 최소한 이해해야합니다. 그래서 여기에 요점이 빠져 있어야합니다 ... 그러나 나는 그것을 보지 못합니다.
execute x
제약 조건 : 에뮬레이터의 핵심을 크로스 플랫폼으로 만들고 싶습니다. 우리는 원시 코드, 특히 C 코드를 사용하지 않습니다. C 하드웨어 개발자가 코드 을 작성하는 방식을 보았습니다. 어디서나 약어 타사는 (tcp/ip 또는 rs232를 통해) 에뮬레이터와 통신하려는 것을 사용합니다. – TimothyP
여기에 차별 노동 조합과 함께 할 것이 무엇인지 설명 하시겠습니까? 나는 그들을 정의하는 방법을 알고 있지만 그들이 나를 도울 방법을 모르겠다 ... – TimothyP
당신은 말했다 : 패턴 매칭은 중첩 된 if/else를 피하기위한 좋은 방법처럼 보였기 때문에 "나는 IMessageProcessor 인터페이스를 구현하기 위해 F #을 사용하기로 결정했다. 그리고 계급 성명서 "와 나는 동의하지만 특히 메시지를 하위 사용자로 만드는 것이 핵심이다. 메시지가 하위 사용자 인 경우 메시지의 패턴 일치가 간단 해지며 이것이 가장 큰 승리라고 생각합니다. (당신은 어떻게 '메시지'를 표현하겠습니까?) – Brian