Стратегический шаблон - это правильно?

Я надеюсь, что вы можете помочь мне с моей проблемой:

У меня есть класс, делающий мыльные звонки. Но если определение мыла изменится, мне придется написать новый класс или наследовать от него и т. д. Поэтому я пришел к решению написать что-то вроде этого:

switch(version)
{
  case "1.0":
     saopV1.getData()
  case "2.0":
     soapV2.getData()
}

Ну, довольно плохой код, я знаю. Затем я прочитал о паттерне Стратегия и подумал, вот что мне нужно, чтобы избавиться от этого плохого переключателя:

abstract SoapVersion
{
    public SoapVersion GetSoapVersion(string version)
    {
         //Damn switch-case thing
         //with return new SoapV1() and return new SoapV2()
    }
    public string[] virtual getData()
    {
          //Basic Implementation
    }
}

class SoapV1:SoapVersion
{
       public override string[] getData()
       {
           //Detail Implementation
       }
}

class SoapV2:SoapVersion
{//the same like soapv1}

Но я не могу избежать использования «если» или переключения регистров в своем коде. Возможно ли это с помощью ОО-методов??

Изменить: функция GetSoapVersion должна быть статической.


person Nathan Nash    schedule 30.11.2010    source источник


Ответы (6)


Это более или менее правильный способ сделать это красиво. В какой-то момент вашего кода вам придется принять решение, следует ли использовать v1 или v2, поэтому вам все равно придется иметь условный оператор (if или switch). Однако при использовании стратегии и фабрики (фабричный метод или фабричный класс) вы принимаете это решение централизованно.

Однако я бы сделал свой фабричный метод для абстрактного класса статическим. Кроме того, я бы использовал шаблон метода шаблона: то есть общедоступный, непереопределяемый метод GetData, который вызывает защищенный виртуальный (абстрактный) метод, который должен быть переопределен в конкретной реализации.

public abstract class SoapProcessor
{

    protected SoapProcessor() { /* protected constructor since public is of no use */  }

    public static SoapProcessor Create( SoapVersion version )
    {
          switch( version )
          {
               case SoapVersion.Version1 : return new SoapV1Processor();
               case SoapVersion.Version2 : return new SoapV2Processor();
               default: throw new NOtSupportedException();
          }
    }


    public string[] GetData()
    {
         return GetDataCore();
    }

    protected abstract GetDataCore();
 }

}

person Frederik Gheysels    schedule 30.11.2010
comment
спасибо за полезный ответ. на самом деле я не знал шаблон шаблона-метода, он очень пригодится позже. но в этом случае мне нужно определить реализацию по умолчанию. Так что этот шаблон не является необходимым, не так ли? - person Nathan Nash; 30.11.2010
comment
Когда вам нужно определить реализацию по умолчанию, я думаю, вам также не нужен абстрактный класс. - person Frederik Gheysels; 30.11.2010

Есть разница, если у вас есть переключатели только на фабриках или во всем коде. У вас есть свое решение (какую реализацию выбрать) в одной точке.

person Stefan Steinegger    schedule 30.11.2010

В подобных случаях я выбираю между рефлексией и if/case по следующему критерию: если поддержка новых версий должна добавляться динамически (как плагины), я выбираю рефлексию, иначе - if/case. Как упоминалось в других ответах, внутри фабричного метода должно быть единое место для создания вещей. Стоит отметить, что Strategy — это поведенческий паттерн, а то, что вы просили, похоже на творчество.

person khachik    schedule 30.11.2010
comment
полезная информация о плагине, спасибо. Итак, я реализовал шаблон Factory Method вместо шаблона стратегии ?? я немного смущен сейчас. Или фабричный метод является своего рода шаблоном стратегии? - person Nathan Nash; 30.11.2010
comment
@fczler 1. Я бы сделал это фабричным методом. 2. Нет, Фабричный Метод не является Стратегией. Это обсуждение может быть полезным: stackoverflow.com/questions/1498823/factory-strategy-patterns - person khachik; 30.11.2010

Вам не нужны операторы switch или if.
Просто используйте делегирование.
Т.е. конкретные реализации абстрактного класса будут выполняться по мере необходимости (например, SoapV1, SoapV2 и т. д.), а клиент устанавливает соответствующий экземпляр в ссылке на объект.
У вас есть только ссылка на базовый класс и соответствующий подкласс устанавливается клиентом. Ваш код просто вызывает методы базового класса (который во время выполнения является одной из производных реализаций). Например. пример (ОТВЕТСТВЕННОСТЬ: код не скомпилирован. Только образец)

public abstract class SoapHandler
{

    protected abstract string[] getData();
 }

public class SoapHandlerV1 extends SoapHandler
{

    public string[] getData(){
        //V1 implementation
    }

}
public class SoapHandlerV2 extends SoapHandler
{

    public string[] getData(){
        //V2 implementation
    }

}


public class SoapProcessor{

    public SoapHandler soapHandler;

    public setSoapHandler(SoapHandler h)
    {
                soapHandler = h;
    }

    public String[] getData(){
        //delegate to specific version
        soapHandler->getData();
    }
}


//in your code
SoapProcessor soap = new SoapProcessor();
soap.setSoapHandler(new SoapHandlerV1());
String[] soapData = soap.getData();//Will get the appropriate version
//use soap data
//do stuff

Проверьте пример GoF для стратегии, чтобы увидеть, что я имею в виду, если это не ясно

person Cratylus    schedule 30.11.2010

Поскольку version известен только во время выполнения, он определенно сведется к некоторому условному выражению (если или переключаться, или использовать карту между строками и прототипом и т. д.).

Следовательно, достойная цель состоит в том, чтобы уменьшить количество условных выражений/изолировать точки изменения.

person lijie    schedule 30.11.2010

Вы должны программировать интерфейс, а не реализацию.

Имейте отдельный сервисный интерфейс, который вы можете использовать со стороны вашего клиента.

public interface IService
{
    string[] GetData();
}

и закодируйте своего клиента как -

IService srvice = ServiceFactory.GetProxy();
string[] value = service.GetData();

Таким образом, ваш клиентский код не изменится при изменении прокси-сервера службы.

Затем вы для начала можете перенести условную логику для создания соответствующего прокси в класс ServiceFactory. Позже вы можете изменить его, чтобы удалить условную логику, используя следующие методы, такие как:

  1. Чтение класса реализации и имени сборки из файла конфигурации и создание его с помощью отражения.
  2. Создание словаря экземпляров прокси с мыльной версией в качестве ключей.
person Unmesh Kondolikar    schedule 30.11.2010
comment
Скажите, чем в данном случае интерфейс лучше абстрактного класса? Абстрактный класс содержит (статический) фабричный метод (невозможно с интерфейсом), а абстрактный класс может содержать базовую (или общую) реализацию (если применимо, в данном случае непонятно). Код клиента также не изменится при использовании класса asbract. - person Frederik Gheysels; 30.11.2010
comment
Что ж... я хочу подчеркнуть, что код, вызывающий сервисный метод, не должен содержать никакой условной логики. Можно использовать абстрактный класс, если он подходит лучше, чем интерфейс. - person Unmesh Kondolikar; 30.11.2010