Какой шаблон дизайна я должен использовать? - PullRequest
1 голос
/ 10 июня 2010

Вот кратко, что я пытаюсь сделать.

Пользователь предоставляет мне ссылку на фотографию с одного из нескольких веб-сайтов обмена фотографиями (таких как Flickr, Zooomr и др.). Затем я выполняю некоторую обработку фотографии, используя соответствующие API.

Сейчас я реализую только один сервис, но, скорее всего, добавлю еще в ближайшее время.

Я не хочу иметь кучу операторов if / else или switch для определения логики для различных веб-сайтов (но, может быть, это необходимо?) Я бы лучше просто вызвал GetImage (url), и он мне изображение из любой службы, с которой домен URL. Я запутался, как должны быть разработаны функция и классы GetImage.

Может быть, мне нужен шаблон стратегии? Я все еще читаю и пытаюсь понять различные шаблоны проектирования и то, как я могу подобрать их в этом случае. Я делаю это в C #, но этот вопрос не зависит от языка.

Ответы [ 5 ]

3 голосов
/ 10 июня 2010

Тебе пока не нужен шаблон.Просто внедрите сервис в чистом виде, чтобы где-нибудь был метод Image FlikrApi.GetImage(Url).

// client code
Image image = flickApi.GetImage(url);

Когда вы приступите к реализации своего второго сервиса, у вас будут некоторые требования относительно того, как решить, какую функцию вызывать на основе URL-адреса.Затем вы можете решить, как это сделать - для двух служб это может быть так же просто, как переключение на доменное имя верхнего уровня.Итак, у вас есть переключатель, который вызывает тот или иной метод для того или иного объекта.

readonly FlickrApi flikrApi = new FlickrApi();
readonly WhateverApi whateverApi = new WhateverApi(); // third party 

