我想制作一个游戏,其中我有3种类型的角色,每种类型对于其他类型都有不同的攻击。我希望有一个带抽象方法的抽象类Character(它是一个要求)和3个继承该方法的类,然后为每个类型重载它。问题是我的Character类中的攻击方法采用了一个类型的Character参数,并且我必须在我的子类中使用一个空方法覆盖它,这使得该抽象类和方法的使用真的无用。那么我能做些什么呢?
public abstract class Character
{
public abstract void Attack(Character t);
}
public class A :Character
{
public override void Attack(Character t){}
public void Attack(A x)
{
/*instructions*/
}
public void Attack(B y)
{
/*instructions*/
}
public void Attack(C z)
{
/*instructions*/
}
}
B类和C类等。
我也希望避免这种情况:
public abstract class Character
{
public abstract void Attack(Character c);
}
public class A :Character
{
public override void Attack(Character t)
{
A x = t as A
if (x != null)
{
/*instructions*/
}
B y = t as B
if (y != null)
{
/*instructions*/
}
C z = t as C
if (z != null)
{
/*instructions*/
}
}
}
我希望我的问题尽管我的英语很清楚。
答案 0 :(得分:1)
您可以使用dynamic
中的一些“魔法”实现此调度:
abstract class Character {
public void Attack(Character c) {
((dynamic)this).DoAttack((dynamic)c);
}
}
class A : Character {
public void DoAttack(A a) { Console.WriteLine("A attacks A"); }
public void DoAttack(B b) { Console.WriteLine("A attacks B"); }
public void DoAttack(C c) { Console.WriteLine("A attacks C"); }
}
class B : Character {
public void DoAttack(A a) { Console.WriteLine("B attacks A"); }
public void DoAttack(B b) { Console.WriteLine("B attacks B"); }
public void DoAttack(C c) { Console.WriteLine("B attacks C"); }
}
class C : Character {
public void DoAttack(A a) { Console.WriteLine("C attacks A"); }
public void DoAttack(B b) { Console.WriteLine("C attacks B"); }
public void DoAttack(C c) { Console.WriteLine("C attacks C"); }
}
注意this
和c
到dynamic
的强制转换,这使得运行时可以在不依赖继承结构的情况下找到相关的DoAttack
覆盖。
这种方法的一个优点是您可以根据需要添加新的实现:只要攻击仅限于“有效”类型的对象对,其余代码将继续有效。
这种方法的一个缺点是它不是静态类型的,这意味着它即使在没有处理所需交互的方法的情况下也会编译。您可以通过在DoAttack(Character c)
类本身中提供Character
的“默认”实现来缓解此问题。
答案 1 :(得分:1)
如果您想避免使用dasblinkenlight提供的动态调度解决方案,您可以通过实现访问者模式来解决此问题。我们需要为你的抽象类添加一个新方法。
public abstract class Character
{
public int HP {get;set;}
public abstract void Attack(Character t);
public abstract void Accept(ICharacterVisitor visitor);
}
创建我们的ICharacterVisitor界面。
public interface ICharacterVisitor {
void Visit(A a);
void Visit(B b);
void Visit(C c);
}
让我们继续并在ICharacterVisitor的术语中定义一些攻击角色A的规则。
public class AttackVisitorA : ICharacterVisitor
{
private int _baseDamage;
public AttackVisitorA(int baseDamage){
_baseDamage = baseDamage;
}
public void Visit(A a)
{
// A does normal damage to A.
a.HP -= _baseDamage;
}
public void Visit(B b)
{
// A does double damage to B.
b.HP -= (_baseDamage * 2);
}
public void Visit(C c)
{
// A does half damage to C.
c.HP -= (_baseDamage / 2);
}
}
现在我们可以实现A,B和C字符。我已将执行攻击B和C作为练习给读者。
public class A : Character
{
public override void Accept(ICharacterVisitor visitor)
{
visitor.Visit(this);
}
public override void Attack(Character t)
{
var damage = 15;
t.Accept(new AttackVisitorA(damage));
}
}
public class B : Character
{
public override void Accept(ICharacterVisitor visitor)
{
visitor.Visit(this);
}
public override void Attack(Character t)
{
throw new NotImplementedException();
}
}
public class C : Character
{
public override void Accept(ICharacterVisitor visitor)
{
visitor.Visit(this);
}
public override void Attack(Character t)
{
throw new NotImplementedException();
}
}
现在,我们可以非常简单地对来自其他角色的角色进行攻击:
void Main()
{
var a = new A { HP = 100 };
var b = new B { HP = 100 };
var c = new C { HP = 100 };
a.Attack(a); // stop hitting yourself
a.Attack(b);
a.Attack(c);
Console.WriteLine(a.HP); // 85
Console.WriteLine(b.HP); // 70
Console.WriteLine(c.HP); // 93
}
逻辑与对象层次结构有效地分离。您可以实现一个遵循标准规则的DefaultAttackVisitor,或者为每个角色类型设置一个AttackVisitor,或者可能是一些基于武器或法术的更复杂的规则。无论哪种方式,你最终都得到了一套相当干净且易于交换的逻辑,用于解决类之间的攻击。</ p>
使用Dynamic Dispatch实现访问者将为您提供更多的编译时类型安全性。如果在某些时候你要添加一个新的字符类型D,那么在你实现D的Accept方法时,如果没有确保你的访问者实现被更新为包含D的逻辑,你将无法进行编译。
使用动态调度,如果攻击类型没有添加Attack(D d)
重载,则会针对任何新添加的字符类型触发您的直通方法。
也就是说,动态调度的实现开销要低得多,但是你放弃了一些编译时类型的安全性。您必须评估哪种权衡对您的用例更重要。
答案 2 :(得分:0)
使用已经内置到C#typeof()
这里是一些示例代码。
public class Program
{
public static void Main()
{
A a = new A();
B b = new B();
a.Attack(b);
b.Attack(a);
Console.WriteLine(typeof(A));
Console.WriteLine(typeof(B));
Console.WriteLine(typeof(A) == a.GetType());
Console.WriteLine(typeof(B) == a.GetType());
Console.ReadLine();
}
public abstract class Character
{
public abstract void Attack(Character c);
}
public class A : Character
{
public override void Attack(Character t)
{
if (t.GetType() == typeof(A))
{
Console.WriteLine("A attacked type A");
return;
}
if (t.GetType() == typeof(B))
{
Console.WriteLine("A attacked type B");
return;
}
if (t.GetType() == typeof(C))
{
Console.WriteLine("A attacked type C");
return;
}
}
}
public class B : Character
{
public override void Attack(Character t)
{
if (t.GetType() == typeof(A))
{
Console.WriteLine("B attacked type A");
return;
}
if (t.GetType() == typeof(B))
{
Console.WriteLine("B attacked type B");
return;
}
if (t.GetType() == typeof(C))
{
Console.WriteLine("B attacked type C");
return;
}
}
}
public class C : Character
{
public override void Attack(Character t)
{
if (t.GetType() == typeof(A))
{
Console.WriteLine("C attacked type A");
return;
}
if (t.GetType() == typeof(B))
{
Console.WriteLine("C attacked type B");
return;
}
if (t.GetType() == typeof(C))
{
Console.WriteLine("C attacked type C");
return;
}
}
}
}
答案 3 :(得分:0)
所有这些答案都非常有趣,即使有些概念超出了我的想法。 阅读他们我终于意识到没有真正的问题(不知道为什么我创建了一个,但你知道......),它看起来不是很好但是我选择了以下最简单的解决方案:
我只考虑最常见的过程,并将其作为继承抽象类的所有类的方法。然后我可以为那些具有特定攻击方式的人指定方法。 所以,让我们说当A类角色攻击A类或B类角色的方式相同但攻击C类不同的角色时,我们只需要做多态:
public abstract class Character
{
public abstract void Attack(Character t);
}
public class A :Character
{
public override void Attack(Character t)
{
/*instructions for the attacking of a character of type A or B*/
}
public void Attack(C z)
{
/*instructions for the attacking of a type C character*/
}
}
一般方法(带有字符参数的方法)将用于任何字符类型对象(或从字符继承的对象),除非它是C类对象/字符;在这种情况下,它将被分派到Attack方法,该方法将类型C对象/字符作为参数。
如果所有树类型都以不同的方式受到攻击,那么我们可以随意选择其中一个树类,以便在抽象方法签名之后实现:
public abstract class Character
{
public abstract void Attack(Character t);
}
public class A :Character
{
public override void Attack(Character t)
{
/*instructions for the attacking of a character of type A*/
}
public void Attack(B y)
{
/*instructions for the attacking of a type B character*/
}
public void Attack(C z)
{
/*instructions for the attacking of a type C character*/
}
}
好吧,我不知道这是不是太过分了,但它确实有效。 dynamic
,接口或泛型类型的使用可能是好事,但实际上没有什么可做的。
感谢您的观点,请告诉我一些事情是否正确。
对于任何读者来说,我建议看看Eric Lippert在他的博客上发表的一篇文章的第3部分中提出的关于密切问题(实际存在的问题)的优雅解决方案。点击此链接:https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/
答案 4 :(得分:-1)
既然你知道你只有3种不同的类型,为什么不在Charackter类中为每种类型创建3个抽象方法?
public abstract class Character
{
public abstract void Attack(A a);
public abstract void Attack(B a);
public abstract void Attack(C a);
}