创建继承类
解决的问题
在编写代码时,您经常会遇到需要为新场景编写几乎完全相同的代码(除了一些小例外)的情况。最简单的方法是复制和粘贴(尽管如此,经验丰富的人通常会采用这种方法,他们应该更了解情况)——直接获取代码,然后获得两个副本,您必须对其进行调试、测试和维护。这里有一个原则:
继承是我们正确复制和粘贴代码的方法。
想象一下,当您需要复制和粘贴时,您不是粘贴实际代码,而是跟踪对原始代码的引用,这样就不会出现重复。当您需要进行更改时,您使用特殊关键字来表明覆盖的意图 - 这就是我们通过继承所做的一切!一般来说,继承和多态性是我们进行复制和调整的系统 - 这些系统为我们提供了复制的所有优点,而没有任何缺点。
我们的场景
假设有一个应用程序,它向一家大型公司的员工及其配偶提供福利信息。配偶可以读取员工可以读取的大部分信息,但不能更改这些信息;也就是说,他们不能将这些信息保存到数据库中,除非经过授权流程。在当前的代码库中,这些差异反映在复制粘贴的代码和一些脆弱而分散的尴尬条件逻辑中。
我们有一个 Employee 类,它封装了大部分福利逻辑:
///<summary>
/// The different account types for users
///</summary>
public enumUserTypes
{
///<summary>
/// The user is the benefits-eligible employee
///</summary>
Employee,
///<summary>
/// The user is a spouse of the benefits-eligible employee
///</summary>
Spouse
}
///<summary>
/// Represents the benefits information for the current user.
///</summary>
public class Employee
{
///<summary>
/// The Health Plan Code for the user
///</summary>
public string HealthPlanCode { get; set; }
///<summary>
/// The balance of the retirement plan for the user.
///</summary>
public decimal RetirementBalance { get; set; }
///<summary>
/// The number of PTO days the user has remaining for the current plan year.
///</summary>
public int PTODaysRemaining { get; set; }
///<summary>
/// The type of the current user.
///</summary>
public UserTypes UserType { get; set; }
public void Save()
{
if (this.UserType != UserTypes.Employee)
{
// send the request to Admin
// who will get authorization from the employee for the change
}
else// UserType == UserType.Employee
{
// actual persistence logic goes here
}
}
}
在顶部,我们有一个枚举,它定义了两个用户类型,即员工和配偶,它们用作类的 UserType 属性的类型。该类有几个属性,当用户登录时,它们会将有关用户福利状态的信息传递给员工或其配偶。
在底部的 Save 方法中,我们进行了检查以确保当前用户是员工而不是配偶——我们不希望配偶未经授权更改福利并造成婚姻冲突以及文书工作麻烦。我们得到的代码可以运行——它通过了所有单元测试,因此它至少满足了软件质量的第一个衡量标准:正确性。但是这些棘手的 if-then 语句(例如,当您需要添加 Dependent 用户类型时,它们最终会变成 switch 语句)是一种代码异味,表明有些事情不对劲。显然,我们需要重构此代码以更好地反映模型的性质。
更好的方法
我们首先通过不同的类来区分员工和配偶,而不是严格按照用户类型属性来区分。如果我们要有一个 Employee 类和一个 Spouse 类,并在它们之间共享大量代码,我们的第一反应是复制和粘贴该代码。我们不会逐字逐句地复制和粘贴,而是以一种很好的面向对象的方式进行复制和粘贴,方法是创建一个从 Employee 对象继承的新 Spouse 对象:
public class Spouse: Employee
{
}
就是这样。此时,我们有一个单独的对象,它可以更好地反映模型,区分用户不是配偶类型的员工(这是不正确的),而是配偶类型的用户,此时,配偶类型拥有员工用户类型的所有特权和权力。
我们之前提到过,条件逻辑遍布整个代码,可能是这样的形式:
switch (user.UserType)
{
case UserTypes.Employee:
// do employee-oriented stuff
break;
case UserTypes.Spouse:
// do spouse-oriented stuff
break;
}
现在我们正在转向正确的继承模型,最终我们会对其进行重构。但目前,我们希望我们的新类能够自动反映其用户类型。为了实现这一点,我们将更改基础 Employee 类上的 UserType 属性,从自动支持的属性改为简单地返回“Employee”作为其用户类型:
///<summary>
/// The type of the current user.
///</summary>
public virtual UserTypes UserType => UserTypes.Employee;
因为 Spouse 继承自 Employee,这意味着,如果不进行任何更改,Spouse 将被视为 Employee。因此,我们添加了virtual关键字 - 这标记属性或方法可以被覆盖。我们将在 Spouse 类中像这样覆盖该属性:
public override UserTypes UserType => UserTypes.Spouse;
很简单。我们的分散条件逻辑将继续发挥作用,直到我们能够将其整合。现在,关于该 Save 方法 - 我们希望将两个分支拆分为对不同对象的单独调用。我们首先修改 Employee Save() 调用,如下所示:
public void Save()
{
// actual persistence logic goes here
}
在此调用中,我们将对数据存储进行实际持久化。然后,我们将以下代码添加到 Spouse 类:
public newvoid Save()
{
// send the request to Admin
// who will get authorization from the employee for the change
}
我们已将管理员的通知和授权代码移至此类。请注意,此处的子方法不再使用override ,而是使用new,而且我没有向基方法添加virtual关键字。出于面向对象的原因,有时您需要采用这种方法,特别是因为您可能无法访问基方法,例如从您无法控制的框架中的类继承时。当我们使用此方法时,在继承类的方法上使用new关键字,这称为隐藏。
有什么区别?它们似乎都覆盖了 Save 方法,至少在非正式意义上是如此。区别在于如何解析对 Save() 的调用。在以下代码中:
var spouse = new Spouse();
((Employee)spouse).Save();
我们正在实例化 Spouse 对象,然后将其强制转换回基 Employee 类以调用 Save。在这种情况下,使用隐藏方法,将调用基 Employee 类的 Save 方法,而不是 Spouse 的新Save 方法。如果我们使用基类上的虚拟关键字覆盖它,则相同的代码将调用 Spouse 的覆盖方法。简而言之 -覆盖和隐藏之间的区别在于如何解析方法调用。在进行建模选择时,请记住这一区别。
从用户类继承
最后,让我们考虑一下如何让 Spouse 类完全取消 Save 功能——我们不希望它出现在 Intellisense 中,并且“如果开发人员尝试在其上调用 Save(),我们希望编译失败。在这种情况下,我们可以创建一个抽象基类 User,并在 Employee 和 Spouse 类中继承它:
由于 Save 功能仅适用于 Employee,因此我们将 Save 方法放在那里,在 Spouse 的继承链之外。我们的属性将放在 User 类中,而 Save 方法将放在 Employee 类中:
///<summary>
/// The different account types for users
///</summary>
public enumUserTypes
{
///<summary>
/// The user is the benefits-eligible employee
///</summary>
Employee,
///<summary>
/// The user is a spouse of the benefits-eligible employee
///</summary>
Spouse
}
public abstractclassUser
{
///<summary>
/// The Health Plan Code for the user
///</summary>
public string HealthPlanCode { get; set; }
///<summary>
/// The balance of the retirement plan for the user.
///</summary>
public decimal RetirementBalance { get; set; }
///<summary>
/// The number of PTO days the user has remaining for the current plan year.
///</summary>
public int PTODaysRemaining { get; set; }
///<summary>
/// The type of the current user.
///</summary>
public virtual UserTypes UserType { get; }
}
///<summary>
/// Represents the benefits information for the current user.
///</summary>
public class Employee: User
{
public override UserTypes UserType => UserTypes.Employee;
public void Save()
{
// actual persistence logic goes here
}
}
public class Spouse: User
{
public override UserTypes UserType => UserTypes.Spouse;
}
如您所见,对于两个用户类型(员工和配偶)而言,绝大多数代码都是相同的,现在都放在用户中,只有每个子类中定义了少数例外。
总结和要点
以下是继承、覆盖和隐藏时需要牢记的几个关键事项:
继承就是我们复制粘贴的方式。当你有复制粘贴的冲动时,你应该把这种精力投入到设计一个合适的继承模型上。
重写方法允许您更改父例程的行为 - 使用虚拟关键字将元素标记为有资格被重写。
隐藏方法意味着为父方法创建新定义,并让父方法可通过类型转换执行。请记住这些区别。
要真正隐藏相关类中的方法,请在对象的继承链之外重构该方法。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~