【问题标题】:Handling polymorphisms with large amount of very similar classes in C#在 C# 中处理具有大量非常相似的类的多态性
【发布时间】:2021-10-19 15:49:52
【问题描述】:

我正在使用不同的粒子进行模拟。他们都有类似的 行为,都与其他潜在的粒子存储在同一个列表中 不同的子类型,并被系统的其余部分平等对待。因此,我创建了 Particle 类,并尝试了不同的多态方法。这是我大致想要实现的简化版本,用伪代码编写:

//Movement methods
Randomwalk() {//code}
Diffusion() {//code}
Steered_Motion() {//code}

//Special actions
Pollute() {//code}
Cleanup() {//code}

//Particles
Particle_Type       Movement_Method           Special_Action
Oil                 Diffusion()               Pollute()
Sorbent             Diffusion()               Cleanup()
Packman_random      Randomwalk()              Cleanup()
Packman_steered     Steered_Motion()          Cleanup()
... (50 more rows)

我已经想到了三种方法来实现这一点,但其中两种只对少量粒子类型(

我尝试的第一个方法是定义一个具有不同粒子类型的 Enum,向 Particle 类添加一个 Field 来定义它是什么类型的粒子,并使用 switch 语句使用该字段来确定哪种运动方法和哪种特殊要调用的操作。

public class Particle {
     public enum ParticleType: int {
          Oil = 0,
          Sorbent = 1,
          //...etc
     }
     
     public readonly ParticleType TypeOfParticle;

     Movement() {
          switch(TypeOfParticle) {
               case ParticleType.Oil:
                    Diffusion();
                    break;
               case ParticleType.Sorbent:
                    Diffusion();
                    break;
          }
     }
}

但是,由于某些类型需要额外的字段,这会留下一堆仅由其中一种子类型使用的各种字段。此外,Enum 类型没有任何内置支持要求唯一性,这意味着如果未正确检查 Enum 定义的值分配,两种粒子类型最终可能会在 switch 语句中触发相同的情况。

第二种方法是使用继承。这里我仍然使用相同的 Particle 类,但每个粒子类型都是 Particle 类的子类。

public class Particle {
     virtual public void Movement() {}

     //Define methods
     Randomwalk() {//some code}
     Diffusion() {//some code}
     Steered_Motion() {//some code}
}

public class Oil: Particle {
     override public void Movement() {
          Diffusion();
     }
}

public class Sorbent: Particle {
     override public void Movement() {
          Diffusion();
     }
}

虽然这解决了只有某些类型的字段的问题,但它会创建一个包含大量类的非常长的列表。也许只有我一个人,但管理这个类型列表真的感觉像是一件苦差事。因此,我开始在第三种方法中寻找一些东西。

第三种理想的方法是使用 DataTable 创建表格,将所有不同的粒子类型作为行,将移动方法和特殊动作作为列。运行这些方法将使用委托来完成。不幸的是,DataTables 是在运行时填充的,而我需要在编译时定义类。此外,管理 DataTables 听起来也不是最舒服的任务。

所以我终于来问你会做什么。有没有我可以使用的类似于 DataTable 的类型?我是一名物理和数学专业的学生,​​编程时间不多,所以即使有些东西听起来很基础,我也可能完全错过了。非常感谢任何输入! 提前谢谢你:)

【问题讨论】:

  • 我没有一个全面的答案,但想指出,在您上面的示例中,您将(可能)有一个 DiffusionParticle 类,它具有 Movement() 方法,并且两者都有 OilSorbent 继承自。 -- 既然您非常关心维护,您是否有数百种不同的类型和/或它们是否非常动态? (我的意思是人口是多少)
  • 感谢您的反应。我想要一个大的(50+)和动态的粒子类型列表,这样我就可以快速添加一个新粒子,改变一个粒子的运动方法等等。我不太明白你关于让 Oil 和 Sorbent 粒子从 DiffusionParticle 继承的建议,因为 C# 只允许从一个基类继承,而我希望粒子继承的不仅仅是运动,比如特殊动作。跨度>

标签: c# polymorphism


【解决方案1】:

您也许可以使用委托来代表您的动作和特殊动作。或者 Action,Func 也只是代表。取决于你的需要。 在这个例子中,我在委托中包含了Particle,这样根据粒子的类型,扩散委托的行为可能会有所不同。 然后,您只需使用 ParticleFactory 来正确实例化您的粒子,具体取决于您想要的 ParticleType

        public delegate void SpecialAction(Particle particle);
        public delegate void Movement(Particle particle);

        public static Movement Diffusion = (particle) => { };
        public static Movement RandomWalk = (particle) => { };
        public static Movement Steered_Motion = (particle) => { };

        public static SpecialAction Pollute = (particle) => { };
        public static SpecialAction Cleanup = (particle) => { };

        public enum ParticleType
        {
            Oil = 0,
            Sorbent = 1,
            Packman_random=2,
            Packman_steered=3,
        }

        public class ParticleFactory
        {
            public static Particle BuildParticle(ParticleType particleType) {
                switch(particleType)
                {
                    case ParticleType.Oil:
                        {
                            return new Particle(particleType, Diffusion, Pollute);
                        }
                    case ParticleType.Sorbent:
                        {
                            return new Particle(particleType, Diffusion, Cleanup);
                        }
                    case ParticleType.Packman_random:
                        {
                            return new Particle(particleType, RandomWalk, Cleanup);
                        }
                }
                return null;
            }
        }

        public class Particle {

            private readonly ParticleType particleType;
            private readonly Movement movement;
            private readonly SpecialAction specialAction;


            internal Particle(ParticleType particleType, Movement movement, SpecialAction specialAction)
            {
                this.particleType = particleType;
                this.movement = movement;
                this.specialAction = specialAction;
            }

            public void Move()
            {
                movement.Invoke(this);
            }

            public void Special()
            {
                specialAction.Invoke(this);
            }
        }

编辑 继承 您始终可以将 Particle 子类化以提供一些附加字段。每个子类都有自己的字段。

public class SubParticle : Particle {
    public int OilParticlesRemoved {get; set;}
}

然后工厂只返回一个子粒子而不是粒子。 剩下的就是如何在代码中访问这些字段了……模式匹配可能会有所帮助。

SubParticle some = new SubParticle();  
switch (some)  
{  
  case Particle particle: // Type pattern
    particle.Move();
    break;  
  case SubParticle subParticle:  // Type pattern
    subparticle.OilParticlesRemoved = 10;
    subparticle.Move();
    break;  
}

装饰器 另一种解决方案可能是使用装饰器。在这种情况下,Particle 上的方法应该是虚拟的。工厂返回一个Sorbent 装饰器,它是一个Particle。但是装饰器存储的属性比基本的Particle 更多。

public Particle {
  ...
  public virtual void Move() {
    movement();
  }
  ...
}

public Sorbent : Particle {
  private readonly Particle particle;
  public Sorbent(Particle particle) {
    this.particle = particle;
  }
  public int OilParticlesRemoved {get; set;}

  public override void Move() {
    OilParticlesRemoved = 10;
    particle.Move();
  }
}

如果你想从代码中访问这些属性,你必须区分类型。

【讨论】:

  • 我喜欢粒子工厂的想法!但是,正如我在问题中所描述的,对粒子类型使用一个类,并使用枚举指定类型会留下处理粒子类型特定字段的问题。例如,Sorbent 和 Packman 粒子有一个 int 字段来存储它们已移除的油粒子的数量,而其他粒子没有这个字段。我想避免必须将每个粒子类型的字段添加到每个其他粒子类型。
  • 我添加了继承和装饰器,看看是否有帮助。我喜欢工厂,因为当您查看开关时,它几乎可以作为数据表读取。您快速查看 column1 是类型,column2 是移动,column3 是动作。
【解决方案2】:

我认为你的第二种方法是正确的,特别是如果:

  • 您具有相对固定数量的粒子类型,即您的建模,
  • 您建模的一些粒子类型具有不被其他粒子类型共享的特殊行为,和/或
  • 您要捕获的粒子类型之间存在任何类型的分组或共同性,作为类层次结构的一部分
public abstract class Particle
{
    // standard movement behaviors available to any subclass of Particle
    protected void RandomWalk() {...}
    protected void SteeredMotion() {...}
    protected void Diffusion() {...}

    // standard "special actions" available to subclasses of Particle
    protected void Pollute() {...}
    protected void CleanUp() {...}

    // abstract methods expected to call the above methods
    protected abstract void Move();
    protected abstract void SpecialAction();
}

public class OilParticle : Particle
{
    protected override void Move() { Diffusion(); }
    protected override void SpecialAction() { Pollute(); }
}

public class PackmanRandomParticle : Particle
{
    protected override void Move() { RandomWalk(); }
    protected override void SpecialAction() { CleanUp(); }
}

【讨论】:

  • 我完全同意您所写的内容,而是在我的情况下没有满足第一次假设,其中一个关于具有固定数量的粒子的假设。我不断向模拟添加扩展,而且通常包括新的粒子类型。我想有一个粒子列表,无论出于何种原因,可以在任何时候都快速更改。 span>
【解决方案3】:

我已经提出了自己的解决方案来解决这个问题,并希望与任何可能最终遇到与我类似的问题的人分享。

我创建了一个自定义类,该类用作数据表,确定Particle 的每个子类的所需方法和行为,并让每个粒子在表中搜索自己的行为。

internal class ParticleTable {
     //Fields for devining suptype behaviour
     internal readonly Type SubType;
     internal readonly MotionDelegate MotionMethod;
     internal readonly ActionDelegate ActionMethod;
}

public abstract class Particle {
     //Delegate definitions
     internal delegate void MotionDelegate(Particle self);
     internal delegate void ActionDelegate(Particle self);

     //SubType behaviour definitions
     internal static readonly ImmutableArray<ParticleTable> SubTypes = ImmutableArray.Create(
          new ParticleTable[]{
               new ParticleTable(Particle_Oil, Randomwalk, Pollute);
               new ParticleTable(Particle_Sorbent, Randomwalk, Cleanup);
               //...
          }
     );

     //Update method
     public void ParticleUpdate() {
          //find entry of own type in table
          ParticleTable self_def;
          foreach (ParticleTable sub in SubTypes) {
               if (sub.SubType == this.GetType()) {
                    self_def = sub;
                    break;
               }
          }
          
          //get methods
          MotionDelegate motion = self_def.MotionMethod;
          ActionDelegate action = self_def.ActionMethod;
          
          //execute actions
          motion.Invoke(this);
          action.Invoke(this);
     }
}

//library of subtypes
public class Particle_Oil : Particle {
     //some type specific code
}

public class Particle_Sorbent : Particl {
     //some type specific code
}
//...

这样,通过更改new ParticleTable() 调用中的参数值,可以在“表”Subtypes 中完成任何配置。任何特定子类型的代码都可以像往常一样写在专用类中。

【讨论】:

    猜你喜欢
    • 2020-03-30
    • 2021-05-17
    • 2017-09-23
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 2010-09-12
    • 1970-01-01
    • 2018-02-02
    相关资源
    最近更新 更多