Можно ли иметь полиморфную фабрику для типов, унаследованных от интерфейса?

Следуя советам в 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

Я думаю, что ваше недопонимание относительно поведения дискриминационных союзов связано с тем фактом, что вы думаете о них как о абстрактных классах с несколькими подклассами, лучше и точнее думать о них как об одном типе с несколькими конструкторами.

Другие вопросы по тегам