【问题标题】:How to call a method of a class embedding List<X> and List<Y>, and of Y, from the X elements themselves?如何从 X 元素本身调用嵌入 List<X> 和 List<Y> 以及 Y 的类的方法?
【发布时间】:2021-08-28 19:46:57
【问题描述】:

虽然我很容易找到这个问题的答案,但我还找不到。

假设我有以下课程:

public class Universe
{
    public list<Human> Humans { get; set; }
    public list<Animal> Animals { get; set; }
    public God AlphaOmega { get; set; }
    
    public void UniverseAction()
    { 
        //dosmthg
    }

    public Animal FindAnAnimal()
    {
        //find an animal
    }
}

public class Animal
{
     //many Animal things
    public void AnimalyStuff()
    {
        //DoSmthg
    }
}

public class God
{
    public bool CantTouchThis = true;
}


public class Human
{
    //many Human things
    public void CallingUniverseAction()
    {
        //How to?
    }

    public void CallingAnimalyStuff()
    {
                  
    }
}

请注意,这些名称仅作为示例给出,我可以使用 A、B、C 和 D。

所以我希望所有人都能调用UniverseAction(),这样我就可以使用特定的Human。 我还希望人类能够调用AnimalyStuff(),因此特定的Human 需要访问Universe 中的FindAnAnimal 以检索特定的Animal 并执行AnimalyStuff()

在处理这种需求时,我曾经在Human 的构造函数中传递Universe。虽然我不希望 Human 可以公开 Universe 的所有方法/参数。例如,Human 不应与 AlphaOmega 交互

最合适的方法是什么? 是通过action delegate 传递给构造函数的吗?如果是这样的话,我从来没有使用过动作代表。如果我希望 Human 访问许多方法,我最终不会传递许多委托吗?

【问题讨论】:

  • 你的代码能编译吗? Universe 开头的那两个未命名属性让我感到困惑
  • aha 抱歉,我的代码是虚拟代码,但是是的,我忘了给它们命名。完成
  • 阅读经典的面向对象设计。弄清楚你的名词(类)是什么,你的动词(这些类的方法)是什么。然后阅读依赖注入。这是一种减少类之间耦合的方法。另请阅读关注点分离。正如你所描述的,一切都耦合在一起,很难理解。一旦你根据你的规范编写了一个系统并让它维护一两年,代码很可能无法破译。
  • 您需要在元素上添加一个字段成员,从而知道集合所在的位置。这称为所有者模式。我不确定这个及其名称是否有标准设计模式。例如:public class Animal { List&lt;Animal&gt; /*or Universe here in fact*/ Owner; },当一个项目被添加到列表中时,所有者被设置。它需要一个自定义列表并管理所有内容以保持一致和健壮(自定义列表更好,但不是强制性的)。因此在这里适应宇宙。请参阅@DekuDesu 的回答:我快速浏览了一下,没关系。 CallingAnimalyStuff 到底是什么鬼?

标签: c# list collections associations owner


【解决方案1】:

如果您想将可用信息限制在Human,您可以选择多种方式。

  • 如果您可以只隐藏信息,它仍然存在但无法访问,除非您明确取消隐藏它考虑使用接口来限制可用的成员。

  • 如果您可以接受 Universe 的传递,但 Human 无法访问某些成员,请考虑使用 protected 修饰符来限制对从 Universe 类继承的成员的访问。

  • 如果您可以在构造函数中传递东西(就像您推荐的那样),您可以将任意数量的方法(委托)传递给人类类,这样他们就可以在需要时随时获取信息,但这涉及更复杂的实现(我已经为您完成了下面的大部分工作)

  • 如果您不确定自己想要做什么,并且这需要(对于某些特定的业务要求)以您描述的方式工作 - 考虑研究一般 Object Oriented Programing设计模式。网上有大量的资源可以教你 OOP。我推荐的主要主题是SOLID 原则,它会教给你很多东西并且非常有用。 感谢@flydog57 提到这一点,因为从长远来看这会更有用。

接口
要在视觉上隐藏/抽象信息,除非显式访问(强制转换),您可以实现一个 IUniverse 接口,该接口仅定义您希望公开访问的成员。

// these would be the only accessible members
public interface IUniverse
{
    Animal FindAnAnimal();
    void UniverseAction();
}

public class Universe : IUniverse { ... }

public class Human
{
    private readonly IUniverse universe;
    public Human(IUniverse universe)
    {
        this.universe = universe;
    }
}

使用接口来抽象哪些信息应该可用,这真的很强大!但是,这并不妨碍HumanIUniverse 显式转换为Universe 对象并访问它的其他公共成员。

受保护的修饰符
您可以使用protected 修饰符(和其他几个修饰符)完全删除对不满足某些要求的其他类的信息的访问。例如,protected 修饰符将禁止从任何继承自Universe 的类访问任何protected 成员。请务必查看Access Modifiers,了解有关您可用的其他选项的更多信息。

