具有与抽象层不同的签名的实现层的问题

时间:2014-07-02 16:01:54

标签: c# abstraction

我第三次遇到这个设计问题,我觉得有一个解决方案,我根本无法弄清楚。我对以前解决它的方式不满意,所以这就是你进来的地方。

假设我正在设计一个与其使用的系统无关的(C#)库。所以我有以下类:

public interface IAction
{
    void Execute();
}

public class Trigger
{
    private List<IAction> actions;

    public void CheckConditions()
    {
        if (AllConditionsMet())
        {
           ExecuteAllActions();
        }
    }

    private void ExecuteAllActions()
    {
        foreach (IAction action in actions)
        {
            action.Execute();
        }
    }

    protected abstract bool AllConditionsMet();
}

public class BigBulkySystem
{
    private List<Trigger> triggers;

    public void CheckAllTriggers()
    {
        foreach (Trigger trigger in triggers)
        {
            trigger.CheckConditions();
        }
    }
}

到目前为止一切顺利。我们在BigBulkySystem中有一个触发器列表,我们经常检查它们的条件。如果满足条件,触发器将负责执行其操作列表。

我现在遇到的问题是我想实现专为Unity设计的图层。在Unity中,您使用coroutines以便能够在继续执行方法之前等待几帧。这正是我想要的Trigger.ExecuteAllActions方法。我想等待每个动作完成执行,然后再转到下一个。为了做到这一点,我需要为Trigger和IAction提供不同的签名。以下是Unity中的情况:

public interface IAction
{
    IEnumerator Execute();
}

public class Trigger
{
    private List<IAction> actions;

    public void CheckConditions()
    {
        if (AllConditionsMet())
        {
            StartCoroutine(ExecuteAllActions());
        }
     }

    public IEnunmerator ExecuteAllActions()
    {
        foreach (IAction action in actions)
        {
            yield return StartCoroutine(action.Execute());
        }
    }
 }

请注意,现在有两种方法返回IEnumerator而不是void,这就是在Unity中定义协同程序的方式。

以下是Unity动作的示例,该动作在3秒延迟后的5秒内将玩家对象从位置(0,0,0)移动到(1,1,1):

public class MovePlayerAction : IAction
{
    public GameObject playerObject;

    public IEnumerator Execute()
    {
        yield return StartCoroutine(WaitFor(3)); // Wait 3 secs

        float animDuration = 5.0f;
        Vector3 initialPos = Vector3.zero;
        Vector3 finalPos = Vector3.one;
        float t = 0;
        while (t < animDuration)
        {
            playerObject.transform.position = Vector3.Lerp(initialPos, finalPos, t / animDuration); // Lerp between initial and final
            yield return null; // Wait a frame
            t += Time.deltaTime;
        }
    }

    private IEnumerator WaitFor(float secs)
    {
        float t = 0;
        while (t < secs)
        {
            yield return null; // Wait a single frame
            t += Time.deltaTime;
        }
    }
}

所以,我的问题是:有没有办法以这样一种方式设计系统,即我可以让非Unity层免受IEnumerator的影响,但Unity层也可以使用它们吗? < / p>

我觉得有某种中间层,但即便如此,我也无法弄清楚IAction如何同时拥有void Execute()IEnumerator Execute()

谢谢!

2 个答案:

答案 0 :(得分:1)

当您使用与所需接口不同的接口的类时,您需要适配器模式。这有几个变种,但对于你的情况,一个简单的变体看起来像这样:

public interface IAction
{
    void Execute();
}

public interface IUnityAction
{
    IEnumerator Execute();
}

public class UnityActionAdapter : IUnityAction
{
    private readonly IAction Action;

    public UnityActionAdapter(IAction action)
    {
        Action = action;
    }

    public IEnumerator Execute()
    {
        Action.Execute();
        yield break; //Or whatever you need to yield here
    }
}

<强>更新

在你的情况下,看起来你正在适应另一个方向,所以:

public class UnityActionAdapter : IAction
{
    private readonly IUnityAction UnityAction;

    public UnityActionAdapter(IAction unityAction)
    {
        UnityAction = action;
    }

    public void Execute()
    {
        StartCoroutine(UnityAction.Execute()); //If I'm correctly understanding how you'd execute an action here
    }
}

然后你有MovePlayerAction实施IUnityAction,而不是直接将MovePlayerAction传递给你的其他图层,而是将其包装在UnityActionAdapter中相反,传递它。

这假设您确实需要在Unity层的界面中维护IEnumerator表单。通过执行以下操作,您可以在不需要适配器的情况下实现相同的目标:

public class MovePlayerAction : IAction
{
    public GameObject playerObject;

    private IEnumerator Coroutine()
    {
        yield return StartCoroutine(WaitFor(3)); // Wait 3 secs

        float animDuration = 5.0f;
        Vector3 initialPos = Vector3.zero;
        Vector3 finalPos = Vector3.one;
        float t = 0;
        while (t < animDuration)
        {
            playerObject.transform.position = Vector3.Lerp(initialPos, finalPos, t / animDuration); // Lerp between initial and final
            yield return null; // Wait a frame
            t += Time.deltaTime;
        }
    }

    public void Execute()
    {
        StartCoroutine(Coroutine());
    }

    private IEnumerator WaitFor(float secs)
    {
        float t = 0;
        while (t < secs)
        {
            yield return null; // Wait a single frame
            t += Time.deltaTime;
        }
    }
}

答案 1 :(得分:0)

如果您使用任务并行库怎么办?

Trigger的示例代码,我看到我们可以实现这个(考虑你的非IEnumerator代码):

public class Trigger
    {
        private readonly List<Task> TaskActions = new List<Task>();

        public void AddAction(IAction action)
        {
            TaskActions.Add(new Task(()=>{action.Execute();})
        }

        public void CheckConditions()
        {
            if (AllConditionsMet())
            {
                ExecuteAllActions();
            }
        }

        private void ExecuteAllActions()
        {
            foreach(Task task in TaskActions)
            {
                task.Start();
            }
            Task.WaitAll(TaskActions);
        }

        protected abstract bool AllConditionsMet();
    }

此致