【问题标题】:Interfaces in data oriented design面向数据设计中的接口
【发布时间】:2023-03-20 07:08:01
【问题描述】:

这句话是这样说的:

“编程到接口/抽象,而不是实现”。

我们都知道接口是面向对象编程中解耦的一种方式。就像某个对象履行的合同一样。

但我无法理解的是:

如何在面向数据的设计中对接口/抽象进行编程?

就像调用一些“Drawable”一样,但我现在不知道它是矩形还是圆形,但它实现了接口“Drawable”。

谢谢

【问题讨论】:

  • 您能否详细说明您的困惑?我不确定您想在这里听到什么作为答案。
  • 没有方法,数据本身是无用的。所以没有必要以这种方式讨论这个概念。还是我错过了什么?请详细说明您的问题。

标签: interface abstraction data-oriented-design


【解决方案1】:

这是一个很好的问题。我相信您要问的是如何使用面向数据的设计 (DOD) 实现多态性?

简短回答:您不使用接口来做这件事。这是一种实现多态性的面向对象编程 (OOP) 方式。在 DOD 中,多态性可以通过实体组件系统 (ECS) 模式来实现。

长答案(附示例):

下面是 OOP 中的多态性示例:

public interface Drawable
{
   void Draw();
}

public class Circle: Drawable
{
   public float posX, posY;
   public float radius;

   public void Draw() { /* Draw Circle */ }
}

public class Rectangle: Drawable
{
   public float posX, posY;
   public float width, height;

   public void Draw() { /* Draw Rectangle */ }
}

以下是您如何使用 DOD 和 ECS(伪代码)实现多态性:

public struct Position { public float x, y; }
public struct Circle { public float radius; }
public struct Rectangle { public float width, height; }

public class DrawCirlceSystem
{
    public void OnUpdate()
    {
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Circle))
            .ForEachEntity((Entity entity, Position position, Circle circle) => {
                /* Draw Circle */
            });
    }
}

public class DrawRectangleSystem
{
    public void OnUpdate()
    {
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Rectangle))
            .ForEachEntity((Entity entity, Position position, Rectangle rectangle) => {
                /* Draw Rectangle */
            });
    }
}

如果你有以下数据布局:

Entity 1: [Position, Circle]
Entity 2: [Position, Circle]
Entity 3: [Position, Rectangle]

DrawCircleSystem 只会在实体 1 和 2 上执行,而DrawRectangleSystem 只会在实体 3 上执行。因此,多态性是通过这些系统的可查询性实现的。

以这种方式编程比 OOP 更高效。但除此之外,它还使我们的代码更具可扩展性和可优化性。例如,如果您想实现剔除以便仅实际呈现视图内的实体,我们可以轻松地做到这一点,只需很少的重构工作。我们需要做的就是引入一个新系统,通过向我们要绘制的实体添加或删除一个名为 Visible 的新组件来处理剔除:

public struct Visible { }

public class CircleCullingSystem
{
    public void OnUpdate()
    {
        // Give me all Circle entities that are NOT Visible
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Ciricle))
            .Exclude(typeof(Visible))
            .ForEachEntity((Entity entity, Position position, Circle circle) => { 
                // Add 'Visible' component to entity if it's within view range
            });

        // Give me all Circle entities that are Visible
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Ciricle))
            .FilterBy(typeof(Visible))
            .ForEachEntity((Entity entity, Position position, Circle circle) => { 
                // Remove 'Visible' component from entity if it's out of view range
            });

    }
}

然后我们只需更新 DrawCirlceSystem 中的查询,以便它按 Visible 组件过滤:

public class DrawCirlceSystem
{
    public void OnUpdate()
    {
        // Process all visible circle entities
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Circle))
            .FilterBy(typeof(Visible))
            .ForEachEntity((Entity entity, Position position, Circle circle) => {
                /* Draw Circle */
            });
    }
}

当然,我们需要创建一个类似于CircleCullingSystemRectangleCullingSystem,因为矩形的剔除行为与圆形不同。

【讨论】:

  • 这是一个很好的答案,很抱歉延迟回复您。然而,我并不完全清楚示例中实体/组件的数据布局。是否有组件数组?有人说实体应该只是一个 ID,用作组件数组的键?
  • @krakers 很抱歉迟到了回复。是的,有许多组件。对于 DrawCircleSystetm,它遍历位置数组(保存实体 1 和 2 的位置组件)和圆数组(保存实体 1 和 2 的圆形组件)。使用实体 ID 来获取您想要的组件是一回事,但是对于这个常见的用例,不需要实体 ID,因为两个数组彼此对齐。所以这两个数组的索引 0 将是实体 1 的 Circle 和 Position 组件。
猜你喜欢
  • 1970-01-01
  • 2012-08-21
  • 1970-01-01
  • 1970-01-01
  • 2014-02-19
  • 2010-12-11
  • 2016-10-22
  • 2012-09-07
  • 1970-01-01
相关资源
最近更新 更多