C# 对象返回的内部组件

C# object's returning internal components

提问人:mgueydan 提问时间:9/23/2023 最后编辑:Andrew Mortonmgueydan 更新时间:9/24/2023 访问量:64

问:

目标

我想在 C# 中实现的一个非常循环的设计如下:一个类,它拥有另一个类的多个实例。

为了清楚起见,让我们举个例子,假设一辆拥有四个“轮子”的“汽车”。

以下是我想要满足的约束:

  • 我希望汽车展示它的车轮,而不是隐藏它们。=> 汽车类中应该有类似的东西

    public Wheel GetWheel(Left, Rear)
    
  • 我希望这辆车能够以某种方式更新它的车轮。例如,在 car 类中可以有如下方法:

    public void ChangeTires(TireType)
    
  • 我不希望其他人能够更新车轮。例如,应该不可能运行如下内容:

    aCar.GetWheel(Left, Rear).SetTire(someTireType); // Does not compile
    

这是一个非常非常重复的方案:网络有节点,椅子有Back,用户有凭据,套接字有IPAddress,BankAccount有所有者......

我阅读了论坛,并询问同事他们是如何做到的。给了我多个答案。他们来了。

我的问题是:

  • 有没有其他方法可以实现上面未列出的目标?
  • 对于“好”的做法是否有普遍共识?
  • 在某处是否有关于这个主题的记录反思?制作 C# 或广泛使用它的杰出人士可能在很久以前就解决了这个话题。

解决方案尝试 1:使用internal

有些人建议将该方法的可见性限制在当前程序集上:SetTire

class Wheel
{
   internal void setTire(TireType someTireType);
}

只有与车轮相同的“组件中的文件”才能更新它(以更改轮胎)。

但它意味着 Car 和 Wheel 在同一个组件中定义,通过传递性,使所有内容都在同一组件中定义。

此外,将其限制为同一程序集是不够的,我想将其限制为 .Car

解决方案尝试 2:返回副本

有些人建议该方法应该返回一个副本:GetWheel

class Car
{
   public Wheel LeftRearWheel;

   public Wheel GetWheel(LeftOrRight, FrontOrRear)
   {
       return LeftRearWheel.DeepCopy();
   }
}

使用此解决方案时,无需告诉客户端代码 wheel 是副本。这意味着,每次客户端编码人员编写 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

如果我想显示的对象是一个容器,那么有一个名为该类的类,据我所知,将解决方案尝试 2 和 3 结合起来:它创建一个副本并将其嵌入到只读接口中:ReadOnlyCollection

class Car
{
    private List<Wheel> myWheels;

    public ReadOnlyCollection<Wheel> GetWheels()
    {
        return myWheels.AsReadOnly();
    }
}

它实现了目标的一部分,即警告用户他不应该尝试更新集合。但它无法阻止更新。换句话说,以下代码编译并且不执行任何操作。

aCar.GetWheels()[0].SetTire(someTireType); // Compile but does nothing

此外,它不适用于非集合。

C# OOP 常量不 可变性

评论

1赞 Rodrigo Rodrigues 9/23/2023
这就是我要做的:1) 有一个接口 IWheel 作为 Car.getWheel 的返回值,接口中没有 setTire。2) 使 Wheel 成为实现 IWheel 的结构体
1赞 T.S. 9/23/2023
而不是有......在这种情况下,您可以设置轮胎、汽车和轮胎以及车轮的内部逻辑 -- 全部为公共wheel.setTireWheelMachine.SetTire(car, location, tire)
0赞 mgueydan 9/23/2023
@RodrigoRodrigues:解决方案 3。这是否意味着您的所有类都是通过公共接口使用的。每天都很重吗?
0赞 mgueydan 9/23/2023
@T.S.我理解并喜欢将 SetTire 函数隔离在它自己的类中的想法。如何防止不是 WheelMachine 的班级更换轮胎?
3赞 Alexei Levenkov 9/23/2023
您能否澄清一下为什么只读接口(解决方案 3)以某种方式导致代码重复?

答:

1赞 Mark Seemann 9/23/2023 #1

我不确定我是否理解这个问题,但我会尝试总结我所理解的内容:

  • Car应该持有一些Wheels
  • Car应该有一个方法ChangeTires
  • 呼叫者不应能够呼叫 - 只能通过 .ChangeTiresWheelCar

如果是这样,一种方法是将公共接口与实现分开。

首先,定义要向客户端代码公开的公共 API:

public interface IWheel
{
    // Put some members here that DON'T include changing tires...
    // Current pressure, size, etc.
}

现在创建类并使实现成为其中的私有嵌套类:CarIWheelCar

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
        }
    }
}

虽然嵌套类的方法被标记为 ,但它仅对类可见,因为该类是一个类。WheelChangeTirepublicCarWheelprivate

因此,可以调用所有对象,但其他类不能。CarChangeTireWheel