目标
我想在 C# 中实现的一个非常常见的设计如下:一个类,拥有另一个类的多个实例。
为了清楚起见,让我们举个例子,假设一辆拥有四个“轮子”的“汽车”。
以下是我想要满足的约束:
我希望汽车显示其车轮,而不是隐藏它们。 => 汽车类中应该有类似的东西
public Wheel GetWheel(Left, Rear)
我希望汽车能够以某种方式更新其车轮。例如,在汽车类中可以有这样的方法:
public void ChangeTires(TireType)
我不希望其他人能够更新轮子。例如,不应该运行类似的东西:
aCar.GetWheel(Left, Rear).SetTire(someTireType); // Does not compile
这是一个非常非常重复的方案:网络有节点,椅子有靠背,用户有凭证,套接字有IP地址,银行帐户有所有者......
我阅读论坛并询问同事他们是如何做到这一点的。我得到了多个答案。他们在这里。
我的问题是:
解决方案尝试1:使用
internal
有些人建议限制
SetTire
方法对当前程序集的可见性:
class Wheel
{
internal void setTire(TireType someTireType);
}
只有Wheel之外的“同一组件中的文件”才能更新它(更换轮胎)。
但这意味着 Car 和 Wheel 是在同一个程序集中定义的,这通过传递性使得所有内容都在同一个程序集中定义。
另外,仅仅将其限制在同一个程序集中是不够的,我想将其限制在
Car
。
解决方案尝试2:返回副本
有些人建议
GetWheel
方法应该返回一个副本:
class Car
{
public Wheel LeftRearWheel;
public Wheel GetWheel(LeftOrRight, FrontOrRear)
{
return LeftRearWheel.DeepCopy();
}
}
使用这个解决方案,客户端代码不会知道轮子是一个副本。这意味着,每次客户端编码器编写 get 时,他不知道是否可以更新返回的对象。
此外,深度复制应用程序中使用的所有内容可能会引起性能问题。
换句话说:以下代码可以编译,但不执行任何操作:
aCar.GetWheel(Left, Rear).SetTire(someTireType); // Compiles but does nothing
解决方案尝试3:创建一个不可变的轮子
有些人建议这样做:
public class ReadonlyWheel
{
public Tire getTire();
}
internal class Wheel : ReadonlyWheel
{
internal void setTire(Tire);
}
public class Car
{
public Wheel LeftRearWheel;
public ReadonlyWheel GetWheel(LeftOrRight, FrontOrRear)
{
return LeftRearWheel;
}
}
一个变体是声明一个接口
public interface IWheel
{
TireType getTire();
}
internal class Wheel: IWheel
{
public TireType getTire();
internal void setTire(Tire);
}
public class Car
{
public Wheel LeftRearWheel;
public IWheel(LeftOrRight, FrontOrRear)
{
return LeftRearWheel;
}
}
这个解决方案确实实现了目标,但意味着大量的代码重复和额外的复杂性。由于这是一种非常频繁出现的模式,我想避免代码重复。
解决方案尝试4:不在乎
有些人声称这个问题本身超出了“c# 思维方式”。
根据一些人的说法,确保我的用户不会用我的模型做“愚蠢”的事情并不是我所关心的。
一个变体是:“如果发生这种情况,那就意味着你的模型很糟糕”。轮子应该是完全私有的或完全公开的。
我无法让自己这样想。也许有人可以指出任何解释“c# 思维方式”如何实现这种建模的文档。
解决方案尝试5:使用ReadOnlyCollection
如果我想显示的对象是一个容器,则有一个名为
ReadOnlyCollection
的类,据我了解,它结合了解决方案尝试 2 和 3 :它创建一个副本并将其嵌入到只读接口中:
class Car
{
private List<Wheel> myWheels;
public ReadOnlyCollection<Wheel> GetWheels()
{
return myWheels.AsReadOnly();
}
}
它实现了部分目标,即警告用户不要尝试更新集合。但它无法阻止更新。换句话说,以下代码编译后不执行任何操作。
aCar.GetWheels()[0].SetTire(someTireType); // Compile but does nothing
此外,不适用于非收藏品。
我不确定我是否理解这个问题,但我会尝试总结我所理解的内容:
Car
应该容纳一些Wheels
Car
应该有一个 ChangeTires
方法ChangeTires
上呼叫 Wheel
- 只能通过 Car
。如果是这样,一种方法是将公共接口与实现分开。
首先,定义您想要向客户端代码公开的公共 API:
public interface IWheel
{
// Put some members here that DON'T include changing tires...
// Current pressure, size, etc.
}
现在创建
Car
类,并使 IWheel
实现成为 Car
内部的私有嵌套类:
public sealed class Car
{
private readonly Wheel leftRearWheel = new Wheel();
public IWheel LeftRearWheel { get { return leftRearWheel; } }
// Other wheels here...
public void ChangeTires(TireType tireType)
{
leftRearWheel.ChangeTire(tireType);
// Other wheels here...
}
private sealed class Wheel : IWheel
{
public void ChangeTire(TireType tireType)
{
// Change the tire
}
}
}
虽然嵌套
Wheel
类的 ChangeTire
方法被标记为 public
,但它仅对 Car
类可见,因为 Wheel
类是一个 private
类。
因此,
Car
可以在所有ChangeTire
对象上调用Wheel
,但其他类不能。