5

J'ai mon code qui effectue un appel de service Web basé sur le type de demande.Comment faire pour résoudre le type à l'exécution pour éviter multipe

Pour ce faire, j'ai le code suivant;

public class Client 
{ 
    IRequest request; 


    public Client(string requestType) 
    { 
     request = new EnrolmentRequest(); 
     if (requestType == "Enrol") 
     { 
      request.DoEnrolment(); 
     } 
     else if (requestType == "ReEnrol") 
     { 
      request.DoReEnrolment(); 
     } 
     else if (requestType == "DeleteEnrolment") 
     { 
      request.DeleteEnrolment(); 
     } 
     else if (requestType == "UpdateEnrolment") 
     { 
      request.UpdateEnrolment(); 
     } 
    } 

} 

Ainsi, selon ouvert principe proche, je peux sous-classe comme:

Class EnrolmentRequest:IRequest 
{ 
    CallService(); 
} 
Class ReEnrolmentRequest:IRequest 
{ 
    CallService(); 
} 
Class UpdateEnrolmentRequest:IRequest 
{ 
    CallService(); 
} 

Maintenant, ma classe client ressemblera à quelque chose comme ceci:

public class Client 
{ 
    public Client(string requestType) 
    { 
     IRequest request; 

     if (requestType == "Enrol") 
     { 
      request = new EnrolmentRequest(); 
      request.CallService(); 
     } 
     else if (requestType == "ReEnrol") 
     { 
      request = new REnrolmentRequest(); 
      request.CallService(); 
     } 
     else if (requestType == "DeleteEnrolment") 
     { 
      request = new UpdateEnrolmentRequest(); 
      request.CallService(); 
     } 
     else if (requestType == "UpdateEnrolment") 
     { 
      request = new UpdateEnrolmentRequest(); 
      request.CallService(); 
     } 
    } 

} 

Maintenant, je dois encore utiliser si et sinon, et devra changer mon code s'il y a un nouveau type de requête.

Donc, ce n'est définitivement pas fermé à la modification.

Est-ce que je manque quelque chose à propos de SOLID? Puis-je utiliser l'injection de dépendance pour résoudre les types lors de l'exécution?

+0

D'où provient 'requestType'? Est-ce pseudocode pour 'request.GetType()'? – InBetween

+0

Peut-être que cela devrait mieux être posté dans http://codereview.stackexchange.com/ –

+0

la décision de quelle classe à instancier doit être faite quelque part - il n'y a pas moyen de contourner cela. La décision peut être déplacée vers une usine qui utilise la réflexion ou l'enregistrement, mais elle ne peut pas être éliminée. –

Répondre

4

Vous pouvez ajouter simple classe usine comme ci-dessous:

public class ServiceFactory : Dictionary<string, Type> 
{ 
    public void Register(string typeName, Type serviceType) { 
     if (this.ContainsKey(typeName)) { 
      throw new Exception("Type registered"); 
     } 
     this[typeName] = serviceType; 
    } 

    public IRequest Resolve(string typeName) { 
     if (!this.ContainsKey(typeName)) { 
      throw new Exception("Type not registered"); 
     } 
     var type = this[typeName]; 
     var service = Activator.CreateInstance(type); 
     return service as IRequest; 
    } 
} 

puis enregistrer les services en un seul endroit comme:

var serviceFactory = new ServiceFactory(); 
     serviceFactory.Register("Enrol", typeof(EnrolmentRequest)); 
     serviceFactory.Register("ReEnrol", typeof(REnrolmentRequest)); 
     serviceFactory.Register("DeleteEnrolment", typeof(UpdateEnrolmentRequest)); 
     serviceFactory.Register("UpdateEnrolment", typeof(UpdateEnrolmentRequest)); 

et l'appeler:

var service = serviceFactory.Resolve(requestType); 
service.CallService(); 

aussi besoin d'ajouter une erreur de manipulation correcte

4

Bonne question, vous pouvez atteindre votre objectif en utilisant une seule méthode:

var request = (IRequest)Activator.CreateInstance("NameOfYourAssembly", requestType); 
request.CallService(); 

réflexion vous aidera à générer votre instance de classe. Après cela, vous pouvez l'appeler sans if/else.

S'il vous plaît se référer à ce lien pour plus d'informations sur la méthode fournie: https://msdn.microsoft.com/it-it/library/3k6dfxfk(v=vs.110).aspx

