在方法中转换不同的子类

Cast different subclasses in a method

我想发送、接收和投射 SkillEvent class 的子class,并能够根据其类型获取内部自定义参数。

可能有很多不同的子class具有不同的参数。

有没有办法避免为每种技能编写不同的方法?

class SkillEvent { float timestamp, EventType type }
class GrenadeSkillEvent : SkillEvent { Vector3 target_point}

[ClientRpc]
RpcSendSkillEvent(SkillEvent event){
    eventManager.ExecuteEvent(event);
}

ExecuteEvent(SkillEvent event){
    switch(event.type){
        case GrenadeSkillEvent : 
            GrenadeSkillEvent grenadeEvent = (GrenadeSkillEvent) event;
            float grenadeParam = event.grenadeParam;
            break;

        case OtherSkillEvent : 
            OtherSkillEvent otherEvent = (OtherSkillEvent ) event;
            string stringParam = event.stringParam;
            break;
    }
}

我以为可以那样做,但显然不行。

有没有办法避免为每种技能编写不同的方法?

编辑:

GrenadeSkillEvent 和 OtherSkillEvent 是 SkillEvent 的子项。

它们都有时间戳和类型,我认为我可能需要帮助将事件变量转换为正确的 SkillEvent 子class。

我的问题是它们有相同的方法,比如说执行,但是每种子class需要不同类型的参数。

例如,GrenadeEvent 可能需要目标点、爆炸的大小和伤害。

一个BonusEvent可以给玩家增加一些HP。

A TerrainEvent 可以在玩家附近的重要物体上激活一些自定义动画,等等。

这些技能背后的逻辑都是一样的。

他们都有两种方法:

Simulate(SkillEvent Event) and Render(SkillEvent Event)

但是在 GrenadeSkill 中我需要它是

Simulate(GrenadeSkillEvent Event) and Render(GrenadeSkillEvent Event)

编辑 2 :

networkedPlayer.RpcOnSkillEvent(
    new SkillEvent[] {
        new GrenadeSkillEvent() {
            timestamp = state.timestamp,
            id = 0,
            point = target
        } 
    }
);

编辑 3:

var dictionary = new Dictionary<Type, Action<SkillEvent>> {
    {
        typeof(GrenadeSkillEvent), (SkillEvent e) => {
            Debug.Log("GrenadeSkill OnConfirm point"+ ((GrenadeSkillEvent)e).point);
        }
    }
};

public override void OnConfirm(SkillEvent skillEvent) {

    if (skillEvent == null) return;

    Debug.Log(skillEvent.GetType());
    dictionary[skillEvent.GetType()](skillEvent);
}

如果您使用 C# 7 并且 GrenadeSkillEventOtherSkillEventSkillEvent 派生,您可以使用模式匹配:

switch (@event) //@event is of SkillEvent type
{
    case GrenadeSkillEvent gse:
        float grenadeParam = gse.grenadeParam;
        break;
    case OtherSkillEvent ose:
        string stringParam = ose.stringParam;
        break;
    case null:
        //null handling
        break;
    default:
        //other types
        break;
}

如果没有,你可以使用字典:

var dictionary = new Dictionary<Type, Action<SkillEvent>>
    {
        {
            typeof(GrenadeSkillEvent), e => 
            {
                float grenadeParam = ((GrenadeSkillEvent)e).grenadeParam;
                //some logic
            }
        },
        {
            typeof(OtherSkillEvent), e => 
            {
                string stringParam = ((OtherSkillEvent)e).stringParam;
                //some logic
            }
        }
    }

if (@event == null)
{
    //null handling
}
else
{
    dictionary[@event.GetType()](@event);
}

或者,一种更面向对象的方法是在 SkillEvent 中创建一个抽象方法,并在所有具体 类 中覆盖它,但您需要具有通用的 return 类型和参数.

最好的方法是在 class、SkillEvent 的顶部写一个通用的 getter 方法,然后在 class 中重写这个方法是特殊的.由于您似乎需要不同的类型,我认为您可以使用一种方法来执行您在 class

中参数化的方法

这是一个包含您当前代码的示例:

class SkillEvent { 

  float timestamp; 

  EventType type ;

  public virtual void executeEvent(){ //handles the general case for the execute funciton
    execute(timestamp);
  }
}

class GrenadeSkillEvent : SkillEvent { //overriden to pass target point into event execution

  Vector3 target_point;

  public override void executeEvent(){
    execute(target_point);
  }
}

class BonusEvent : SkillEvent { 

  int bonus_health;

