如果您阅读 Eric Lippert 关于这个主题的文章, 他最终会推荐一种基于规则的方法。
这会将向目标打出一张牌的逻辑推入负责将逻辑结合在一起的类中。让我们尝试一下。(为简洁起见,这将是一个稍微简化的版本。)
首先让我们定义一些我们需要的基本接口:
public interface ITarget
{
string Name { get; }
}
public interface ICard : ITarget
{
}
(显然实际的应用会稍微扩展这些。)
接下来,我们可以编写一个“规则集合”,它将封装和隐藏任何反射,并管理在目标上打牌的规则集合:
public sealed class RulesCollection
{
public void AddRule<TCard, TTarget>(Action<TCard, TTarget> play) where TCard : ICard where TTarget : ITarget
{
string key = keyFromTypes(typeof(TCard), typeof(TTarget));
_rules.Add(key, (card, target) => play((TCard)card, (TTarget)target));
}
public bool IsValidTarget(ICard card, ITarget target)
{
string key = keyFromObjects(card, target);
return _rules.ContainsKey(key);
}
public void PlayCard(ICard card, ITarget target)
{
string key = keyFromObjects(card, target);
if (_rules.TryGetValue(key, out var play))
play(card, target);
else
throw new InvalidOperationException($"{card.Name} cannot be played on {target.Name}");
}
static string keyFromObjects(ICard card, ITarget target)
{
return keyFromTypes(card.GetType(), target.GetType());
}
static string keyFromTypes(Type cardType, Type targetType)
{
return cardType.FullName + "|" + targetType.FullName;
}
delegate void playDelegate(ICard card, ITarget target);
readonly Dictionary<string, playDelegate> _rules = new Dictionary<string, playDelegate>();
}
(此类仅处理两种类型 - 卡片类型和目标类型。但是,您可以轻松扩展它以处理更多类型,从而实现更复杂的交互。)
现在让我们定义一些具体的游戏对象类型:
public sealed class Fireball : ICard
{
public int Damage { get; set; } = 10;
public string Name => "Fireball";
}
public sealed class Hurricane : ICard
{
public string Name => "Hurricane";
public int WindSpeed => 90;
public int Rain => 100;
}
public sealed class Player : ITarget
{
public int Health { get; set; } = 100;
public string Name => "Player";
}
public sealed class Enemy : ITarget
{
public int Health { get; set; } = 50;
public string Name => "Enemy";
}
public sealed class Weather : ITarget
{
public string Name => "Weather";
public int WindSpeed { get; set; } = 5;
public int Rain { get; set; } = 0;
}
现在我们可以创建一个类,它实际上实现了所有必要的卡片规则并使用来 RulesCollection
存储它们:
public sealed class RulesManager
{
public RulesManager()
{
_rules.AddRule<Fireball, Player>(fireballPlayer);
_rules.AddRule<Fireball, Enemy>(fireballEnemy);
_rules.AddRule<Hurricane, Weather>(hurricaneWeather);
}
public bool IsValidTarget(ICard card, ITarget target) => _rules.IsValidTarget(card, target);
public void PlayCard (ICard card, ITarget target) => _rules.PlayCard (card, target);
static void hurricaneWeather(Hurricane hurricane, Weather weather)
{
weather.Rain = hurricane.Rain;
weather.WindSpeed = hurricane.WindSpeed;
Console.WriteLine($"Hurricane set weather wind speed to {hurricane.WindSpeed} and rain to {hurricane.Rain}");
}
static void fireballPlayer(Fireball fireball, Player player)
{
player.Health -= fireball.Damage;
Console.WriteLine($"Fireball damages player for {fireball.Damage}. Player health is now {player.Health}");
}
static void fireballEnemy(Fireball fireball, Enemy enemy)
{
enemy.Health -= fireball.Damage;
Console.WriteLine($"Fireball damages enemy for {fireball.Damage}. Enemy health is now {enemy.Health}");
}
readonly RulesCollection _rules = new RulesCollection();
}
您应该能够看到如何扩展它以涵盖所有卡的交互。
最后我们可以练习一下规则:
public static void Main()
{
var rules = new RulesManager();
var player = new Player();
var fireball = new Fireball();
var hurricane = new Hurricane();
var weather = new Weather();
var enemy = new Enemy();
rules.PlayCard(fireball, player);
rules.PlayCard(fireball, enemy);
rules.PlayCard(hurricane, weather);
Console.WriteLine(rules.IsValidTarget(hurricane, weather)); // Prints "true".
Console.WriteLine(rules.IsValidTarget(hurricane, player)); // Prints "false".
rules.PlayCard(fireball, weather); // Throws exception with message "Fireball cannot be played on Weather".
}
请参阅此 DotNetFiddle 以获取可运行的示例 :
输出结果为:
Fireball damages player for 10. Player health is now 90
Fireball damages enemy for 10. Enemy health is now 40
Hurricane set weather wind speed to 90 and rain to 100
True
False
Unhandled exception. System.InvalidOperationException: Fireball cannot be played on Weather
at RulesCollection.PlayCard(ICard card, ITarget target)
at RulesManager.PlayCard(ICard card, ITarget target)
at Net48Console.Program.Main()