Hope this peut aider

+0

Veuillez noter que 'CreateInstance()' est plusieurs ordres de grandeur ** plus lent ** que l'équivalent 'new EnrolmentRequest()' selon l'autre réponse sur cette page – MickyD

+0

Merci @MickyD, tu as raison. Je viens de fournir un moyen de faire face à cette situation. Dans ce cas, nous ne parlons pas de performance, en tout cas votre information est fondamentale pour faire le choix de ce qu'il faut faire avec l'instruction de code. –

+0

D'accord. Pas un problème bon monsieur. :) – MickyD

8

La nécessité d'écrire un nouveau code pour gérer les nouvelles exigences ne va pas disparaître. L'objectif est de ne pas avoir à modifier l'ancien code lors de la gestion des nouvelles exigences, et votre structure de classe gère cela.

Vous pouvez minimiser les modifications en remplaçant votre chaîne de conditions par un autre mécanisme de création de nouvelles instances. Par exemple, vous pouvez créer un dictionnaire ou utiliser un framework d'injection de dépendance pour associer un type à une chaîne.

est ici une mise en œuvre sans utiliser le cadre de DI:

private static readonly IDictionary<string,Func<IRequest>> ReqTypeMapper = 
    new Dictionary<string,Func<IRequest>> { 
     {"Enrol",() => new EnrolmentRequest() } 
    , {"ReEnrol",() => new ReEnrolmentRequest() } 
    , ... 
    }; 

Maintenant, l'appel ressemblera à ceci:

Func<IRequest> maker; 
if (!ReqTypeMapper.TryGetValue(requestType, out maker)) { 
    // Cannot find handler for type - exit 
    return; 
} 
maker().CallService(); 
5

Vous ne pouvez pas vraiment supprimer la liste des if - else ou switch - case déclarations complètement, à moins que vous rétablissez à l'aide de la réflexion. Quelque part dans le système, vous aurez certainement une sorte de répartition (soit en utilisant une liste codée en dur ou par réflexion).

Votre conception pourrait cependant bénéficier d'une approche plus message, où les demandes de Incomming sont un message, tels que:

class DoEnrolment { /* request values */ } 
class DoReenrolment { /* request values */ } 
class DeleteEnrolment { /* request values */ } 
class UpdateEnrolment { /* request values */ } 

Cela vous permet de créer une defenition unique interface pour « gestionnaires » de cette demande:

interface IRequestHandler<TRequest> { 
    void Handle(TRequest request); 
} 

Vos gestionnaires se présente comme suit:

class DoEnrolmentHandler : IRequestHandler<DoEnrolment> { 
    public void Handle(DoEnrolment request) { ... } 
} 

class DoReenrolmentHandler : IRequestHandler<DoReenrolment> { 
    public void Handle(DoReenrolment request) { ... } 
} 

class DeleteEnrolmentHandler : IRequestHandler<DeleteEnrolment> { 
    public void Handle(DeleteEnrolment request) { ... } 
} 

L'avantage de ceci est que l'application des préoccupations transversales est un jeu d'enfant, car il est très simple de définir un décorateur générique pour IRequestHandler<T> qui implémente quelque chose comme la journalisation.

Cela nous ramène encore à l'expédition bien sûr. Dispatching peut être extrait du client, derrière sa propre abstraction:

interface IRequestDispatcher { 
    void Dispatch<TRequest>(TRequest request); 
} 

Cela permet au client d'envoyer simplement la demande il faut:

// Client 
this.dispatcher.Dispatch(new DoEnrolment { EnrolId = id }); 

Une mise en œuvre du répartiteur de demande pourrait ressembler à ceci:

class ManualRequestDispatcher : IRequestDispatcher { 
    public void Dispatch<TRequest>(TRequest request) { 
     var handler = (IRequestHandler<TRequest>)CreateHandler(typeof(TRequest)); 
     handler.Handle(request); 
    } 

    object CreateHandler(Type type) => 
     type == typeof(DoEnrolment)? new DoEnrolmentHandler() : 
     type == typeof(DoReenrolment) ? new DoReenrolment() : 
     type == typeof(DeleteEnrolment) ? new DeleteEnrolment() : 
     type == typeof(UpdateEnrolment) ? new UpdateEnrolment() : 
     ThrowRequestUnknown(type); 