  public override void executeEvent(){/overriden to pass bonus health into event
    execute(bonus_health);
  }
}

class TerrainEvent : SkillEvent { 

  GameObject obj;

  public override void executeEvent(){//overriden to play animation before event is executed
    animation(obj);
    execute();
  }
}

[ClientRpc]
RpcSendSkillEvent(SkillEvent event){
    eventManager.ExecuteEvent(event);
}

ExecuteEvent(SkillEvent event){
    event.executeEvent();
}

简短的回答是,您可以通过制作自己的类型来完成您所要求的,但您真的不想那样做。这种代码正是多态性旨在防止的。要理解原因,假设您在代码中的 5 或 10 个不同位置使用了此模式——每个位置都有一个基于 Type 的 switch 语句,以及在每种情况下为 运行 的不同代码。现在假设您要引入一种新的 SkillEvent 类型:您必须找到代码中打开 Type 的每个地方并添加更多代码。您突然打开所有已经过 QA 的代码,并且必须再次对其进行 QA。另外,如果您忘记了其中一个景点怎么办?多么痛苦,不得不去编辑所有这些不同的地方只是为了添加一个新的具体实例 class.

现在考虑一下正确的做法:对于这 10 个地方中的每一个,您都在基础 class 中创建一个抽象方法,然后在每个具体的 class 中重写它.抽象方法创建一个契约,每个具体 class 都可以以自己的方式实现。

在您的具体示例中,您说要从不同的具体 classes 中提取不同的参数。你打算用这些参数做什么?是创建一个描述对象的字符串吗?是将这些参数传递给您即将进行的服务调用吗?想想你想要实现的目标。我假设是后一种情况,因为这是您实际需要单独参数的地方。所以,让你在基础 class 中定义的抽象方法成为

abstract Map<String, Object> getParameters();

或者

abstract void addParameters(Map<String, Object>);

现在,具体的 classes 创建一个映射并用它们自己的参数填充它,但是您的调用方法不必知道有关此方法实现的任何信息。这就是你希望 GrenadeSkillEvent 的知识所在的地方,无论如何,在 class 中。您不希望其他 class 知道 GrenadeSkillEvent 的任何细节。毕竟,您可能会决定在将来更改其实现,使其具有新参数。在这一点上,您不需要记住去寻找其中包含此开关的 callXYZServiceCall 代码。 (更糟糕的是,不是你添加了新的参数,而是项目中的另一个工程师,他甚至不知道要担心这个 switch 语句。)

Idk,这只是想法。 (不知道,我使用相同的方法)

也许好的解决方案是使用技能数据库,并发送带有字符串参数的 RPC 而不是可序列化 类(您的 SkillEvent

因此,使用这个技能数据库,您可以根据需要使用字符串参数执行代码。

你可以把技能id和参数放到字符串中,发送rpc,接收命令,解析字符串,通过id技能和参数执行代码。

  1. 将技能参数和技能 ID 放入字符串
  2. 使用此字符串发送 RPC
  3. 接收 RPC
  4. 将字符串解析为您的格式(在此示例中为 jsonObject)
  5. 通过id_skill查找使用的技能并使用参数执行代码
  6. 盈利!

解析或创建字符串的有用工具,将是一些 JSON 解析器(serializator..idk)

也许你会问我:"why strings and json?"

我回答你:"because it's flexible!"

示例:

void UseGrenade(){ // when some player using grenade skill.
    JsonObject json = new JsonObject();
    json.addField("id_skill", id); // id of grenade skill
    json.addField("timestamp", 1.5f);
    json.addField("position", targetPosition);
    SendRpcSkill(json.Print()); // converting json to string and sending this string
}

例如执行手榴弹的参数:

 // it is pseudo json for example
{
    id_skill: 1, // id of grenade
    timestamp: 1.5,
    position: {1,21,3}
}

或其他一些技能:

// it is pseudo json for example
{
    id_skill: 2, // id of some skill
    timestamp: 1.8,     
    msg:"rain of fishes for cats!",
    // ... and other params
}

并执行此技能:

[ClientRpc]
RpcSendSkill(string skillData){
    JsonObject json = new JsonObject(skillData); //converting string to json
    int idskill = json["id_skill"]; // get id of used skill
    mySkillsDataBase[idskill].Execute(json); // find skill and execute code
}

你游戏中的某处:

public class GrenadeSkill: BaseSkill{

    public override void Execute(JsonObject json){
       float timeStamp = (int)json["timestamp"];        
       Vector3 targetPosition = (Vector3)json["position"];
       // ...
       // and you grenade logic
    }
}

希望你能理解我的想法。