前言
不知道各位有没有玩过三国杀,这里将用策略模式去模拟三国杀游戏,各位看完一定会感觉很生动形象,最后就是比较策略模式,状态模式,桥接模式什么同异。
1.策略模式模拟三国杀
三国杀的手牌功能基本相似的情况下,使用分支语言将带来复杂和难以维护的问题,所以每个玩家手里一堆牌,可以用策略模式表达效果,相当于目标对象持有很多个策略对象,只不过网上给出的例子都是目标对象只持有一个策略对象,还可以自主性的改变使用的策略对象,这里将持有多个策略对象,接下来用代码模拟一下:
public abstract class IBasePoker
{
private int pokerValue;//牌大小 0是A黑桃....,4表示2黑桃
public int PokerValue
{
get { return pokerValue; }
set { pokerValue = value; }
}
public abstract void SendMessage(int uid,int index, byte[] target);
}
public class KillPoker : IBasePoker//杀
{
public override void SendMessage(int uid, int index,byte[] target)
{
Console.WriteLine("玩家" + uid + "使用杀牌索引:" + index + "影响对象" + target);
}
}
public class FlashPoker : IBasePoker//闪
{
public override void SendMessage(int uid, int index, byte[] target)
{
Console.WriteLine("玩家" + uid + "使用闪牌索引:" + index + "影响对象" + target);
}
}
public class PeachPoker : IBasePoker//桃
{
public override void SendMessage(int uid, int index, byte[] target)
{
Console.WriteLine("玩家" + uid + "使用桃牌索引:" + index + "影响对象" + target);
}
}
public class BreakoutPoker : IBasePoker//南蛮入侵
{
public override void SendMessage(int uid, int index, byte[] target)
{
Console.WriteLine("玩家" + uid + "使用南蛮入侵索引:" + index + "影响对象" + target);
}
}
public class InvalidPoker : IBasePoker//无懈可击
{
public override void SendMessage(int uid, int index, byte[] target)
{
Console.WriteLine("玩家" + uid + "使用无懈可击索引:" + index + "影响对象" + target);
}
}
public class Player
{
int userId;
List<IBasePoker> basePokers;
public Player()
{
basePokers = new List<IBasePoker>();
}
public void PlayPoker(int index,byte[] target)
{
try
{
basePokers[index].SendMessage(userId, index, target);
basePokers.RemoveAt(index);
}
catch (Exception e)
{
}
}
public void SendPoker(IBasePoker basePoker)
{
basePokers.Add(basePoker);
}
}
使用不同牌就会取用不同的策略对象,虽然以上代码可能和网上提供的部分demo小有不同,但还是属于策略模式的范畴。接下来考虑有没有其他的方式去实现相同的效果。
2.换个方式实现功能
新增一个牌就要去多实现一个策略类,可能会有点繁琐,而且也会导致定义的子类过多。幸好你们遇到此篇文章了,接下来要进行一段猛如虎的骚操作了,我们可以使用委托列表去替换策略模式,直接在玩家类里把出牌的功能函数定义出来,然后自己添加自己可以执行的动作,具体代码如下:
public class Player
{
int userId;
List<Action<int,byte[]>> pokerFunctions;
public Player()
{
pokerFunctions = new List<Action<int, byte[]>>();
}
public void PlayPoker(int index,byte[] target)//出牌
{
try
{
pokerFunctions[index].Invoke(index, target);
pokerFunctions.RemoveAt(index);
}
catch (Exception e)
{
}
}
public void SendPoker(Action<int, byte[]> pokerFunction)//获得牌
{
pokerFunctions.Add(pokerFunction);
}
public void SendKillMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用杀牌索引:" + index + "影响对象" + target);
}
public void SendFlashMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用闪牌索引:" + index + "影响对象" + target);
}
public void SendPeachMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用桃牌索引:" + index + "影响对象" + target);
}
public void SendInvalidMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用无懈可击索引:" + index + "影响对象" + target);
}
public void SendBreakoutMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用南蛮入侵索引:" + index + "影响对象" + target);
}
}
可以看到把牌的功能全部集中到玩家类中,正常情况发牌的函数都是new策略对象添加到列表里,这里就不一样了,直接告诉它玩家可以执行什么功能了,把自己的函数添加到委托列表里,具体代码如下:
Player player = new Player();
player.SendPoker(player.SendBreakoutMessage);//获得执行南蛮入侵的权限
player.SendPoker(player.SendKillMessage);//获得执行杀的权限
player.SendPoker(player.SendKillMessage);//获得执行杀的权限
你们可能感觉很奇怪,好像缺少点什么?恍然大悟缺少了一些基本参数,比如牌的参数应该有花色和牌的大小,上面抽象类代码里都有这些参数,凭什么在这里不加上去,如下图所示:
幸好我埋伏了一手,之后闷声发大财。不对不对(没有在斗地主..),其实我们可以用字典去替换列表,Key表示需要的参数,Value就是出牌的委托,调整后的代码如下:
public enum PokerType
{
basePoker,//基本牌
equipPoker,//装备牌
skillPoker,//锦囊牌
}
public struct PokerInfo
{
public int index;//唯一索引
public short card;//牌面比如♠A
public byte type;//牌功能类型,比如0表示杀牌,1表是闪牌
public PokerType pokerType;//牌类型
}
public class Player
{
int userId;
Dictionary<PokerInfo,Action<int,byte[]>> pokerInfos;
public Player()
{
pokerInfos = new Dictionary<PokerInfo, Action<int, byte[]>> { };
}
public void PlayPoker(int index,byte[] target)
{
try
{
foreach (KeyValuePair<PokerInfo, Action<int, byte[]>> value in pokerInfos)
{
if (value.Key.index == index)
{
value.Value.Invoke(index, target);
pokerInfos.Remove(value.Key);
break;
}
}
}
catch (Exception e)
{
}
}
public Action<int, byte[]> ParsePokerFunction(byte type)
{
switch (type)
{
case 0:
return SendKillMessage;
case 1:
return SendFlashMessage;
case 2:
return SendPeachMessage;
case 3:
return SendInvalidMessage;
case 4:
return SendBreakoutMessage;
default:
Console.WriteLine("解析不出索引为" + type + "的功能函数");
return default;
}
}
public void SendPoker(PokerInfo pokerInfo,Action<int, byte[]> pokerFunction)
{
pokerInfos.Add(pokerInfo,pokerFunction);
}
public void SendKillMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用杀牌索引:" + index + "影响对象" + target);
}
public void SendFlashMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用闪牌索引:" + index + "影响对象" + target);
}
public void SendPeachMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用桃牌索引:" + index + "影响对象" + target);
}
public void SendInvalidMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用无懈可击索引:" + index + "影响对象" + target);
}
public void SendBreakoutMessage(int index, byte[] target)
{
Console.WriteLine("玩家" + userId + "使用南蛮入侵索引:" + index + "影响对象" + target);
}
}
pokerInfo结构体应该是游戏服务器通过发牌协议发过来的,可以通过里面的type去判断具体使用那个函数存储到字典,这样用户拥有手牌的功能就完全可以搞定了,具体代码如下:
player.SendPoker(pokerInfo, player.ParsePokerFunction(pokerInfo.type));
在下也不太清楚有没有这个设计模式,或者说这个东西能不能算是设计模式,但是我还是准备把它叫做丁氏螺旋升天设计模式😂。
最后总结之前,先给出桥接模式和状态模式的传说门,然后说一下它们的同异。
桥接模式:https://blog.csdn.net/m0_37920739/article/details/104363943
状态模式:https://blog.csdn.net/m0_37920739/article/details/104525017
总结
1.桥接模式、状态模式、策略模式同异
它们设计方式大同小异,但是目的确实不同。桥接模式是为了整个模块功能抽离出来,可以实现插拔或替换整个功能模块的目的。状态模式和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变对象行为的,而策略模式是通过本身决策来改变对象行为的。所谓的状态转移,是指在运行过程中由于一些条件发生改变而使得对象行为发生改变和对象状态的切换,注意必须要是在运行过程中。
2.策略模式优缺点
优点:1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
3.策略模式使用场景
1.三国杀游戏玩家拥有卡牌的逻辑
2.游戏分享链接时,具体的分享模式。