模块实际上与仅包含共享成员的类非常相似。事实上,在 C# 中,不存在“模块”这样的构造。如果没有至少一个模块或类,您就无法编写任何应用程序,因此我怀疑您真正的问题不是“为什么使用类和模块”,而是“为什么使用多个类和模块以及何时适合启动新的类和模块” 。由于模块和类本质上是相同的东西,所以我将只关注为什么你会有多个类。创建新类本质上有四个主要原因:
- 将数据存储在离散项目中
- 组织你的代码
- 在代码中提供接缝
- 将代码分层并支持 n 层
现在,让我们更详细地看看每一项:
将数据存储在离散项目中
通常,您需要存储有关单个项目的多个数据,并将该数据作为单个对象在方法之间传递。例如,如果您编写一个与某人一起工作的应用程序,您可能需要存储有关该人的多个数据,例如他们的姓名、年龄和头衔。显然,您可以将这三个数据存储为三个单独的变量,并将它们作为单独的参数传递给方法,例如:
Public Sub DisplayPerson(name As String, age As Integer, title As String)
Label1.Text = name
Label2.Text = age.ToString()
Label3.Text = title
End Sub
但是,将所有数据作为单个对象传递通常更方便,例如,您可以创建一个MyPersonClass
, 像这样:
Public Class MyPersonClass
Public Name As String
Public Age As Integer
Public Title As String
End Class
然后您可以在单个参数中传递有关一个人的所有数据,如下所示:
Public Sub DisplayPerson(person As MyPersonClass)
Label1.Text = person.Name
Label2.Text = person.Age.ToString()
Label3.Text = person.Title
End Sub
通过这样做,将来修改你的人会变得更加容易。例如,如果您需要添加存储人员技能的功能,并且尚未将人员数据放入类中,则必须转到传递人员数据的代码中的每个位置并添加附加参数。在大型项目中,找到所有需要修复的地方可能非常困难,这可能会导致错误。然而,当您开始需要存储多人列表时,对类的需求就变得更加明显。例如,如果您需要存储 10 个不同人的数据,则需要一个变量列表或数组,例如:
Dim names(9) As String
Dim ages(9) As Integer
Dim titles(9) As String
当然,这一点并不明显names(3)
and age(3)
两者都存储同一个人的数据。这是你必须知道的事情,或者你必须把它写在评论中,这样你就不会忘记。然而,当您有类来存储一个人的所有数据时,这会更干净、更容易做到:
Dim persons(9) As Person
现在,很明显persons(3).Name
and persons(3).Age
都是同一个人的数据。这样,它就是自我记录的。不需要任何评论来澄清你的逻辑。因此,代码将不再那么容易出现错误。
通常,类不仅包含特定项目的数据,还包含作用于该数据的方法。这是一个方便的机制。例如,您可能想添加一个GetDesciption
person 类的方法,例如:
Public Class MyPersonClass
Public Name As String
Public Age As Integer
Public Title As String
Public Function GetDescription() As String
Return Title & " " & Name
End Function
End Class
然后你可以像这样使用它:
For Each person As MyPersonClass In persons
MessageBox.Show("Hello " & person.GetDescription())
Next
我相信你会同意,这比这样做更干净、更容易:
For i As Integer = 0 To 9
MessageBox.Show("Hello " & GetPersonDescription(title(i), names(i)))
Next
现在假设您想为每个人存储多个昵称。正如你可以很容易地看到的,persons(3).Nicknames(0)
比一些疯狂的二维数组简单得多,例如nicknames(3)(0)
。如果您需要存储有关每个昵称的多个数据,会发生什么情况?正如您所看到的,不使用类会很快变得混乱。
组织你的代码
当您编写一个冗长的程序时,如果您没有正确组织代码,它可能会很快变得非常混乱,并导致非常错误的代码。在这场与意大利面条式代码的战斗中,最重要的武器是创建更多的类。理想情况下,每个类仅包含逻辑上直接相关的方法。每个新类型的功能都应该分解为一个新的、命名良好的类。在一个大型项目中,这些类应该进一步组织到单独的命名空间中,但如果你不至少将它们拆分成类,那么你真的会把事情弄得一团糟。例如,假设您将以下方法全部放入同一个模块中:
GetPersonDescription
GetProductDescription
FirePerson
SellProduct
我相信您会同意,如果将这些方法分解为单独的类,那么遵循代码会更容易,例如:
这只是一个非常非常简单的例子。当您有数千个方法和变量处理许多不同的项目和不同类型的项目时,我相信您可以轻松想象为什么类对于帮助组织和自我记录代码很重要。
在代码中提供接缝
这个可能有点高级,但它非常重要,所以我将尝试用简单的术语来解释它。假设您创建了一个跟踪记录器类,它将日志条目写入跟踪日志文件。例如:
Public Class TraceLogger
Public Sub LogEntry(text As String)
' Append the time-stamp to the text
' Write the text to the file
End Sub
End Class
现在,假设您希望记录器类能够写入文件或数据库。此时很明显,将日志条目写入文件实际上是一种单独的逻辑类型,它应该一直位于其自己的类中,因此您可以将其分解为单独的类,如下所示:
Public Class TextFileLogWriter
Public Sub WriteEntry(text As String)
' Write to file
End Sub
End Class
现在,您可以创建一个公共接口并在两个不同的类之间共享它。这两个类都将处理写入日志条目,但它们将以完全不同的方式执行功能:
Public Interface ILogWriter
Sub WriteEntry(text As String)
End Interface
Public Class TextFileLogWriter
Implements ILogWriter
Public Sub WriteEntry(text As String) Implements ILogWriter.WriteEntry
' Write to file
End Sub
End Class
Public Class DatabaseLogWriter
Implements ILogWriter
Public Sub WriteEntry(text As String) Implements ILogWriter.WriteEntry
' Write to database
End Sub
End Class
现在,您已经将该数据访问逻辑分解为自己的类,您可以像这样重构您的记录器类:
Public Class TraceLogger
Public Sub New(writer As ILogWriter)
_writer = writer
End Sub
Private _writer As ILogWriter
Public Sub LogEntry(text As String)
' Append the time-stamp to the text
_writer.WriteEntry(text)
End Sub
End Class
现在,您可以重复使用TraceLogger
在更多情况下可以调用该类,而无需接触该类。例如,你可以给它一个ILogWriter
将条目写入 Windows 事件日志、电子表格、甚至电子邮件的对象 - 所有这些都无需触及原始内容TraceLogger
班级。这是可能的,因为您已经创建了seam条目格式和条目书写之间的逻辑。
格式不关心条目如何记录。它所关心的是如何格式化条目。当它需要写入和输入时,它只是要求一个单独的写入器对象来完成该部分工作。该作者实际上在内部如何做以及做什么是无关紧要的。类似地,编写者并不关心条目的格式如何,它只是期望传递给它的任何内容都是需要记录的已格式化的有效条目。
正如您可能已经注意到的,不仅是TraceLogger
现在可重复使用以写入任何类型的日志,而且写入器也可重复使用以将任何类型的日志写入这些类型的日志。您可以重复使用DatabaseLogWriter
例如,写入跟踪日志和异常日志。
关于依赖注入的一点抱怨
请稍微幽默一下,因为我将这个答案写得更长一些,并对一些对我来说很重要的事情进行了咆哮……在最后一个例子中,我使用了一种称为依赖注入(DI)的技术。它被称为依赖注入,因为 writer 对象是依赖性记录器类的依赖项对象通过构造函数注入到记录器类中。您可以通过执行以下操作来完成类似的事情,而无需依赖注入:
Public Class TraceLogger
Public Sub New(mode As LoggerModeEnum)
If mode = LoggerModeEnum.TextFile Then
_writer = New TextFileLogWriter()
Else
_writer = New DatabaseLogWriter()
End If
End Sub
Private _writer As ILogWriter
Public Sub LogEntry(text As String)
' Append the time-stamp to the text
_writer.WriteEntry(text)
End Sub
End Class
但是,正如您所看到的,如果您这样做,现在每次创建新类型的编写器时都需要修改该记录器类。然后,仅仅为了创建一个记录器,您就必须引用每种不同类型的编写器。当您以这种方式编写代码时,很快,每当您包含一个类时,您突然就必须引用整个世界来完成一项简单的任务。
依赖注入方法的另一种替代方法是使用继承来创建多个TraceLogger
课程,每种类型的作家一个:
Public MustInherit Class TraceLogger
Public Sub New()
_writer = NewLogWriter()
End Sub
Private _writer As ILogWriter
Protected MustOverride Sub NewLogWriter()
Public Sub LogEntry(text As String)
' Append the time-stamp to the text
_writer.WriteEntry(text)
End Sub
End Class
Public Class TextFileTraceLogger
Inherits TraceLogger
Protected Overrides Sub NewLogWriter()
_Return New TextFileLogWriter()
End Sub
End Class
Public Class DatabaseTraceLogger
Inherits TraceLogger
Protected Overrides Sub NewLogWriter()
_Return New DatabaseLogWriter()
End Sub
End Class
像这样使用继承来实现,比模式枚举方法更好,因为您不必引用所有数据库逻辑来记录到文本文件,但是,在我看来,依赖项注入更干净、更灵活。
回到逻辑接缝总结
因此,总而言之,逻辑中的接缝对于代码的可重用性、灵活性和可互换性非常重要。在小型项目中,这些事情并不是最重要的,但随着项目的增长,清晰的接缝可能变得至关重要。
创建接缝的另一个大好处是它使代码更加稳定和可测试。一旦您知道TraceLogger
有效,能够扩展它以供将来使用有一个很大的优势,例如将日志写入电子表格,而不必接触实际的TraceLogger
班级。如果您不必接触它,那么您就不会冒引入新错误并可能危及已使用它的其余代码的风险。此外,单独测试每段代码也变得更加容易。例如,如果您想测试TraceLogger
类,您可以在测试中使用一个假的 writer 对象,该对象仅记录到内存、控制台或其他东西。
将您的代码划分为多个层并支持 N 层
一旦您将代码正确地组织到单独的类中,其中每个类仅负责一种类型的任务,那么您就可以开始将您的类分组到layers。层只是代码的高级组织。从技术上讲,该语言没有任何特定的东西可以使某些东西成为一个层。由于语言中没有直接说明每一层开始和结束位置的内容,因此人们通常会将每一层的所有类放入单独的命名空间中。因此,例如,您可能拥有如下所示的命名空间(其中每个命名空间都是一个单独的层):
MyProduct.Presentation
MyProduct.Business
MyProduct.DataAccess
通常,您总是希望代码中至少有两层:表示层或用户界面层以及业务逻辑层。如果您的应用程序进行任何数据访问,那么通常也将其放在自己的层中。每层应尽可能独立且可互换。所以,举例来说,如果我们的TraceLogger
上面示例中的类位于业务层,它应该可以被任何类型的 UI 重用。
层通过提供进一步的组织、自文档、可重用性和稳定性来扩展之前的所有主题。然而,层的另一个主要好处是,将应用程序分成多个层变得更加容易。例如,如果您需要将业务和数据访问逻辑转移到 Web 服务中,如果您已经将代码干净地编写到定义的层中,那么这样做将非常简单。然而,如果所有这些逻辑都是混合在一起且相互依赖的,那么尝试将数据访问和业务逻辑分解为一个单独的项目将是一场噩梦。
我要说的结束了
简而言之,你永远不会need创建多个类或模块。它永远都会是possible在单个类或模块中编写整个应用程序。毕竟,在面向对象语言被发明之前,整个操作系统和软件套件就已经开发出来了。然而,面向对象编程(OOP)语言如此流行是有原因的。对于许多项目来说,面向对象是非常有益的。