博客地址

01-初始

一般在编码过程当中,我们需要通过一个条件进行筛选某一子类的时候,需要用到大量的if else或者Swich,本文章将在一点点的解决这些问题,在需要新的需求的时候,直接添加子类便可实现相关功能。

我们都知道,处理一个问题的时候,使用策略模式可以达到多样化的处理,并且可以在不修改原有Context的情况下进行实现。

举个例子,某游戏中的Vip机制,在一定金钱下的消费是普通会员,没有折扣。当消费到一定程度的时候,这里我们假设为1000金的时候,升级为初级会员,可以享受九折折扣,这里我们直接使用策略模式进行编写代码:

会员基类:IVipStrategy,根据当前消费price计算当前会员制度的折扣价格。

  1. public interface IVipStrategy
  2. {
  3. float GetExtenditure(float price);
  4. }

消费等级的子类,大众会员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);
    }
}

输出结果:
image.png
这里我们就可以看到两次购买价值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);
        }
    }
}

image.png
这里我们看到我们准备分三次购买价值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);
        }
    }
}

image.png
第三次成功享受八折优惠。
目前为止已经完成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();
    }
}

这样就可以获取了,运行观察结果
image.png
我们发现结果没有进行任何折扣,全部执行的大众会员策略,很明显是我们最后添加的子类获取不正确,下断点看一下
image.png
可以看到类型获取到了,那么继续向下走
image.png
这里我们观察到,消费策略的基类是Object,并不是我们的接口基类,我又回去看了下反射,发现接口基类是获取不到具体的类型的,所以我将接口的基类更改为抽象类,这时注意子类中的方法需要添加上override

public abstract class IVipStrategy
{
    public abstract float GetExtenditure(float price);
}

运行观察效果
image.png
成功获取,如果需要添加新的会员制度,只需要编写新的子类就可以实现了。
完成工程:
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);
    }
}