为可能的空值使用不同的类(c#作为示例)

本文关键字:空值 | 更新日期: 2023-09-27 18:05:52

我创建了一个具有以下属性的类

class Human
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
}

我在我的api服务中声明了以下方法

public async Task<List<Human>> GetAllHumans() 
{
    // Using mock data (would call the api service here)
    return new List<Human>
    {
        new Human { FirstName = "John", LastName = "Doe" },
        new Human { FirstName = "Jane", LastName = "Doe" }
    };
}
public async Task<Human> GetDetailedInformationForHuman(Human human)
{
    // Retrieves data from the server for more detailed information
    var extraInformation = /* Api Call */
    human.Age = extraInformation.Age;
    human.Address = extraInformation.Address;
    return human;
}

没有办法从GetAllHumans方法中获得更详细的human版本。(年龄和地址属性已经设置)

我应该创建一个不同的类(例如"DetailedHuman")并将Age和Address属性移到那里并从Human继承吗?从我什么时候开始在我的程序中使用人类类型。我总是不确定年龄或地址是否为空,除非我检查它们。在这种情况下,最好的做法是什么?谢谢您抽出宝贵的时间。

编辑1:我的第二个例子,作为一个评论的请求,是创建第二个类并放置属性。

class Human
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
class DetailedHuman : Human
{
    public int Age { get; set; }
    public string Address { get; set; }
}
public async Task<List<Human>> GetAllHumans() 
{
    // Using mock data (would call the api service here)
    return new List<Human>
    {
        new Human { FirstName = "John", LastName = "Doe" },
        new Human { FirstName = "Jane", LastName = "Doe" }
    };
}
public async Task<DetailedHuman> GetDetailedInformationForHuman(Human human)
{
    // Retrieves data from the server for more detailed information
    var extraInformation = /* Api Call */
    var detailedHuman = new DetailedHuman { FirstName = human.FirstName, LastName = human.LastName };
    detailedHuman.Age = extraInformation.Age;
    detailedHuman.Address = extraInformation.Address;
    return detailedHuman;
}

为可能的空值使用不同的类(c#作为示例)

解决这个问题有几种方法:

方法#1:继承(你的例子)

class Human
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
class DetailedHuman : Human
{
    public int Age { get; set; }
    public string Address { get; set; }
}

优点:可以使用多态性(可以将DetailedHuman的实例传递给需要Human类实例的代码)。

缺点:您可能会遇到必须进行类型检查的情况(取决于您的用例):if (human is DetailedHuman) { ... }或强制类型转换:DetailedHuman detailedHuman = human as DetailedHuman; if (detailedHuman != null) { ... }

当您确实需要使用多态性时,我建议使用这种方法。

方法#2:完全独立的类

class Human
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
class DetailedHuman
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
}

你也可以像这样给DetailedHuman添加复制构造函数:

public DetailedHuman(Human human)
{
    FirstName = human.FirstName;
    LastName = human.LastName;
}

优点:

  1. 最灵活的方法-你可以在这些类中添加/删除任何字段,因为它们是完全独立的。
  2. 与其他方法相比,代码的复杂性较低(尽管代码总量可能会更大)。

缺点:你不能使用可能导致代码重复的通用或多态代码。

我通常在项目开始时选择这种方法,当时需求并不完全明确,DetailedHuman类中的附加字段数量相对较少(我会说五个或更少)。这种方法的好处是,您可以轻松地更改类型的结构,以反映需求中的更改。此外,一旦需求变得更加清晰,并且您看到您的代码肯定会从这种切换中受益,您可以轻松地切换到继承或聚合方法。

方法3:aggregation

与@Fabjan的例子中的概念相同,尽管我建议以稍微不同的方式使用它:

class HumanName
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
class Human
{
    public HumanName Name { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
}

优点:

  1. 你不需要复制所有的公共字段。只有一个赋值操作:human.Name = humanName .
  2. 可以将human.Name传递给需要HumanName类型的代码。
  3. 你可以有额外的内部设置,如

    class HumanCredentials
    {
        public string Login { get; set; }
        public string AccessToken { get; set; }
    }
    class Human
    {
        public HumanName Name { get; set; }
        public HumanCredentials Credentials { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }
    }
    

缺点:

  1. 当你需要添加一个新的字段到HumanName类型,但不想将其添加到Human类型时,你将无法处理这种情况。
  2. 你必须写像human.Name.FirstName这样的东西,有时可能很乏味。

关于你的代码,在这种情况下,第一个方法将返回List<HumanName>,第二个方法将返回基于HumanNameHuman对象。

当附加字段的数量相对较大(> 5)或者当需求变化导致这些字段集在未来发生变化的可能性很小时,我通常选择这种方法。

所有这些方法都假设HumanDetailedHuman本质上是相同的域实体(它们之间存在"is-a"关系:https://en.wikipedia.org/wiki/Is-a)。另一个例子是拥有包含所有字段(FirstName, LastName, Age, Address)的数据库表,因此Human类(或方法#3中的HumanName)只是这些字段的一个子集(换句话说:HumanDetailedHuman类的投影)。如果不是这样,那么就意味着将来类型的结构可能会发生变化,所以在这种情况下,我绝对建议根据您的用例使用方法#2或#3。

这里我建议不要继承。如果我们期望某些数据可能被填充,也可能没有被填充,我们无论如何都必须检查它。我们可以通过添加新的Details与所有可选字段,并把它放在Human类:

class Details 
{
   public int Age { get; set; }
   public string Address { get; set; }
}
class Human
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Details Details { get; set; }
}

现在我们可以检查是否所有细节数据都被填充了,只需检查一个属性:

if(human.Details != null) { do smth when all data was populated by service... }
else { do smth when only FirstName and LastName were populated }

或者使用c# 6.0,我们可以安全地访问我们需要的属性,而不会有抛出NullReferenceException的风险,只需一行代码:

human?.Details.Address