Другой, менее распространенный, но заслуживающий упоминания подход заключается в использовании так называемых фантомных типов . Идея заключается в том, что у вас будет общая оболочка ID<'T>
, а затем вы будете использовать разные типы для 'T
для представления разных типов идентификаторов. Эти типы фактически никогда не создаются, поэтому они называются фантомными типами.
[<Struct>]
type ID<'T> = ID of System.Guid
type CustomerID = interface end
type ProductID = interface end
Теперь вы можете создавать значения ID<CustomerID>
и ID<ProductID>
для представления двух типов идентификаторов:
let newCustomerID () : ID<CustomerID> = ID(System.Guid.NewGuid())
let newProductID () : ID<ProductID> = ID(System.Guid.NewGuid())
Приятно то, что вы можете легко писать функции, работающие с любым идентификатором:
let printID (ID g) = printfn "%s" (g.ToString())
Например, теперь я могу создать один идентификатор клиента, один идентификатор продукта и распечатать оба, но я не могу выполнить тест на равенство этих идентификаторов, поскольку их типы не совпадают:
let ci = newCustomerID ()
let pi = newProductID ()
printID ci
printID pi
ci = pi // Type mismatch. Expecting a 'ID<CustomerID>' but given a 'ID<ProductID>'
Это хитрый трюк, но он немного сложнее, чем просто использование нового типа для каждого идентификатора. В частности, вам, вероятно, понадобится больше аннотаций типов в различных местах, чтобы сделать эту работу, и ошибки типов могут быть менее очевидными, особенно когда используется общий код. Тем не менее, стоит упомянуть об этом в качестве альтернативы.