我应该使用哪种设计模式?

时间:2010-06-09 21:18:08

标签: language-agnostic design-patterns

以下是我正在尝试做的事情。

用户通过多个照片共享网站之一(例如Flickr,Zooomr等)向我提供了photo的链接。然后,我使用各自的API对照片进行一些处理。

目前,我只实施了一项服务,但我很可能会在不久的将来增加更多服务。

我不希望有一堆if / else或switch语句来定义不同网站的逻辑(但也许这是必要的?)我宁愿只调用GetImage(url)让它让我来自url域的任何服务的图像。我很困惑如何设计GetImage函数和类。

也许我需要策略模式?我还在阅读并尝试理解各种设计模式以及如何在这种情况下使其适合。我在C#中这样做,但这个问题与语言无关。

5 个答案:

答案 0 :(得分:3)

你还不需要一个模式。只需以干净的方式实现服务,以便某处有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)方法来询问服务是否它识别网址。在这种情况下,您不必使用委托与任何第三方代码交谈,而是必须使用包装器。

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

首先做最简单的事情,第二个最简单的事情是第二个,也是最复杂的事情。

答案 1 :(得分:2)

是的,Strategy听起来是一个很好的解决方案。另一种选择可能是Template Method

与不同网站相关联的策略可以由Abstract Factory / Factory Method生成,但如果它们是无状态的,它们也可以简单地存储在地图中,其中键是相关的部分。网站网址。

答案 2 :(得分:1)

看起来你想要做两件事:

  1. 每项服务的单独处理实施。在这里,您可以使用策略模式。
  2. 隐藏来自您的来电者的每项服务有不同的处理实施细节这一事实。在这里,使用工厂模式。工厂模式将封装您编写的任何逻辑以区分服务,并构建适当的策略。
  3. 一般情况下,我建议您将设计模式的抽象和使用延迟到可以帮助您的程度 - 不同的模式可以解决不同的问题。一堆使您的代码难以阅读的设计模式并没有多大帮助(除非您只是想了解设计模式)。

    您可能会觉得有用的其他模式:

    • Adapter Pattern - 如果您想要定义自己的API,并“适应”每个特定服务的API以适合您自己的API,则非常有用。
    • Template Method Pattern - 如果您的算法保持基本相同,但您需要根据实施情况“切换”特定部分

答案 3 :(得分:1)

是的,战略模式是一个不错的选择。

您应该定义一个interface来定义自己的API,然后在几个策略中实现它。

在维基百科中查看c#示例:Strategy pattern

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

答案 4 :(得分:1)

如果您只使用一种方法GetImage,那么策略是一个不错的选择,您可以使用Factory方法设置正确的工厂。

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

如果你使用更多的方法,那么结构基本相同,但不称为策略,它是简单的委托对象。