    object ThrowRequestUnknown(Type type) { 
     throw new InvalidOperationException("Unknown request " + type.Name); 
    } 
} 

Si vous utilisez un conteneur DI cependant, vous serez en mesure à enregistrer vos lots gestionnaires de requêtes avec quelque chose comme suit (en fonction de la bibliothèque que vous utilisez o cours f):

container.Register(typeof(IRequestHandler<>), assemblies); 

Et votre distributeur peut se présenter comme suit:

class ContainerRequestDispatcher : IRequestDispatcher { 
    private readonly Container container; 
    public ContainerRequestDispatcher(Container container) { 
     this.container = container; 
    } 

    public void Dispatch<TRequest>(TRequest request) { 
     var handler = container.GetInstance<IRequestHandler<TRequest>>(); 
     handler.Handle(request); 
    } 
} 

Vous pouvez trouver plus d'informations sur ce type de conception here et here.

+1

Marqué Sergie comme réponse car il était facile de suivre le long.Votre réponse est de premier ordre et prendra un certain temps à comprendre. Thaks for the solution – Simsons

+0

"Vous ne pouvez pas vraiment supprimer complètement la liste des instructions if-elseor switch-case, à moins que vous ne retourniez à l'utilisation de la réflexion." Vraiment? voir dasblinkenlights réponse. – weston

+1

@weston: Notez que je parle d'une "liste codée en dur" d'options. Le dictionnaire est une liste codée en dur de même que 'if'-'else' et' switch'-'case'. – Steven

0

vous pouvez utiliser autofac keyed or named service..

public enum OperationType 
{ 
    Enrol, 
    ReEnrol, 
    DeleteEnrolment, 
    UpdateEnrolment 
} 

     //register types 
     builder.RegisterType<EnrolmentRequest>().Keyed<IRequest>(OperationType.Enrol); 
     builder.RegisterType<ReEnrolmentRequest>().Keyed<IRequest>(OperationType.ReEnrol); 
     builder.RegisterType<UpdateEnrolmentRequest>().Keyed<IRequest>(OperationType.DeleteEnrolment | OperationType.UpdateEnrolment); 


     // resolve by operationType enum 
     var request = container.ResolveKeyed<IRequest>(OperationType.Enrol); 
3

Vous pouvez utiliser modèle usine avec RIP (Si Replace avec Polymorphism) pour éviter plusieurs if-else.

code suivant l'exemple de code en fonction de votre Client classe:

public enum RequestType : int 
{ 
    Enrol = 1, 
    ReEnrol, 
    UpdateEnrolment 
} 

public interface IRequest 
{ 
    void CallService(); 
} 

public class EnrolmentRequest : IRequest 
{ 
    public void CallService() 
    { 
     // Code for EnrolmentRequest 
    } 
} 

public class ReEnrolmentRequest : IRequest 
{ 
    public void CallService() 
    { 
     // Code for ReEnrolmentRequest 
    } 
} 

public class UpdateEnrolmentRequest : IRequest 
{ 
    public void CallService() 
    { 
     // Code for UpdateEnrolmentRequest 
    } 
} 

// Factory Class 
public class FactoryChoice 
{ 
    private IDictionary<RequestType, IRequest> _choices; 

    public FactoryChoice() 
    { 
     _choices = new Dictionary<RequestType, IRequest> 
      { 
       {RequestType.Enrol, new EnrolmentRequest() }, 
       {RequestType.ReEnrol, new ReEnrolmentRequest()}, 
       {RequestType.UpdateEnrolment, new UpdateEnrolmentRequest()} 
      }; 
    } 

    static public IRequest getChoiceObj(RequestType choice) 
    { 
     var factory = new FactoryChoice(); 

     return factory._choices[choice]; 
    } 
} 

et il sera appeler comme:

IRequest objInvoice = FactoryChoice.getChoiceObj(RequestType.ReEnrol); 
objInvoice.CallService(); 

Ici, principales choses sont arrivées dans le constructeur de la classe FactoryChoice. C'est pourquoi quelqu'un l'a appelé constructeur intelligent. De cette façon, vous pouvez éviter les lignes multiples if-else ou switch-case.

Pour connaître la base de RIP vous pouvez consulter ma diapositive here.