5

У меня есть код, который создает веб-сервис Call, основанный на типе запроса.Как разрешить тип во время выполнения, чтобы избежать использования multipe if else

Для этого у меня есть следующий код;

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(); 
     } 
    } 

} 

Так, как в открытом близко принципе, я могу подкласс как:

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

Теперь мой класс клиент будет выглядеть примерно так:

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(); 
     } 
    } 

} 

Теперь, я до сих пор использовать if и else, и мне придется изменить свой код, если есть новый тип запроса.

Итак, это определенно, не закрыто для модификации.

Не хватает ли какой-либо вещи в отношении SOLID?

Могу ли я использовать инъекцию зависимостей, чтобы разрешать типы во время выполнения?

+0

Откуда: 'requestType'? Это псевдокод для 'request.GetType()'? – InBetween

+0

Возможно, это должно быть лучше опубликовано в http://codereview.stackexchange.com/ –

+0

, решение о том, какой класс нужно создать, должно быть сделано где-то - этого не обойти. Решение может быть перенесено на фабрику, которая использует отражение или регистрацию, но ее нельзя устранить. –

ответ

4

Вы можете добавить простой класс фабрики, как показано ниже:

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; 
    } 
} 

затем зарегистрируйте услуги в одном месте:

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

и называют его:

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

также необходимо добавить правильную обработку ошибок

4

Хороший вопрос, вы можете достичь своей цели, используя один единственный метод:

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

Отражение поможет вам генерировать экземпляр класса. После этого вы можете вызывать его без if/else.

Пожалуйста, обратитесь к этой ссылке для получения дополнительной информации о предоставленном способе: https://msdn.microsoft.com/it-it/library/3k6dfxfk(v=vs.110).aspx

Надеется, что это может помочь

+0

Обратите внимание, что 'CreateInstance()' на несколько порядков меньше медленнее **, чем эквивалент 'new EnrolmentRequest()' в соответствии с другим ответом на этой странице – MickyD

+0

Спасибо @MickyD, Ты прав. Я только что предложил способ справиться с этой ситуацией. В этом случае мы говорим не о производительности, в любом случае ваша информация имеет основополагающее значение для выбора того, что делать с инструкцией кода. –

+0

Согласен. Не проблема, сэр. :) – MickyD

8

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

Вы можете свести к минимуму изменения, заменив цепочку условных обозначений на другой механизм создания новых экземпляров. Например, вы можете создать словарь или использовать инфраструктуру инъекции зависимостей для связывания типа со строкой.

Вот реализация без использования рамки DI:

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

Теперь вызов будет выглядеть следующим образом:

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

Вы не можете удалить список if - else или switch - case заявления полностью, если вы не вернуться к использованию отражение. Где-то в системе у вас определенно будет какая-то диспетчеризация (либо с использованием жестко закодированного списка, либо через отражение).

Ваш дизайн однако может извлечь выгоду из основанных еще сообщений подхода, в котором запросы Incomming являются сообщениями, такими как:

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

Это позволяет создать единый интерфейс Defenition для «обработчиков» такого запроса:

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

обработчики будут выглядеть следующим образом:

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) { ... } 
} 

Преимущество этого заключается в том, что применение сквозных проблем - это бриз, так как очень просто определить общий декоратор для IRequestHandler<T>, который реализует что-то вроде ведения журнала.

Это все еще возвращает нас к отправке, конечно. Диспетчерская можно извлечь из клиента, за его собственной абстракции:

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

Это позволяет клиенту просто отправить запрос, он требует:

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

Реализация запроса диспетчеру может выглядеть следующим образом:

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); 
    } 
} 

Если вы используете DI контейнер, однако, вы будете иметь возможность пакетной зарегистрировать обработчики запросов с чем-то следующим образом (в зависимости от библиотеки вы используете о е курс):

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

И диспетчер может выглядеть следующим образом:

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); 
    } 
} 

Вы можете найти более подробную информацию об этом типе конструкции here и here.

+1

Отмеченный ответ Сергея, как легко было проследить. Ваш ответ на высшем уровне и займет некоторое время, чтобы понять. Thaks для решения – Simsons

+0

«Вы действительно не можете полностью удалить список операторов case-ifor-switchor, если только вы не вернетесь к использованию отражения». Действительно? см. ответ dasblinkenlights. – weston

+1

@weston: Обратите внимание, что я говорю о «жестко запрограммированном списке» опций. Словарь является жестко запрограммированным списком, так же как 'if'-'sese' и' switch' -'case'. – Steven

0

вы можете использовать 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

Вы можете использовать Factory шаблон с RIP (Заменить Если с полиморфизмом), чтобы избежать многочисленных if-else.

После кода приведен пример кода в соответствии с вашим Client класса:

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]; 
    } 
} 

и он будет называть как:

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

Вот основные вещи происходили в FactoryChoice конструктор класса. Вот почему кто-то назвал это умный конструктор. Таким образом, вы можете избежать multilpe if-else или switch-case.

Чтобы узнать основную информацию о RIP, вы можете проверить мой слайд here.

Смежные вопросы