У меня есть пример кода:
type Db =
| Db of Map<string, string>
let get id db =
let (Db dict) = db
dict
|> Map.tryFind id
|> (fun x -> x, db)
let add id value db =
let (Db dict) = db
dict
|> Map.add id value
|> Db
|> (fun x -> (), x)
let remove id db =
let (Db dict) = db
dict
|> Map.remove id
|> Db
|> (fun x -> (), x)
type GetByIdCapability =
| GetByIdCapability of ? (Id -> Value * ?) ?
type AddValueCapability =
| AddValueCapability of ? (Id -> Value -> ?) ?
type RemoveByIdCapability =
| RemoveByIdCapability of ? (Id -> ?) ?
type CreateAddValueCapability =
| CreateAddValueCapability of (Db -> AddValueCapability)
type AddValueService =
{
AddValueCapability: AddValueCapability
}
let createAddValueService db (CreateAddValueCapability createAddValueCapabilityFunc) =
let addValueCapability = db |> createAddValueCapabilityFunc
{
AddValueCapability = addValueCapability
}
let addValue id value (addValueService: AddValueService) =
addValueService.AddValueCapability ?
Я хочу иметь AddValueService
, который должен иметь доступ только к возможности «добавлять ценность в БД» и не имеет доступа к другим функциям БД внутри логики службы. Но в то же время я хочу получить обновленный экземпляр Db после вызова логики службы. Возможно ли реализовать?
Если я правильно понимаю вопрос, вы можете определить свой Db API в отдельном модуле и такие возможности:
type AddValueCapability =
private AddValueCapability of (string -> string -> Db -> unit * Db)
let addValueCapability = AddValueCapability add
let runAddValue (AddValueCapability f) = f
Обратите внимание, что тип AddValueCapability
имеет конструктор case private
, а это означает, что никакой код за пределами определяющего модуля не может сопоставляться с ним по образцу.
Значение addValueCapability
— это единственный способ, которым внешний клиентский код может получить значение этого типа. Вы можете экспортировать или не экспортировать это значение, в зависимости от ваших конкретных потребностей.
Хотя внешний клиентский код не может выполнять сопоставление с шаблоном в конструкторе case AddValueCapability
, он может запускать эту возможность с помощью функции runAddValue
.
Вот простой пример функции, которая определена вне модуля Db:
let useCapability (cap : AddValueCapability) db =
runAddValue cap "foo" "bar" db
Он может запускать эту возможность, но не что-либо еще, если вы также заблокируете базовую функциональность:
let private get id db =
let (Db dict) = db
dict
|> Map.tryFind id
|> (fun x -> x, db)
let private add id value db =
let (Db dict) = db
dict
|> Map.add id value
|> Db
|> (fun x -> (), x)
let private remove id db =
let (Db dict) = db
dict
|> Map.remove id
|> Db
|> (fun x -> (), x)
Вот демонстрация xUnit.net:
[<Fact>]
let Demo () =
let db = Map.empty |> Db
let _, actual = useCapability addValueCapability db
Assert.Equal (Map.ofList ["foo", "bar"] |> Db, actual)
Вы можете создавать другие возможности аналогичным образом и передавать только те возможности, которые вы хотите, чтобы клиентский код имел...
... или вы можете просто определить некоторые интерфейсы с одним методом и передать их клиентскому коду. Вот как вы обычно делаете это в OOD.