【问题标题】:Which design pattern should I be using?我应该使用哪种设计模式?
【发布时间】:2010-06-09 21:18:07
【问题描述】:

这是我想要做的简要说明。

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

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

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

也许我需要策略模式?我仍在阅读并尝试了解各种设计模式以及如何在这种情况下做出适合的设计模式。我在 C# 中执行此操作,但这个问题与语言无关。

【问题讨论】:

    标签: language-agnostic design-patterns


    【解决方案1】:

    你还不需要一个模式。只需以干净的方式实现服务,以便在某处有一个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);
    

    学会数数 - 一、二、多。当您遇到许多问题时,请考虑重构为一种模式。可能您无法实现两个以上的服务,因为您正在为它们开发一个框架,而不是做一些有用的事情。

    如果您确实需要更多动态服务,并且只能基于顶级域进行选择,那么我可能会有一个地图 - 一个填充有 tlds 的 Dictionary<string, Func<Url,Image>>和服务代表。

    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】:

      是的,Strategy 听起来是一个很好的解决方案。替代方案可能是Template Method。

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

      【讨论】:

        【解决方案3】:

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

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

        在 wikipedia 中查看 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. 每个服务的单独处理实现。在这里,您可以使用策略模式。
          2. 对调用者隐藏每个服务的不同处理实现细节这一事实。在这里,使用工厂模式。工厂模式将封装您为区分服务而编写的任何逻辑,并构建适当的策略。

          一般来说,我建议您将设计模式的抽象和使用延迟到对您有帮助的地步——不同的模式解决不同的问题。一堆让你的代码更难阅读的设计模式并没有多大用处(除非你只是想了解设计模式)。

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

          • Adapter Pattern - 如果您想定义自己的 API,并“调整”每个特定服务的 API 以适应您自己的需求,这很有用。
          • Template Method Pattern - 如果您的算法基本保持不变,则很有用,但您需要特定部分“可插入”,具体取决于实现

          【讨论】:

            【解决方案5】:

            如果您只使用一种方法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()
                       ......         
                 }
            }
            

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-10-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-11-29
              • 1970-01-01
              相关资源
              最近更新 更多