为可能的空值使用不同的类(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;
}
解决这个问题有几种方法:
方法#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;
}
优点:
- 最灵活的方法-你可以在这些类中添加/删除任何字段,因为它们是完全独立的。
- 与其他方法相比,代码的复杂性较低(尽管代码总量可能会更大)。
缺点:你不能使用可能导致代码重复的通用或多态代码。
我通常在项目开始时选择这种方法,当时需求并不完全明确,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; }
}
优点:
- 你不需要复制所有的公共字段。只有一个赋值操作:
human.Name = humanName
. - 可以将
human.Name
传递给需要HumanName
类型的代码。 你可以有额外的内部设置,如
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; } }
缺点:
- 当你需要添加一个新的字段到
HumanName
类型,但不想将其添加到Human
类型时,你将无法处理这种情况。 - 你必须写像
human.Name.FirstName
这样的东西,有时可能很乏味。
关于你的代码,在这种情况下,第一个方法将返回List<HumanName>
,第二个方法将返回基于HumanName
的Human
对象。
当附加字段的数量相对较大(> 5)或者当需求变化导致这些字段集在未来发生变化的可能性很小时,我通常选择这种方法。
所有这些方法都假设Human
和DetailedHuman
本质上是相同的域实体(它们之间存在"is-a"关系:https://en.wikipedia.org/wiki/Is-a)。另一个例子是拥有包含所有字段(FirstName, LastName, Age, Address)的数据库表,因此Human
类(或方法#3中的HumanName
)只是这些字段的一个子集(换句话说:Human
是DetailedHuman
类的投影)。如果不是这样,那么就意味着将来类型的结构可能会发生变化,所以在这种情况下,我绝对建议根据您的用例使用方法#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