0

Suite à des conseils dans SO et d'autres endroits, j'ai essayé d'implémenter le polymorphisme en utilisant des interfaces, comme dans l'exemple simplifié suivant.Peut-on avoir une fabrique polymorphique pour les types héritant d'une interface?

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 

Ces valeurs peuvent être utilisées par les constructeurs de X, Y et Z:

let zX = 1 
let zY = "2" 
let zZ = 3.0 

Je voudrais construire une usine polymorphes comme

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" 

Cependant, create sera pas compiler car il renverrait des objets de types différents.

Une alternative serait de transtyper tous les cas 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" 

Puis create1 mais sa compilation aurait valeur de retour serait (je crois) inutile à moins baissés à X, Y ou Z Mais cela soulève à nouveau le problème du polymorphisme: pour redescendre génériquement, j'aurais besoin d'une fonction qui renvoie des valeurs de différents types.

Une autre alternative serait d'utiliser une Union discriminés

type ClassDU = 
    | XClass of X 
    | YClass of Y 
    | ZClass of Z 

et utilisez la fonction create définie ci-dessus. Mais ça ne marche pas. Sans upcasting le compilateur voit toujours les différents cas comme ayant différents types. Et upcasting à ClassDU ne fonctionne pas puisque ClassDU n'est pas une classe.

Dans ce code snippet

http://fssnip.net/k6/title/-F-Factory-Pattern

le problème est résolu en utilisant une classe abstraite XYZ à partir de laquelle X, Y et Z hériterait la méthode abstraite foo. La fonction analogue à create serait transtyper tous les cas à la classe abstraite:

let create2 (str: string) = 
    match str with 
    | "X" -> X(zX) :> XYZ 
    | "Y" -> Y(zY) :> XYZ 
    | "Z" -> Z(zZ) :> XYZ 
    | _ -> failwith "Something went wrong" 

Est-il possible de créer une usine polymorphes pour les types héritant d'une interface ou doit-on créer une classe abstraite comme dans l'extrait de code?

Répondre

4

Interfaces

Pour ce faire avec des interfaces, il vous suffit de transtyper le type de l'interface, par exemple:

let create (str: string) = 
    match str with 
    | "X" -> X(zX) :> IFace 
    | "Y" -> Y(zY) :> IFace 
    | "Z" -> Z(zZ) :> IFace 
    | _ -> failwith "Something went wrong" 

C'est de type signature:

val create : str:string -> IFace 

Unions décimés

Pour ce faire avec les DU, vous devez ajouter le constructeur de cas approprié dans la fonction 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" 

C'est de type signature:

val createDU : str:string -> ClassDU 

Je pense que votre incompréhension autour du comportement des syndicats discriminés découle du fait que vous pensez à eux comme étant comme des classes abstraites avec plusieurs sous-classes, il est préférable et plus précis de les considérer comme un seul type avec plusieurs constructeurs .

+0

J'ai suivi votre suggestion concernant les interfaces et bien sûr cela a fonctionné. Deux questions. D'abord, après avoir construit mes objets en utilisant la fonction 'create', certaines mises à jour sont devenues inutiles et le compilateur a commencé à donner des avertissements à leur sujet. Les objets que je suis en train de traiter sont-ils différents des objets que j'avais créés avant d'utiliser les constructeurs de classes régulières? Si c'est le cas, est-ce que je perds certaines fonctionnalités? Deuxièmement: que se passe-t-il si mes classes héritent de plusieurs interfaces et ne créent que des mises à jour pour l'une d'entre elles? Puis-je diffuser simultanément plusieurs interfaces? – Soldalma