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






4.75/5 (3投票s)
策略模式不一定需要接口 - 另外两种方法。
引言
希望你熟悉 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');
}
};
你根本看不到任何类。只有三个静态只读委托变量,在被调用时返回一个字符串。
实际上,readonly
或 static
也是可选的 - 这种方法非常灵活。
在所有方法中执行所有策略
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
类及其派生类(FileStream
、NetworkStream
、GZipStream
、...)中。
因此,每个接受Stream
参数的方法 - 根据 GoF 的定义 - 都可以被视为一个“使用独立可变算法的客户端”(即流)。 - 委托策略模式
在List<T>.Sort(Comparison<T>)
方法中设计。此外,许多 Linq 扩展接受委托,以启用不同的过滤、排序、分组等策略。