01-初始
一般在编码过程当中,我们需要通过一个条件进行筛选某一子类的时候,需要用到大量的if else或者Swich,本文章将在一点点的解决这些问题,在需要新的需求的时候,直接添加子类便可实现相关功能。
我们都知道,处理一个问题的时候,使用策略模式可以达到多样化的处理,并且可以在不修改原有Context的情况下进行实现。
举个例子,某游戏中的Vip机制,在一定金钱下的消费是普通会员,没有折扣。当消费到一定程度的时候,这里我们假设为1000金的时候,升级为初级会员,可以享受九折折扣,这里我们直接使用策略模式进行编写代码:
会员基类:IVipStrategy,根据当前消费price计算当前会员制度的折扣价格。
public interface IVipStrategy{float GetExtenditure(float price);}
消费等级的子类,大众会员PublicVipStrategy, 初级会员 JuniorVipStrategy。
public class PublicVipStrategy : IVipStrategy
{
public float GetExtenditure(float price)
{
return price;
}
}
public class JuniorVipStrategy : IVipStrategy
{
public float GetExtenditure(float price)
{
return price * 0.9f;
}
}
上下文环境我们使用消费者PlayerContext。
using System;
public class Consumer
{
private float _price;
private float _totalConsume;
private IVipStrategy _vipStrategy;
public Consumer()
{
_vipStrategy = new PublicVipStrategy();
}
public void Buy(int price)
{
_price = GetConsume(price);
Console.WriteLine("消费了 : " + _price);
_totalConsume += _price;
}
private float GetConsume(int price)
{
if ((0 <= _totalConsume) && (_totalConsume < 1000))
_vipStrategy = new PublicVipStrategy();
else if ((1000 <= _totalConsume) && (_totalConsume < 2000))
_vipStrategy = new JuniorVipStrategy();
return _vipStrategy.GetExtenditure(price);
}
}
然后我们在主函数中调用测试一下
public class ContextView
{
public void Main()
{
Consumer consumer = new Consumer();
consumer.Buy(1000);
consumer.Buy(1000);
}
}
输出结果:
这里我们就可以看到两次购买价值1000金的商品,第一次由于是大众会员不享受折扣优惠,则直接全价购买。购买完毕之后达到了下一等级Vip要求,然后第二次购买的时候则享受9折优惠。接下来我们加入工厂模式将Cosumer中的策略选择提取出来。
public class VipStrategyFactory
{
private static VipStrategyFactory _instance;
public static VipStrategyFactory Instance
{
get
{
if (_instance == null)
_instance = new VipStrategyFactory();
return _instance;
}
}
public IVipStrategy GetVipStrategy(Consumer consumer)
{
float totalConsume = consumer.TotalConsume;
if ((0 <= totalConsume) && (totalConsume < 1000))
return new PublicVipStrategy();
if ((1000 <= totalConsume) && (totalConsume < 2000))
return new JuniorVipStrategy();
return new PublicVipStrategy();
}
}
改写下消费者类
using UnityEngine;
public class Consumer
{
private float _price;
public float TotalConsume { private set; get; }//修改为属性
private IVipStrategy _vipStrategy;
public Consumer()
{
_vipStrategy = new PublicVipStrategy();
}
public void Buy(int price)
{
_price = GetConsume(price);
Debug.Log("消费了 : " + _price);
TotalConsume += _price;
}
private float GetConsume(int price)
{
//由工厂选择策略
_vipStrategy = VipStrategyFactory.Instance.GetVipStrategy(this);
return _vipStrategy.GetExtenditure(price);
}
}
通过工厂我们可以看得到这里我们仍然需要使用到if else进行策略选择,当我们添加新的会员政策的时候,需要编写子类,同时需要修改工厂添加新的条件,在下一篇我们引入C#特性的概念给每一个会员添加自己的消费范围。
02-利用特性限制策略条件
上一篇我们完成了策略模式以及工厂模式的添加,已经成功将策略的选择从消费者中分离出来,但是在工厂中仍然需要使用if else进行策略的筛选,为了解决这个问题我们引入一些新的东西:特性。
特性大家可以理解为和属性差不多的东西,特性本质是一个类,可以给其他的对象赋予属性。这里我不对特性进行过多说明,大家可以参考这篇文章。
在我们这次的例子中是以价格作为筛选策略的条件,也就是所每一个策略都拥有自己的价格区间,那么我们可以利用特性进行区间的限定操作,我们编写一个价格区间的特性类。
[AttributeUsage(AttributeTargets.Class)]//限定由于修饰类
public class PriceAttribute : Attribute
{
public float Max { private set; get; }
public float Min { private set; get; }
public PriceAttribute(int min = -1, int max = 999999)
{
Max = max;
Min = min;
}
}
有了这个我们就可以给每一个策略添加上下限的特性。
[Price(0,1000)]//特性命名中的Attribute可以省略
public class PublicVipStrategy : IVipStrategy
{
public float GetExtenditure(float price)
{
return price;
}
}
[Price(1000,2000)]
public class JuniorVipStrategy : IVipStrategy
{
public float GetExtenditure(float price)
{
return price * 0.9f;
}
}
策略拥有了特性之后我们在工厂中对特性进行读取。
using System;
public class VipStrategyFactory
{
private static VipStrategyFactory _instance;
public static VipStrategyFactory Instance
{
get
{
if (_instance == null)
_instance = new VipStrategyFactory();
return _instance;
}
}
//改写获取方法,利用特性筛选
public IVipStrategy GetVipStrategy(Consumer consumer)
{
var totalConsume = consumer.TotalConsume;
Type[] types =
{
typeof(PublicVipStrategy),
typeof(JuniorVipStrategy)
};
foreach (var type in types)
{
var att = GetAttribute(type);
if (att == null) continue;
if ((att.Min <= totalConsume) && (totalConsume < att.Max))
return (IVipStrategy) Activator.CreateInstance(type);
}
return new PublicVipStrategy();
}
private PriceAttribute GetAttribute(Type t)
{
var atts = t.GetCustomAttributes(typeof(PriceAttribute), true);
if ((atts == null) || (atts.Length <= 0)) return null;
foreach (PriceAttribute att in atts)
{
return att;
}
return null;
}
}
改写完成以后我们可以看到已经去除掉if else的选择了,会根据特性区间进行自动筛选策略。如果需要添加新的Vip策略,直接继承基类,并且赋予特性区间范围即可。如高级会员
[Price(2000, 3000)]
public class SeniorVipStrategy : IVipStrategy
{
public float GetExtenditure(float price)
{
return price*0.8f;
}
}
注意修改工厂中Type数组,添加该会员的类型
Type[] types =
{
typeof(PublicVipStrategy),
typeof(JuniorVipStrategy),
typeof(SeniorVipStrategy)
};
我们看下消费情况
namespace Assets.Demo01.Strategy
{
public class ContextView
{
public void Main()
{
var consumer = new Consumer();
consumer.Buy(1000);
consumer.Buy(1000);
consumer.Buy(1000);
}
}
}

