【问题标题】:Assistance with setting up Classes and Objects in C# [closed]协助在 C# 中设置类和对象 [关闭]
【发布时间】:2018-12-28 15:47:39
【问题描述】:

肯定仍然是一名正在接受培训的程序员,但正试图为我的项目做点什么,并绕着圈子转。请在设置我的课程时寻求任何指导。我正在开发代码供其他人通过 .dll 程序集使用。

让我只描述一个简单的类(使用 Base 类),名为 Car,它有一些属性。这些就是我想要达到的要求:

1) 创建 Car 类实例的能力。用户实例化的 Car 对象的所有属性都应该是可变的。

2) 能够为他们提供 3 个预生成的 Car 对象供他们直接使用。怎么样:本田 CRV、丰田普锐斯、日产 Rogue

3) 预先生成的汽车(Honda CRV、Toyota Prius、Nissan Rogue)应该被锁定(只读?)。甚至 Base 类 Transport 中的属性也应该是只读的。

实现这一目标的最佳方法是什么?我是否使用静态属性?我是否使用 readonly 修饰符?我是否创建了一个名为 HondaCRV 的新类(例如)并使用 Car 作为基类?然后让东西在那里只读?我将什么设为公开和私有(或受保护)?还是我只是创建一个名为“Cars”的新类,它公开了我的 3 个预先生成的汽车(作为属性)而没有别的?

我只是不知道什么是最好的方法,哪种方法可以为我的组装消费者提供最佳体验。我尝试了一些事情并且接近了,但我永远无法让“只读”传播到基类。

请原谅我在示例代码中可能写的任何不良做法。我仍然是一名正在接受培训的 C# 程序员。

提前感谢您为我提供的任何帮助并尝试理解我的帖子。我很高兴(也很兴奋)学习并采纳您对我的任何想法。我也很乐意澄清任何事情。我尽力表达了我的要求。

阿马尔

enum Terrain
{
    Land,
    Water,
    Air
}

class Transportation
{
    private Terrain terrainType;

    public Terrain TerrainType
    {
        get { return terrainType; }
        set { terrainType = value; }
    }
}

class Car:Transportation
{
    private string mfg;
    private string model;

    public string Mfg
    {
        get { return mfg; }
        set { mfg = value; }
    }

    public string Model
    {
        get { return model; }
        set { model = value; }
    }

    public Car(string Manufacturer, string CarModel)
    {
        Mfg = Manufacturer;
        Model = CarModel;
        TerrainType = Terrain.Land;
    }
}

【问题讨论】:

  • 也许一件好事是拥有一个具有三个属性的静态 Cars 类......所以当我的 Assembly 的用户在 Cars 中键入时。那么这三个 Pregenerated 将只出现(HondaCRV、ToyotaPrius、NissanRogue)。我能够做到这一点,但是当我使用该类时,我能够更改所有我不想要的属性。
  • 嗨,阿马尔,感谢您的提问。请考虑将其发布到 codereview.stackexchange.com/,因为它更适合那里,并且您更有可能得到您期望的答案。
  • 这样做的一种方法是将每辆车设为只读。修改汽车意味着要创建具有所需属性的新车。
  • 我同意,听起来像使用参数化构造函数让你的类不可变可以解决问题。然后,您可以定义一个预先创建的静态汽车池。
  • 如果不出意外,汽车的品牌和型号应该是可变的。如果这些属性发生变化,没有人会认为它和以前一样。

标签: c# class oop object


【解决方案1】:

这就是我要做的。在现实生活中,我可能在数据库中有一个Manufacturer 表和一个Model 表,它们之间存在外键关系。在这里,我使用enumDictionary 对其进行建模

我将枚举用于制造商。这是一个不会经常更改的列表(在过去十年左右的时间里,我可以将 Genesis 和 Tesla 视为新制造商):

public enum CarManufacturer
{
    Acura,
    BMW,
    Buick,
    //fill in more
    Honda,
    Nissan,
    Tesla,
    Toyota,
    //etc
}

我留下了你的“地形”枚举:

public enum Terrain
{
    Land,
    Water,
    Air
}

