Можно ли иметь полиморфную фабрику для типов, унаследованных от интерфейса?
Следуя советам в SO и других местах, я попытался реализовать полиморфизм, используя интерфейсы, как в следующем упрощенном примере.
type IFace =
// abstract methods
abstract member foo: int -> int
type X(i: int) =
// some code
interface IFace with
member this.foo j = i + j
type Y(s: string) =
// some code
interface IFace with
member this.foo j = (int s) + j
type Z(x: float) =
// some code
interface IFace with
member this.foo j = (int x) + j
Эти значения могут использоваться конструкторами X, Y и Z:
let zX = 1
let zY = "2"
let zZ = 3.0
Я хотел бы построить полиморфную фабрику, как
let create (str: string) =
match str with
| "X" -> X(zX)
| "Y" -> Y(zY)
| "Z" -> Z(zZ)
| _ -> failwith "Something went wrong"
let XObj = create "X"
let YObj = create "Y"
let ZObj = create "Z"
Тем не мение, create
не будет компилироваться, так как он будет возвращать объекты разных типов.
Одной из альтернатив будет отклонение всех случаев в create
в System.Object:
let create1 (str: string) =
match str with
| "X" -> X(zX) :> System.Object
| "Y" -> Y(zY) :> System.Object
| "Z" -> Z(zZ) :> System.Object
| _ -> failwith "Something went wrong"
затем create1
будет компилироваться, но его возвращаемое значение будет (я считаю) бесполезным, если не уменьшить его до X, Y или Z. Но это снова поднимает проблему полиморфизма: для общего понижения мне понадобится функция, которая возвращает значения разных типов.
Другой альтернативой будет использование Дискриминационного Союза
type ClassDU =
| XClass of X
| YClass of Y
| ZClass of Z
и использовать функцию create
определено выше. Но это не работает. Без перерасчета компилятор по-прежнему видит разные случаи как имеющие разные типы. И преобразование в ClassDU не работает, поскольку ClassDU не является классом.
В этом фрагменте кода
http://fssnip.net/k6/title/-F-Factory-Pattern
проблема решается с использованием абстрактного класса XYZ, от которого X, Y и Z наследуют абстрактный метод foo. Функция, аналогичная create
превратить все случаи в абстрактный класс:
let create2 (str: string) =
match str with
| "X" -> X(zX) :> XYZ
| "Y" -> Y(zY) :> XYZ
| "Z" -> Z(zZ) :> XYZ
| _ -> failwith "Something went wrong"
Можно ли создать полиморфную фабрику для типов, унаследованных от интерфейса, или нужно создать абстрактный класс, как в фрагменте кода?
1 ответ
Интерфейсы
Чтобы сделать это с интерфейсами, вам просто нужно выполнить переход на тип интерфейса, например:
let create (str: string) =
match str with
| "X" -> X(zX) :> IFace
| "Y" -> Y(zY) :> IFace
| "Z" -> Z(zZ) :> IFace
| _ -> failwith "Something went wrong"
Это имеет тип подписи:
val create : str:string -> IFace
Отмененные Союзы
Чтобы сделать это с DU, вам нужно добавить конструктор case-appriopriate в create
функция.
type ClassDU =
| XClass of X
| YClass of Y
| ZClass of Z
let createDU (str: string) =
match str with
| "X" -> XClass <| X(zX)
| "Y" -> YClass <| Y(zY)
| "Z" -> ZClass <| Z(zZ)
| _ -> failwith "Something went wrong"
Это имеет тип подписи:
val createDU : str:string -> ClassDU
Я думаю, что ваше недопонимание относительно поведения дискриминационных союзов связано с тем фактом, что вы думаете о них как о абстрактных классах с несколькими подклассами, лучше и точнее думать о них как об одном типе с несколькими конструкторами.