Image GetImage (Url uri) {
    switch (url.TopLevelDomain()) {
       case "flickr.com":
           return flikrApi.GetImage(url);
           break;
       case "whatever.com":
           return whateverApi.GetWhateverImage(url);
           break;
       default: 
           throw new UnhandledUriException(uri);
}

// client code
Image image = GetImage(uri);

Научитесь считать - один, два, много.Когда вы нажмете много, подумайте о рефакторинге в шаблон.Может случиться так, что вам не удастся реализовать более двух сервисов, так как вы работаете над инфраструктурой для них, а не делаете что-то полезное.

Если у вас есть естьПотребность в более динамичных сервисах, и их можно выбирать только на основе домена верхнего уровня, тогда у меня, вероятно, будет карта - Dictionary<string, Func<Url,Image>>, заполненная tlds и делегатами сервиса.

readonly Dictionary<string, Func<Url,Image>> apis = new ...;

ImageApi () {
    apis["flickr.com"] = new FlickrApi().GetImage;
    apis["whatever.com"] = new WhateverApi().GetWhateverImage;
    apis["zzze.com"] = (uri) => Zoobers.GetWhateverImage(new ZooberCreds(), uri.ToString());
}

static Image GetImage (Url uri) {
    string tld = urli.TopLevelDomain();
    if (!imageApis.ContainsKey(tld)) throw new UnhandledUriException(uri);
    return imageApis[tld](uri);
}

// client code unchanged

На языке без замыканий / делегатов вы определяете интерфейс и используете его, но C # лучше этого, а использование встроенного типа функции позволяет вам использовать любую подходящую функцию вместосоздать класс просто для соответствия интерфейсу.Это не совсем паттерн стратегии, поскольку между контекстом и стратегией нет структурной связи - в паттерне стратегии есть контекст, в котором есть стратегия, только одна стратегия, и эта стратегия может измениться.Здесь мы выбираем между стратегиями, основанными на простом условии.

Если у вас есть более сложные требования к решению, какую службу использовать, то вам может понадобиться перебрать список интерфейсов до IImageApi, где интерфейс включает метод bool HandlesUrl(Url), чтобы спросить службу,распознает URL.В этом случае вместо использования делегатов для общения с любым сторонним кодом вам нужно будет использовать оболочку.

interface IImageApi {
    bool HandlesUri(Url);
    Image GetImage(Url);
}

readonly List<IImageApi> apis = new ...;

ImageApi () {
    apis.Add(new FlickrApi()); // you wrote this, so it can implement the interface
    apis.Add(new WhateverApiAdapter()); // third party requires adapter
    apis.Add(new ZoobersApiAdapter());  // ditto

    // or you can use something like MEF to populate the list
}

static Image GetImage (Url uri) {
    foreach (var api in apis)
       if (api.HandlesUri(uri))
           return api.GetImage(uri);

    throw new UnhandledUriException(uri);
}

// client code unchanged

Сначала сделайте самое простое, затем самое второе, самое простое, второе и наиболеесложная вещь, если вам нужно.

2 голосов
/ 10 июня 2010

Да, Стратегия звучит как хорошее решение для этого.Альтернативой может быть Шаблонный метод .

Стратегии, связанные с различными сайтами, могут быть созданы Абстрактной фабрикой / Фабричным методом , но еслиони не имеют состояния, они также могут быть просто сохранены на карте, причем ключи являются релевантной частью URL сайта.

1 голос
/ 10 июня 2010

Если вы используете только один метод GetImage, тогда стратегия - хороший выбор, вы можете использовать фабричный метод, чтобы установить правильную фабрику.

class Context
{
     IStrategy strategy;
     String url;

     void GetImage( url ) 
     {
          strategy = StrategyFactory.GetStrategyFor( url );
          image = strategy.GetImage( url );
     }
}
IStrategy 
{
     Image GetImage( string url );
}
FlickrStrategy:IStrategy
{
     Image GetImage( string url ) 
     {
          FlickerService service = new FlickrService();
          service.authenticate();
          // 
          return service.get( url );
     }
}

ImageShackUs:IStrategy
{
     Image GetImage( string url ) 
     {
          HttpClient service = new HttpClient(url);
          service.Connect();
          byte [] data = service.read()
           ......         
     }
}

Если вы используете больше методов, то структура в основном такая же, но не называется стратегией, это просто объект делегата.

1 голос
/ 10 июня 2010

Да, шаблон стратегии - хороший выбор.

Вы должны определить interface для определения своего собственного API, а затем реализовать его в нескольких стратегиях.

Взгляните наwikipedia пример c #: Шаблон стратегии

 interface IStrategy
  {
    void Execute();
  }

  // Implements the algorithm using the strategy interface
  class ConcreteStrategyA : IStrategy
  {
    public void Execute()
    {
      Console.WriteLine( "Called ConcreteStrategyA.Execute()" );
    }
  }

  class ConcreteStrategyB : IStrategy
  {
    public void Execute()
    {
      Console.WriteLine( "Called ConcreteStrategyB.Execute()" );
    }
  }
1 голос
/ 10 июня 2010

Похоже, вы хотите сделать две вещи:

  1. Отдельные реализации обработки для каждого сервиса. Здесь вы можете использовать шаблон стратегии.
  2. Скрыть тот факт, что существуют разные детали реализации обработки для каждой услуги от вашего абонента. Здесь используйте фабричный образец. Шаблон фабрики будет инкапсулировать любую логику, которую вы пишете, чтобы различать службы, и построить соответствующую стратегию.

В общем, я бы порекомендовал вам отложить абстракции и использование шаблонов проектирования до такой степени, чтобы это помогло вам - различные шаблоны решают разные проблемы. Куча шаблонов проектирования, которые усложняют чтение вашего кода, не имеет большого значения (если вы просто не пытаетесь узнать о шаблонах проектирования).

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

  • Шаблон адаптера - полезно, если вы хотите определить свой собственный API и "адаптировать" API каждого конкретного сервиса под свой собственный.
  • Шаблонный метод Pattern - Полезно, если ваш алгоритм остается в основном таким же, но вам нужно, чтобы определенные секции были «подключаемыми» в зависимости от реализации
...