我将您的 Transportation 类更改为 abstract(即不可实例化)并将 TerrainType 属性设为只读。只能在构造函数中设置。我还只提供了一个非默认构造函数(因此必须设置该属性):

//This is abstract, no one can instantiate one, 
//only subclasses of this type can be constructed
public abstract class Transportation
{
    //this is read-only and must be set at construction time
    public Terrain TerrainType { get; }

    protected Transportation(Terrain terrainType)
    {
        TerrainType = terrainType;
    }

    //you probably want some methods (example, Move).  You can declare them "abstract"
}

对汽车类的大量更改:

  • 我制作了一个私有(静态)制造商字典 模型关系。
  • 我提供对该字典的有限访问(客户可以获得模型列表,并可以验证特定模型是否由制造商制造)
  • 我将 Manufacturer 和 Model 属性设为只读 - 它们都必须在构建时设置
  • 我让(唯一的)构造函数设置了这些属性,并酌情调用了非默认的 Transportation 构造函数

结果如下:

public class Car : Transportation
{
    //in real life, this might be stored in a database
    private static readonly Dictionary<CarManufacturer, List<string>> CarModels =
        new Dictionary<CarManufacturer, List<string>>
        {
            {CarManufacturer.Acura, new List<string> {"RDX", "NSX", "MDX",}},
            {CarManufacturer.BMW, new List<string> {"2Series", "3Series", "etc",}},
            {CarManufacturer.Honda, new List<string> {"Civic", "Fit", "CRV", "Pilot",}},
            {CarManufacturer.Nissan, new List<string> {"Altima", "Maxima", "Rogue",}},
            {CarManufacturer.Toyota, new List<string> {"Corolla", "Prius",}},
        };

    //these get set at constructor time, they cannot be changed after that
    public CarManufacturer Manufacturer { get; }
    public string Model { get; }

    //you need to call the parameterized base class constructor
    public Car(CarManufacturer manufacturer, string model) : base(Terrain.Land)
    {
        Manufacturer = manufacturer;
        if (!ValidateCarModel(manufacturer, model))
        {
            throw new ArgumentException($"Manufacturer {manufacturer} does not make model: {model}");
        }
        Model = model;
    }

    public static bool ValidateCarModel(CarManufacturer manufacturer, string model)
    {
        if (!CarModels.TryGetValue(manufacturer, out var models))
        {
            return false;
        }

        return models.Contains(model);
    }

    public static IEnumerable<string> ModelsForManufacturer(CarManufacturer manufacturer)
    {
        if (!CarModels.TryGetValue(manufacturer, out var models))
        {
            //maybe this should through, but this also makes sense
            return new List<string>();
        }
        //otherwise
        return models;
    }
}

现在我可以制作一些汽车了。我可以在创建它们之前测试它们是否有效(或无效):

 Car CRV, Rogue, Prius;
 if (Car.ValidateCarModel(CarManufacturer.Honda, "CRV"))
 {
     CRV = new Car(CarManufacturer.Honda, "CRV");
 }
 if (Car.ValidateCarModel(CarManufacturer.Nissan, "Rogue"))
 {
     Rogue = new Car(CarManufacturer.Nissan, "Rogue");
 }
 //I know that Toyota makes a Prius, so I just create one.  If I mess up, I'll get an exception
 Prius = new Car(CarManufacturer.Toyota, "Prius");

我认为这符合你的需求,而且有点指导意义

请注意,除了制造商枚举之外,如果我使用数据库而不是枚举/字典方法来完成此操作,我可以拥有大致相同的界面。