这里我们看到我们准备分三次购买价值1000金的商品,第二次我们是初级会员打折扣了,第三次的时候为什么没有打折扣呢,看程序,第二次我们实际只消费的900元,也就是目前共消费1900元,仍然处于初级会员状态,所以下一次消费仍然是900,我们想要第三次消费可以享受高级会员的优惠,可以适当提高第二次的消费如
namespace Assets.Demo01.Strategy
{
public class ContextView
{
public void Main()
{
var consumer = new Consumer();
consumer.Buy(1000);
consumer.Buy(2000);
consumer.Buy(2800);
}
}
}

第三次成功享受八折优惠。
目前为止已经完成70%,为什么呢,因为我们发现区间的问题很好解决,甚至使用属性就可以。但是新增加的策略需要修改工厂进行添加类型,这样就不符合我们的开闭原则,所以我们在下章解决该问题。
03-利用反射获取策略的子类
总体的解决方法全部使用反射,所以会消耗一定的性能,如果大家又更好的方法欢迎留言讨论
这一篇我们将解决手动编辑Type数组的问题,直接修改工厂类。
using System;
using System.Collections.Generic;
using System.Reflection;
public class VipStrategyFactory
{
private static VipStrategyFactory _instance;
public static VipStrategyFactory Instance
{
get
{
if (_instance == null)
_instance = new VipStrategyFactory();
return _instance;
}
}
//改写获取方法,利用特性筛选
public IVipStrategy GetVipStrategy(Consumer consumer)
{
var totalConsume = consumer.TotalConsume;
var types = GetVipStrategy();
foreach (var type in types)
{
var att = GetAttribute(type);
if (att == null) continue;
if ((att.Min <= totalConsume) && (totalConsume < att.Max))
return (IVipStrategy) Activator.CreateInstance(type);
}
return new PublicVipStrategy();
}
private PriceAttribute GetAttribute(Type t)
{
var atts = t.GetCustomAttributes(typeof(PriceAttribute), true);
if ((atts == null) || (atts.Length <= 0)) return null;
foreach (PriceAttribute att in atts)
return att;
return null;
}
private Type[] GetVipStrategy()
{
var temp = Assembly.GetCallingAssembly().GetTypes();
var typeFullName = typeof(IVipStrategy);
var list = new List<Type>();
foreach (var type in temp)
{
var baseType = type.BaseType;
while (baseType != null)
{
if (baseType == typeFullName)
{
list.Add(type);
break;
}
baseType = baseType.BaseType;
}
}
return list.ToArray();
}
}
这样就可以获取了,运行观察结果
我们发现结果没有进行任何折扣,全部执行的大众会员策略,很明显是我们最后添加的子类获取不正确,下断点看一下
可以看到类型获取到了,那么继续向下走
这里我们观察到,消费策略的基类是Object,并不是我们的接口基类,我又回去看了下反射,发现接口基类是获取不到具体的类型的,所以我将接口的基类更改为抽象类,这时注意子类中的方法需要添加上override
public abstract class IVipStrategy
{
public abstract float GetExtenditure(float price);
}
运行观察效果
成功获取,如果需要添加新的会员制度,只需要编写新的子类就可以实现了。
完成工程:
PriceAttribute.cs
using System;
[AttributeUsage(AttributeTargets.Class)] //限定由于修饰类
public class PriceAttribute : Attribute
{
public PriceAttribute(int min = -1, int max = 999999)
{
Max = max;
Min = min;
}
public float Max { private set; get; }
public float Min { private set; get; }
}
IVipStrategy.cs
public abstract class IVipStrategy
{
public abstract float GetExtenditure(float price);
}
PublicVipStrategy.cs
[Price(0,1000)]
public class PublicVipStrategy : IVipStrategy
{
public override float GetExtenditure(float price)
{
return price;
}
}
JuniorVipStrategy.cs
[Price(1000,2000)]
public class JuniorVipStrategy : IVipStrategy
{
public override float GetExtenditure(float price)
{
return price * 0.9f;
}
}
SeniorVipStrategy .cs
[Price(2000, 3000)]
public class SeniorVipStrategy : IVipStrategy
{
public override float GetExtenditure(float price)
{
return price*0.8f;
}
}
VipStrategyFactory.cs
using System;
using System.Collections.Generic;
using System.Reflection;
public class VipStrategyFactory
{
private static VipStrategyFactory _instance;
public static VipStrategyFactory Instance
{
get
{
if (_instance == null)
_instance = new VipStrategyFactory();
return _instance;
}
}
//改写获取方法,利用特性筛选
public IVipStrategy GetVipStrategy(Consumer consumer)
{
var totalConsume = consumer.TotalConsume;
var types = GetVipStrategy();
foreach (var type in types)
{
var att = GetAttribute(type);
if (att == null) continue;
if ((att.Min <= totalConsume) && (totalConsume < att.Max))
return (IVipStrategy) Activator.CreateInstance(type);
}
return new PublicVipStrategy();
}
private static PriceAttribute GetAttribute(Type t)
{
var atts = t.GetCustomAttributes(typeof(PriceAttribute), true);
if ((atts == null) || (atts.Length <= 0)) return null;
foreach (PriceAttribute att in atts)
return att;
return null;
}
private Type[] GetVipStrategy()
{
var temp = Assembly.GetCallingAssembly().GetTypes();
var typeFullName = typeof(IVipStrategy);
var list = new List<Type>();
foreach (var type in temp)
{
var baseType = type.BaseType;
while (baseType != null)
{
if (baseType == typeFullName)
{
list.Add(type);
break;
}
baseType = baseType.BaseType;
}
}
return list.ToArray();
}
}
Consumer.cs
using UnityEngine;
public class Consumer
{
private float _price;
public float TotalConsume { private set; get; }
private IVipStrategy _vipStrategy;
public Consumer()
{
_vipStrategy = new PublicVipStrategy();
}
public void Buy(int price)
{
_price = GetConsume(price);
Console.WriteLine("消费了 : " + _price);
TotalConsume += _price;
}
private float GetConsume(int price)
{
_vipStrategy = VipStrategyFactory.Instance.GetVipStrategy(this);
return _vipStrategy.GetExtenditure(price);
}
}
ContextView.cs
public class ContextView
{
public void Main()
{
var consumer = new Consumer();
consumer.Buy(1000);
consumer.Buy(2000);
consumer.Buy(2800);
}
}
