问题描述
我虽然很容易找到这个问题的答案,但我还没有找到。
假设我有以下课程:
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
需要访问 FindAnAnimal
中的 Universe
以检索特定的 Animal
并执行AnimalyStuff()
有一段时间在处理这种需求时,我曾经在 Universe
的构造函数中传递 Human
。虽然我不希望 Human 可以公开 Universe 的所有方法/参数。例如,Human
不应与 AlphaOmega
最合适的方法是什么?
是通过我将在构造函数中传递的 action delegate
吗?如果是这样,我从未使用过动作委托。如果我想让 Human 访问很多方法,我最终不会传递很多委托吗?
解决方法
如果您想将可用信息限制为 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;
}
}
使用接口来抽象哪些信息应该在哪些地方可用是非常强大的!但是,这并不能阻止 Human
将 IUniverse
显式转换为 Universe
对象并访问它的其他公共成员。
受保护的修饰符
您可以使用 protected
修饰符(和其他几个修饰符)完全删除对不满足某些要求的其他类的信息的访问。例如,protected
修饰符将禁止从任何不继承自 protected
的类访问任何 Universe
成员。请务必查看 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
实例。任何方法组通常都可以转换为某种形式的 Action
或 Func
。请务必查看 Actions 和 Funcs 以获取有关两者以及如何传递它们的更多信息。
您可以简单地将这些传递给 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+),您还可以实现更健壮但更复杂的系统。在构造函数中传递大量内容不是一种可行的模式,但如果您真的想这样做,如果您要求这样做以与遗留系统互操作,它可以工作系统。
这里有一个简短的片段,展示了使用反射的实现可能是什么样子。
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;
}
}