【讨论】:

  • 天哪,FlyDog57,我从没想过我的帖子会有这样的回应。太感谢了。我正在研究大家的答案和学习技术,也在研究我不熟悉的C#中的关键字。这太好了。我会尽快消化所有内容后再次发布。该实现看起来应该可以工作。
  • 对不起,我也忘了说谢谢你的回答 Flydog57。
  • @AmarKapadia:关于这种工作方式的一件事是,如果您使用图形用户界面,您可以从CarManufacturer 类型中获取制造商名称并使用它们来填充列表框。然后,一旦用户选择了制造商,您就可以获得模型列表并填充第二个列表框。一旦用户从两个列表中进行选择,您就知道制造商/型号组合是正确的,因此您可以实例化汽车。
  • 知道了。还没有深入 UI,但我明白了你的意思(从我的 MS Access Form Builder 经验中汲取)是的,这肯定会强制执行有效的组合。所以这很棒。我遵循了一切(也放入 Visual Studio 以真正了解它是如何工作的)。它奏效了。我在测试时特别着迷……我不小心做了一辆本田 CRv,它标记了一个异常。从您的代码中可以学到很多东西,但我相信您的技术以及 Dmitri 的上述技术正是我所需要的。我由衷的感谢。今天很开心。
  • 您可以对其进行编码以进行独立于大小写的比较,然后修复大小写(查看StringComparer.OrdinalIgnoreCase(或类似的东西,它可能是“StringComparison” - 我将它们混合在一起))。
【解决方案2】:

为他们提供 3 个预先生成的 Car 对象,让他们开箱即可使用

只添加几个类的实例怎么样?

class Car
{
    ...

    public static readonly List<Car> Instances = new List<Car>() 
    {
        new Car() {Model = "Honda", Name = "CRV"}, 
        new Car() {Model = "Toyota", Name = "Prius"}, 
        new Car() {Model = "Nissan", Name = "Rogue"}
    };
}

他们可以像这样访问它们:

Car car = Car.Instances[0];

【讨论】:

  • 非常感谢 Hirasawa...这是一个很好的答案,但我对这种方法的唯一担心是组件的用户不会知道索引 0 将是本田,索引 1 将是普锐斯等。否则,这完全有道理。
【解决方案3】:

我不知道在一个类中做你想做的事情的直接方法,但你可以使用模式组合作为解决方法:不可变和工厂。

首先,您声明 ImmutableCar 类,它隐藏了这样的基类设置器。

public class ImmutableCar : Car
{
    public string Mfg
    {
        get { return base.Mfg; }
    }

    public string Model
    {
        get { return base.Model; }
    }

    public Terrain TerrainType
    {
        get { return base.TerrainType; }
    }

    internal ImmutableCar(string Manufacturer, string CarModel) : base (Manufacturer, CarModel)
    {
    }
}

其次,您将 Car 和 ImmutableCar 类的所有构造函数都设置为内部,因此您的程序集的用户无法调用它。

internal Car(string Manufacturer, string CarModel)
{
    Mfg = Manufacturer;
    Model = CarModel;
    TerrainType = Terrain.Land;
}

internal ImmutableCar(string Manufacturer, string CarModel) : base (Manufacturer, CarModel)
{
}

第三,创建工厂类,实例化所有汽车。它通过属性返回 ImmutableCar 提供您的“只读”汽车。对于可编辑的汽车,它提供了 GenerateCar 方法。

public static class CarFactory
{
    public static ImmutableCar HondaCrv { 
        get
        {
            return new ImmutableCar("Honda", "Crv");
        }
    }

    public static Car GenerateCar(string Manufacturer, string CarModel)
    {
        return new Car(Manufacturer, CarModel);
    }
}

最后进行一些测试。

        var landroverDiscovery = CarFactory.GenerateCar("Land Rover", "Discovery 2");
        landroverDiscovery.Model = "test"; // Ok
        var hondaCrv = CarFactory.HondaCrv;
        hondaCrv.Model = "test"; // Produces error

【讨论】:

  • 亲爱的德米特里....非常感谢您的帖子。我已经查看了大家的帖子并研究了我不熟悉的 C# 关键字。我会很快再次发布。现在,只想对您为我提供实现和代码表示衷心的感谢。
  • 德米特里!!!是的,一切正常。我不得不粘贴并做一些小的调整只是为了让代码工作......但它确实像我想要的那样工作。这太棒了。我真的必须详细研究所有内容并申请回我的项目,但你已经向我展示了一条通往我所需要的道路。非常感谢!!!
  • 很高兴为您提供帮助)您可能还想知道的另一件事。 ImmutableCar 类中的属性可以应用“new”关键字以避免编译器警告。像这个“公共新字符串模型”。工作方式相同,但向编译器明确表明您确实想隐藏基类属性。
猜你喜欢
  • 2012-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多