OOP:在一个类中为"实体"设置"动作"逻辑但在另一个类中调用它?

She*_*eto -1 c# oop artificial-intelligence

建立一个AI结构我在接口处遇到了一个紧急问题,对于平庸的评论感到抱歉:

//Probability is just a class I've made to represent (you guessed it) probability

public interface IAction
{
    /// <summary>
    /// Returns a Dictionary of possible future states of the IEntity parameter and their estimated probability
    /// </summary>
    Dictionary<IEntity, Probability> EstimatePossibleOutcomeSpectrum(IEntity entity);
    /// <summary>
    /// Have the IEntity parameter "do" this action
    /// </summary>
    void Do(IEntity entity);
}

public interface IEntity
{
}
Run Code Online (Sandbox Code Playgroud)

问题在于常识要求实体执行动作,而不是相反,显然我可以重命名方法,因此它具有语法意义,但最终我想将"Do"移动到IEntity中,但是当它涉及到实现接口我不知道如何让"实体"做"行动"而不是简单地"传递接力棒",具体如下:

public class EntityExample
{
    /// <summary>
    /// Returns a Dictionary of estimated future states after "doing" the IAction parameter
    /// </summary>
    Dictionary<IEntity, Probability> EstimatePossibleOutcomeSpectrum(IAction action)
    {
        action.[Some method or a Action<Entity> call](this);
    }
    /// <summary>
    /// "do" the Action paramater
    /// </summary>
    void Do(IAction action)
    {
        action.[Some method or a Action<Entity> call](this);
    }
}
Run Code Online (Sandbox Code Playgroud)

即使不是更糟,感觉也一样.

对于这样的事情,最好的编码实践是什么?我承认我最初的方法在功能上很好,但这感觉就像使用gotos一样糟糕.

Eri*_*ert 7

问题在于常识要求实体执行操作,而不是反过来,

由于我不知道"实体"是什么,我的常识告诉我实体是否执行操作,或者操作是否消耗实体.

在这种情况下,"实体"和"行动"都是名词,所以我认为他们不应该同时成为班级.

对于这样的事情,最好的编码实践是什么?我承认我最初的方法在功能上很好,但这感觉就像使用gotos一样糟糕.

不要担心什么是最"纯粹的OOP"解决方案,特别是如果它意味着制作奇怪,不优雅或低效的代码.相信OOP本身就是目的,即使它使编码变得更加昂贵和困难,我称之为"对象幸福病".编写OOP代码不应该让你开心; 有效地使用OOP来降低成本并提高质量应该让你开心.如果OOP不是正确的工具和函数编程,请使用函数式编程.

相反,请担心您希望代码从调用者方面看起来如何.API是一种被其他开发人员使用的机器,因此请确保您的API设计时考虑到他们的想法,感受和需求,并且需要处于最前沿,而不是"大多数OOP". 你希望你的来电者写什么样的代码?总是从那开始.


说到函数式编程,您可以考虑的方法是充分发挥功能,并将离散概率分布表示为monad.

想想其他一些单子.A IEnumerable<T>只是T的序列,可能是空的.A Nullable<T>在逻辑上与序列相同,仅限于一个或零个元素.An IObservable<T>是一个T序列,被推向你而不是被拉出来.A Task<T>在逻辑上是一个只有一个值的可观察对象.

并非每个monad在逻辑上都是一个序列,但概率分布是.概率分布Distribution<T>T的无限随机序列,其输出符合特定分布.

如果你走这条路线,你会发现许多操作自然而然地从序列运算符中消失了.条件分布(如"滚动两个骰子但丢弃双打")只是Where.P(A | B)(A给定B的概率)是Func<B, Probability<A>>,这意味着我们可以应用一元绑定操作者条件概率以链的条件概率一起,从而贝叶斯推理变得简单.等等.这是一种简单但极其强大的技术.

尝试编写一些实现

interface IDistribution<T> 
{
  T Sample();
  IDistribution<U> Bind<U>(Func<T, IDistribution<U>> f);
  // f is probability of U given a T
}
Run Code Online (Sandbox Code Playgroud)

并看看它在哪里得到你.

  • @Shefeto:你没跟我来这儿.您的问题是如何重新架构此代码,因为您遇到了双重调度问题.我是说如果使用monadic类型构建工作流程,*无论你如何解决双重调度问题*都无关紧要; 你有*灵活性*可以随心所欲地解决它,并*在不修改工作流代码的情况下*改变*实现.传统的OOP解决方案将是访问者模式,传统的功能解决方案将是可以部分评估的外部方法.无所谓.获得正确的类型代数. (3认同)
  • @Shefeto:**实体和行动都不应该做概率推理**.你说一个实体是"一个独立存在的东西".好吧,我的烤面包机是一个独立存在的东西,**我从来没有要求我的烤面包机计算一个震动的可能性,因为我把刀插入其中**.我也没有问过任何刀具的问题,也没有问过将一件事情粘在另一件事上的"行动"的问题.如果你想建模条件概率分布,那么*为那个*写一个类. (2认同)