public class Universe
{
    protected List<Human> Humans { get; set; } = new();
    protected List<Animal> Animals { get; set; } = new();
    protected God AlphaOmega { get; set; }

    public void UniverseAction()
    {
        //dosmthg
        Console.WriteLine(nameof(UniverseAction));
    }

    public Animal FindAnAnimal()
    {
        //find an animal
        Console.WriteLine(nameof(FindAnAnimal));
        return Animals.FirstOrDefault();
    }
}

public class Human
{
    private readonly Universe universe;
    public Human(Universe universe)
    {
        this.universe = universe;
    }
    
    //many Human things
    public void CallingUniverseAction()
    {
        //How to?
        universe.UniverseAction(); // works
        UniverseAction.Humans.Clear(); // no access it's protected
    }

    public void CallingAnimalyStuff()
    {
        var animal = universe.FindAnAnimal(); // works
        UniverseAction.Animals.Clear(); // no access it's protected
        AlphaOmega.Kill(); // no access it's protected
    }
}

通过代表
例如,您可以将委托传递给人类,以避免传递它自己的 Universe 实例。任何方法组通常都可以转换为某种形式的ActionFunc。请务必查看ActionsFuncs,了解有关两者以及如何传递它们的更多信息。

你可以简单地传递这些 super 例如:

public class Universe
{
    public Human CreateHuman()
    {
        var newHuman = new Human(UniverseAction, FindAnAnimal);

        Humans.Add(newHuman);

        return newHuman;
    }    
}

public class Human
{
    private readonly Action universeAction;
    private readonly Func<Animal> animalyStuff;

    public Human(Action universeAction, Func<Animal> animalyStuff)
    {
        this.universeAction= universeAction;
        this.animalyStuff = animalyStuff;
    }
    
    //many Human things
    public void CallingUniverseAction()
    {
        //How to?
        universeAction?.Invoke();
    }

    public void CallingAnimalyStuff()
    {
        var animal = animalyStuff?.Invoke();
    }
}

如果您需要在构造函数(如 20+)中传递大量函数,您还可以实现更健壮但更复杂的系统。在构造函数中传递大量的东西不是可行的模式,但如果你真的想这样做,如果你需要这样做以与遗留系统互操作,它可以工作系统。

以下是使用反射的实现可能是什么样子的简短 sn-p。

public class Universe
{
    protected List<Human> Humans { get; set; } = new();
    protected List<Animal> Animals { get; set; } = new();
    protected God AlphaOmega { get; set; }

    public Human CreateHuman()
    {
        var newHuman = new Human(
            (nameof(FindAnAnimal), (Func<Animal>)FindAnAnimal),
            (nameof(UniverseAction), (Action)UniverseAction)
        );

        Humans.Add(newHuman);

        return newHuman;
    }

    public void UniverseAction()
    {
        //dosmthg
    }

    public Animal FindAnAnimal()
    {
        //find an animal
    }
}

public class Human
{
    //many Human things
    public void CallingUniverseAction()
    {
        Invoke(nameof(Universe.UniverseAction));
    }

    public void CallingAnimalyStuff()
    {
        var animal = Invoke(nameof(Universe.FindAnAnimal));
    }

    public Human(params (string Name, object Delegate)[] Methods)
    {
        foreach (var item in Methods)
        {
            InvokableReferences.Add(item.Name, item.Delegate);
        }
    }

    private Dictionary<string, object> InvokableReferences = new();

    public object Invoke(string DelegateName, params object[] Parameters)
    {
        if (InvokableReferences.ContainsKey(DelegateName))
        {
            object storedDelegate = InvokableReferences[DelegateName];

            var delegateType = storedDelegate.GetType();

            // check for the invoke method
            var invokeMethod = delegateType.GetMethod(nameof(Invoke));

            if (invokeMethod != null)
            {
                // check to see if it's an action or a func
                var methodParams = invokeMethod.GetParameters();

                if (methodParams is null)
                {
                    // since there were no parameters then it is probably an Action or Func<T>
                    return invokeMethod.Invoke(storedDelegate, null);
                }

                // if it requires parameters it's probably a Action<T,..N> or Func<T...N,TResult>
                // make sure we have enough parameters to invoke the method
                if (methodParams.Length == Parameters.Length)
                {
                    return invokeMethod.Invoke(storedDelegate, Parameters);
                }
            }
        }

        // if we failed to find the item return null;
        return default;
    }
}

【讨论】:

    猜你喜欢
    • 2019-01-13
    • 2016-08-17
    • 2022-06-10
    • 2023-03-15
    • 1970-01-01
    • 2013-09-01
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    相关资源
    最近更新 更多