65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (13投票s)

2013年8月24日

CPOL

6分钟阅读

viewsIcon

99235

downloadIcon

1857

本文将通过一个具体且实用的示例,向您解释在 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日

© . All rights reserved.