65.9K
CodeProject 正在变化。 阅读更多。
Home

设计模式:策略(替代方案)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (3投票s)

2015年10月23日

CPOL

2分钟阅读

viewsIcon

13589

downloadIcon

95

策略模式不一定需要接口 - 另外两种方法。

引言

希望你熟悉 Volkan Paksoy 在 原始文章 中定义和解释的策略模式。

定义一系列算法,将每个算法封装起来,并使它们可以互换。策略模式允许算法独立于使用它的客户端而变化。

我想在文章中补充一点,除了实现接口之外,还有另外两种实现策略模式的方法。

这两种方法是:1) 重写抽象基类 2) 使用具有适当签名的委托。

我只是简单地展示代码,而没有进行详细的解释,因为它的“工作部分”已经在基础文章中介绍过了。 
我只是用不同的方式包装了它,使用了与接口不同的 C# 语言特性。
每种方法:接口 / 基类 / 委托 都有其自身的优点和缺点,因此了解它们全部可能很有用。

通过重写基类实现策略模式

abstract class IpCheckBase {
   //abstract Strategy-baseclasses can provide some general logic and Infrastructure to its Derivates

   public string GetIp() {     //  provide general logic to Derivates: create / destroy HttpClient
      using (var client = new HttpClient()) {
         return GetIpCore(client);
      }
   }
   protected abstract string GetIpCore(HttpClient client); // placeholder for the specific logic
}

class DynDnsIPCheckStrategy : IpCheckBase {
   protected override string GetIpCore(HttpClient client) {
      client.BaseAddress = new Uri("http://checkip.dyndns.org/");
      HttpResponseMessage response = client.GetAsync("").Result;
      return HelperMethods.ExtractIPAddress(response.Content.ReadAsStringAsync().Result);
   }
}

class AwsIPCheckStrategy : IpCheckBase {
   protected override string GetIpCore(HttpClient client) {
      client.BaseAddress = new Uri("http://checkip.amazonaws.com/");
      string result = client.GetStringAsync("").Result;
      return result.TrimEnd('\n');
   }
}

class CustomIpCheckStrategy : IpCheckBase {
   protected override string GetIpCore(HttpClient client) {
      client.BaseAddress = new Uri("http://check-ip.herokuapp.com/");
      client.DefaultRequestHeaders.Accept.Clear();
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

      HttpResponseMessage response = client.GetAsync("").Result;
      string json = response.Content.ReadAsStringAsync().Result;
      dynamic ip = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
      string result = ip.ipAddress;
      return result;
   }
}

你看到 4 个类:第一个是抽象的,包含每个派生类都需要的一般逻辑。派生类没有自己的公共成员 - 它们只是用特定的策略覆盖了占位符。
注意:重写策略架构也可以更简单,也可以更复杂。

编辑:正如 marcus obrien 在 他的评论 中指出的,基类不一定需要是抽象的。即使是带有虚拟重写的类派生,有时也可以被视为或用作基于策略的替代方案。

通过委托实现策略模式

/*
   * strategy-delegates can be located everywhere, can be static or not, or anonymous. Only a matching signature is obligatory
   */
public readonly static Func<string> DynDnsIPCheckStrategy = () => {
   using (var client = new HttpClient()) {
      client.BaseAddress = new Uri("http://checkip.dyndns.org/");
      HttpResponseMessage response = client.GetAsync("").Result;
      return HelperMethods.ExtractIPAddress(response.Content.ReadAsStringAsync().Result);
   }
};
public readonly static Func<string> CustomIpCheckStrategy = () => {
   using (var client = new HttpClient()) {
      client.BaseAddress = new Uri("http://check-ip.herokuapp.com/");
      client.DefaultRequestHeaders.Accept.Clear();
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

      HttpResponseMessage response = client.GetAsync("").Result;
      string json = response.Content.ReadAsStringAsync().Result;
      dynamic ip = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
      string result = ip.ipAddress;
      return result;
   }
};
public readonly static Func<string> AwsIPCheckStrategy = () => {
   using (var client = new HttpClient()) {
      client.BaseAddress = new Uri("http://checkip.amazonaws.com/");
      string result = client.GetStringAsync("").Result;
      return result.TrimEnd('\n');
   }
};

你根本看不到任何类。只有三个静态只读委托变量,在被调用时返回一个字符串。
实际上,readonlystatic 也是可选的 - 这种方法非常灵活。

在所有方法中执行所有策略

static class StrategyTester {

   static public void ExecuteAll() {
      ExecuteByInterface();
      ExecuteByDelegate();
      ExecuteByOverride();
      Console.ReadKey();
   }

   static private void ExecuteByInterface() {
      Console.WriteLine("ExecuteByInterface");
      IIpCheckStrategy ipChecker;
      ipChecker = new DynDnsIPCheckStrategy();
      Console.WriteLine(ipChecker.GetExternalIp());
      ipChecker = new AwsIPCheckStrategy();
      Console.WriteLine(ipChecker.GetExternalIp());
      ipChecker = new CustomIpCheckStrategy();
      Console.WriteLine(ipChecker.GetExternalIp());
   }

   static private void ExecuteByDelegate() {
      Console.WriteLine("\nExecuteByDelegate");
      var dlg = DelegateStrategies.DynDnsIPCheckStrategy;
      Console.WriteLine(dlg.Invoke());
      dlg = DelegateStrategies.AwsIPCheckStrategy;
      Console.WriteLine(dlg());
      Console.WriteLine(DelegateStrategies.CustomIpCheckStrategy());
   }

   static private void ExecuteByOverride() {
      Console.WriteLine("\nExecuteByOverride");
      var ipChecks = new OverrideStrategy.IpCheckBase[] {
         new OverrideStrategy.DynDnsIPCheckStrategy(),
         new OverrideStrategy.AwsIPCheckStrategy(),
         new OverrideStrategy.CustomIpCheckStrategy() };
      foreach (var checker in ipChecks) Console.WriteLine(checker.GetIp());
   }
}

我认为这不言而喻。请注意,ExecuteByInterface() 代码访问了基础文章的代码(3 个接口策略)。

框架中的策略模式方法 - 示例

  • 通过接口实现策略模式
    最常用的通过接口实现策略模式可能是 IEnumerable<T> 接口,每当我们使用 foreach(...) 循环集合时都会使用它。
  • 重写策略模式
    抽象重写策略模式存在于 Stream 类及其派生类(FileStreamNetworkStreamGZipStream、...)中。
    因此,每个接受 Stream 参数的方法 - 根据 GoF 的定义 - 都可以被视为一个“使用独立可变算法的客户端”(即流)。
  • 委托策略模式
    List<T>.Sort(Comparison<T>) 方法中设计。此外,许多 Linq 扩展接受委托,以启用不同的过滤、排序、分组等策略。
© . All rights reserved.