在 VB.NET (OOP) 中使用接口和继承的优势






4.74/5 (13投票s)
本文将通过一个具体且实用的示例,向您解释在 VB.NET 中结合使用接口与继承进行面向对象编程的方法。
下载 OOP_Inteface_Inheritance-noexe.zip - 源代码(C# 和 VB.NET),不含 EXE 文件,
Visual Studio 10 项目文件
文章版本:2.03
引言
许多初级程序员可能不了解也未在代码中使用面向对象的特性,尽管他们能写出成堆的代码,但这些代码对后续的开发人员来说可读性差,会给他们带来麻烦。
OOP 的特性之一是接口。“接口只包含方法、属性、事件或索引器的签名。
实现接口的类或结构体必须实现
接口定义中指定的接口成员。”正如微软所说。
在本文中,我将解释一个使用接口并结合 OOP 中继承概念的简单示例。
这个例子对于那些在理解如何同时使用接口和继承方面有困难的 VB.NET 新手程序员会很有用。
本文代码使用 Visual Basic.Net 语言编写,可能适用于 Windows 应用程序、Web 应用程序 (ASP.NET)、移动应用程序等。
背景
在您继续阅读本文之前,请注意,我假设您已经了解 OOP 编程的理论概念,例如类、方法、属性、继承。
有关面向对象编程概念(如类、方法、属性、继承、访问修饰符)的更多信息,请在 codeproject.com 或 Microsoft MSDN 上搜索。
场景
想象一下,您收到了软件项目经理的指令,需要为系统中的三种不同类型的用户准备三个不同的类。某些用户具有共同的属性,例如姓名、姓氏、年龄、电话、地址等。
您可以提供一个父类,并将所有通用属性放入其中,然后创建三个子类。
如果我们假设有“销售”用户、“经理”用户和“开发人员”用户;最终我们应该有三个以它们命名的类,以及一个名为“EmployeeBase”的父类,到目前为止这都很明确。
您的软件项目经理想要的方法之一是“GetSalary()”,这意味着这些类应该根据用户类型从数据库中获取薪水。任何时候您需要使用“GetSalary()”方法时,您都应该首先确定“当前用户类型”,然后创建相应的用户类实例,再调用“GetSalary()”。
If UserType = "Developer" Then
Dim objectDeveloper As New EmployeeDeveloper
objectDeveloper.GetSalery()
End If
If UserType = "Manager" Then
Dim objectManager As New EmployeeManager
objectManager.GetSalery()
End If
上述代码非常糟糕,所有专业的高级软件开发人员都无法接受。
使用代码
为了解决这个问题,我将尝试引导您深入 OOP 概念,经过四次迭代,我们将得到整洁、美观、基于 OOP 的代码,最终我们将拥有一段能被大多数软件高级工程师和软件团队负责人接受的代码。
在这里,接口的概念帮助我们改变代码,使其变得更好,我将解释代码的演进步骤。
第一次迭代:
Public Interface IEmployee
Function GetSalary() As Double
Function GetEmpType() As String
End Interface
Public Class EmployeeManager
Inherits EmployeeBase
Implements IEmployee
Sub New()
End Sub
Public Function GetSalary() As Double Implements IEmployee.GetSalary
Return 2300.0
End Function
Public Function GetEmpType() As String Implements IEmployee.GetEmpType
Return "I am an Employee >> 'Manager' "
End Function
End Class
然后我们还有另外两个具有相同实现的类(请参考源代码文件)
当我们需要使用它时:
Module Main_Module
Sub Main()
Dim oEmp As IEmployee
oEmp = New EmployeeDeveloper
Console.WriteLine(oEmp.GetEmpType())
Console.WriteLine("The Salary is : " & oEmp.GetSalary())
oEmp = New EmployeeManager
Console.WriteLine(oEmp.GetEmpType())
Console.WriteLine("The Salary is : " & oEmp.GetSalary())
End Sub
End Module
然后结果会是这样
经过第一次迭代,我们的问题到目前为止已经解决了,您已经学会了如何使用接口以及为什么需要接口,您可能会问,就这些了吗?
当您的软件项目经理要求您根据工作时间和工资百分比计算每位员工的薪水时,第二个问题就出现了。那么您应该忘记 GetSalary() 方法,并改进您的代码以获得更好的结果。最好我们创建另一个负责计算薪水的类,我称之为“Accounting”类。
第二次迭代
我在接口中添加了另一个函数签名
Public Interface IEmployee
Function GetSalary() As Double
Function GetEmpType() As String
Function GetWorkTime() As Integer
Function GetExtraWagePercent() As Short
End Interface
我已在所有三个子类中添加了 GetExtraWagePercent() 方法
Public Class EmployeeManager
Inherits EmployeeBase
Implements IEmployee
' Public Function GetSalary() As Double Implements IEmployee.GetSalary
' Return 2300.0
' End Function
Public Function GetWorkTime() As Integer Implements IEmployee.GetWorkTime
Return 210
End Function
Public Function GetExtraWagePercent() As Short Implements IEmployee.GetExtraWagePercent
Return 1.3
End Function
Public Function GetEmpType() As String Implements IEmployee.GetEmpType
Return "I am an Employee >> 'Manager' "
End Function
End Class
另外两个类也遵循相同的实现,但工资百分比金额等不同
(请参考本项目的源代码)
我还创建了一个“Acounting”类
Public Class Accounting
Public Function CalculateSalery(employee As IEmployee) As Double
'For Ex : 5$ per hours
Return employee.GetWorkTime * 5 * employee.GetExtraWagePercent
End Function
您应该将这些类连接到“Accounting”类,并且该类
应该为您提供 CalculateSalary() 方法,该方法能够在不需要了解其他类函数的情况下计算薪水。
然后在主模块中,我们可以如下使用
Module Main_Module
Sub Main()
Dim oEmp As IEmployee
Dim oAccounting As New Accounting
oEmp = New EmployeeManager
Console.WriteLine(oEmp.GetEmpType())
Console.WriteLine("My Salary is : " & oAccounting.CalculateSalery(oEmp))
oEmp = New EmployeeSales
Console.WriteLine(oEmp.GetEmpType())
Console.WriteLine("My Salary is : " & oAccounting.CalculateSalery(oEmp))
oEmp = New EmployeeDeveloper
Console.WriteLine(oEmp.GetEmpType())
Console.WriteLine("My Salary is : " & oAccounting.CalculateSalery(oEmp))
End Sub
End Module
然后您可以继续改进您的代码。
然后结果会是这样
请记住,我使用继承来访问通用属性,例如:姓名
第三次迭代
现在在第三次迭代中,我想在 Accounting 类中添加一个方法,它除了能计算薪水外,还能写入员工姓名,但我不想在每个类中都连接到数据库,只想在一个地方,那就是在父类 (EmployeeBase) 中。因此,我需要修改 EmployeeBase,只添加一个属性“Name”作为示例。另一个问题是父类不知道应该返回哪个 Name,所以我需要将员工 ID 从每个子类传递到父类。如您在 OOP 中所知,我们可以从子类的构造函数访问父类,如下例所示:
MyBase.New()
我们需要为所有三个类编写一个 Initialize Sub,它能够从用户界面 (UI) 获取员工 ID(根据 Klaus Luedenscheidt 的评论,我改进了这部分),然后我们只需要在每个子类的构造函数中调用 Initialize Sub。Initialize Sub 可以从数据库(XML、SQL Server 等)获取数据。
根据赋予新子类实例的员工 ID(在调用期间,这里发生在主模块中)
注意:Initialize Sub 的访问修饰符是 Protected
,这意味着所有子类都可以使用这个子程序。很明显,员工 ID 将从子类的构造函数发送到 Initialize 类。因此,每个子类都有 2 个构造函数:
第一个:用于创建实例(普通情况)
第二个:用于处理已有数据时
“第二种方式的示例:想象一下,您只想在数据库中添加员工,只需要使用第一个构造函数。但是当您决定删除或修改员工时,就应该使用第二个构造函数。”
MyBase.New()
Initialize(EmpId)
下面的代码向您展示了对 EmployeeBase 类的最后修改
Public Class EmployeeBase
Private _name As String
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
_name = value
End Set
End Property
Sub New()
Name = "-----"
End Sub
Protected Sub Initialize(ByVal EmpId As Integer)
'Normally here we will access the database to retrieve the employees data
'For Example I Connected To Database Then I Get the Employee Name According
' to Their Employee Ids
'Open Connection
'Get Data
Select Case EmpId
Case 1
Name = "Babak Manager"
Case 2
Name = "Ali Developer"
Case 3
Name = "Mike Sales"
Case Else
End Select
'Close Db Connection
End Sub
End Class
我们需要为所有“子类”编写第二个构造函数,并且我还在 IEmployee 接口中编写了两个函数签名 GetEmployeeNameAndSalary() 和 GetEmployeeName(),然后我在子类中编写了相关函数,以展示 Accounting 类如何在不直接访问子类的情况下从子类获取 EmployeeName。
Public Class EmployeeSales
Inherits EmployeeBase
Implements IEmployee
'first Constructor of Employee Sales Class
Sub New()
End Sub
'second Constructor of Employee Sales Class
Sub New(ByVal EmpId As Integer)
MyBase.New()
Initialize(EmpId)
End Sub
Public Function GetWorkTime() As Integer Implements IEmployee.GetWorkTime
Return 142
End Function
Public Function GetExtraWagePercent() As Short Implements IEmployee.GetExtraWagePercent
Return 1.1
End Function
Public Function GetEmployeeNameAndSalery() As String Implements IEmployee.GetEmployeeNameAndSalery
Dim objAccounting As New Accounting
Return MyBase.Name & " - " & objAccounting.CalculateSalery(Me)
End Function
Public Function GetEmployeeName() As String Implements IEmployee.GetEmployeeName
Return MyBase.Name
End Function
Public Function GetEmpType() As String Implements IEmployee.GetEmpType
Return "I am an Employee >> 'Sales' "
End Function
End Class
最终 IEmployee 接口将如下所示
Public Interface IEmployee
Function GetSalary() As Double
Function GetWorkTime() As Integer
Function GetExtraWagePercent() As Short
Function GetEmpType() As String
Function GetEmployeeNameAndSalery() As String
Function GetEmployeeName() As String
End Interface
而 accounting 类将如下所示
Public Class Accounting
Private _employeeId As Short
Public Property EmployeeId As Short
Get
Return _employeeId
End Get
Set(value As Short)
_employeeId = value
End Set
End Property
Public Function CalculateSalery(employee As IEmployee) As Double
'For Ex : 5$ per hours
' Wana need different price for each type of Employee
' Defind it in Interface and get it from each Child Class :)
Return employee.GetWorkTime * 5 * employee.GetExtraWagePercent
End Function
Public Function CalculateSaleryAndName(employee As IEmployee) As String
Dim EmpSalery As Double = CalculateSalery(employee)
Return employee.GetEmployeeNameAndSalery
End Function
Public Function Customize_CalculateSaleryAndName(employee As IEmployee) As String
Dim EmpSalery As Double = CalculateSalery(employee)
Dim EmpName As String = employee.GetEmployeeName
Return "Employee Name is : " & EmpName & " - Employee Salery is : " & EmpSalery & " $"
End Function
End Class
然后结果将是这样
类图将显示究竟发生了什么
结论
如果您懒于编写多余的代码,面向对象编程将是一个非常有趣的话题。它还能帮助后来的开发人员理解您的代码并扩展您编写的程序。但如果您不关心这些概念,其他开发人员也不会关心您,他们会毁掉您创建的所有代码,然后从一个新项目开始。通过使用 OOP 的概念和特性,我们可以拥有可用且健壮的代码。
下载 OOP_Inteface_Inheritance-noexe.zip
(源代码使用 C# 和 VB.NET)
历史
1- 版本 2:进行了一次更新,添加了 Initialize 方法以避免创建基类的实例 - 2013年9月6日