有课程或模块的目的是什么?我读到的所有东西都试图告诉我它是什么,但不是它的用途.我为什么要做一个?
我读到的所有内容似乎都是对阅读教程的人做出假设,好像我知道的很多.
>将数据存储在谨慎的项目中
>组织您的代码
>在代码中提供接缝
>将代码划分为多个层并支持n层
现在,让我们更详细地看一下每一个:
将数据存储在Discreet项目中
通常,您需要存储关于单个项目的多个数据,并将这些数据作为单个对象在方法之间传递.例如,如果您编写的应用程序适用于某个人,您可能希望存储有关此人的多个数据,例如他们的姓名,年龄和头衔.显然,您可以将这三个数据存储为三个单独的变量,并将它们作为单独的参数传递给方法,例如:
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
当然,名称(3)和年龄(3)都存储同一个人的数据并不是很明显.这是你必须要知道的,或者你必须在评论中写下它,所以你不要忘记.但是,当您有一个类来存储一个人的所有数据时,这会更清晰,更容易做到:
Dim persons(9) As Person
现在,很明显人(3).姓名和人(3).年龄都是同一个人的数据.通过这种方式,它是自我记录的.无需评论即可澄清您的逻辑.结果,代码将不再容易出错.
通常,类不仅包含特定项的数据,还包含对该数据起作用的方法.这是一种方便的机制.例如,您可能希望将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
现在假设你想为每个人存储多个昵称.你可以很容易地看到,人(3).Nicknames(0)比一些疯狂的二维数组简单得多,比如昵称(3)(0).如果您需要存储关于每个昵称的多个数据,会发生什么?正如您所看到的,不使用类会很快变得混乱.
组织您的代码
当你编写一个冗长的程序时,如果你没有正确组织你的代码,它会很快变得非常混乱并导致非常错误的代码.在与意大利面条代码的斗争中,你最重要的武器就是创造更多的课程.理想情况下,每个类只包含逻辑上彼此直接相关的方法.每种新类型的功能都应该分解为一个新命名的类.在一个大型项目中,这些类应该进一步组织成单独的命名空间,但如果你不至少将它们分成几个类,那么你真的会弄得一团糟.例如,假设您将以下方法全部投入到同一模块中:
> GetPersonDescription
> GetProductDescription
> FirePerson
> SellProduct
我相信你会同意,如果将这些方法分解为单独的类,例如:
>人
> GetDescription
>火
>产品
> GetDescription
>卖
这只是一个非常非常简单的例子.当你有成千上万的方法和变量处理许多不同的项目和不同类型的项目时,我相信你可以很容易地想象为什么类对于帮助组织和自我记录代码很重要.
在您的代码中提供接缝
这个可能会更高级,但它非常重要,所以我会尝试用简单的术语来解释它.假设您创建了一个trace-logger类,它将日志条目写入跟踪日志文件.例如:
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类.这是可能的,因为您在逻辑中创建了条目格式和条目写入之间的接缝.
格式不关心条目的记录方式.它关心的是如何格式化条目.当它需要编写和输入时,它只是要求一个单独的编写器对象来完成这部分工作.作者如何以及在内部实际做什么是无关紧要的.类似地,编写器不关心条目是如何格式化的,它只是期望传递给它的任何东西都是需要记录的已经格式化的有效条目.
您可能已经注意到,TraceLogger现在不仅可以重用于写入任何类型的日志,而且编写器可以重用于将任何类型的日志写入这些类型的日志.例如,您可以重用DatabaseLogWriter来编写跟踪日志和异常日志.
关于依赖注入的一点关注
只是幽默我,一点点,因为我对这个问题做了一些关于我的重要事情的答案……在最后一个例子中,我使用了一种称为依赖注入(DI)的技术.它被称为依赖注入,因为writer对象是logger类的依赖项,并且依赖项对象通过构造函数注入到logger类中.通过这样做,您可以在没有依赖注入的情况下完成类似的事情:
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类,你可以只为你的测试使用一个假的编写器对象,它只记录到内存,控制台或其他东西.
一旦将代码正确组织到单独的类中,每个类只负责一种类型的任务,那么您就可以开始将类组合在一起.图层只是代码的高级组织.语言中没有任何特定内容可以使技术上的某个层成为一个层.由于语言中没有任何内容可以清楚地表明每个层的开始和结束位置,因此人们通常会将每个层的所有类放入不同的名称空间中.因此,您可能拥有看起来像这样的名称空间(每个名称空间是一个单独的图层):
> MyProduct.Presentation
> MyProduct.Business
> MyProduct.DataAccess
通常,您始终希望代码中至少包含两个层:表示层或用户界面层以及业务逻辑层.如果您的应用程序执行任何数据访问,那么通常也会将其放在自己的层中.每层应尽可能独立且可互换.因此,如果上面示例中的TraceLogger类位于业务层中,则它应该可以通过任何类型的UI重用.
通过提供进一步的组织,自我记录,可重用性和稳定性,层可以扩展所有以前的主题.但是,图层的另一个主要好处是可以更轻松地将应用程序拆分为多个层.例如,如果您需要将业务和数据访问逻辑移动到Web服务中,那么如果您已将代码干净地写入已定义的层中,则这样做非常简单.但是,如果所有这些逻辑混合在一起并相互依赖,那么尝试将数据访问和业务逻辑分解为单独的项目将是一场噩梦.
我要说的结束
简而言之,您永远不需要创建多个类或模块.始终可以将整个应用程序编写在单个类或模块中.毕竟,在面向对象语言被发明之前,开发了整个操作系统和软件套件.但是,面向对象编程(OOP)语言如此受欢迎是有原因的.对于许多项目,面向对象非常有益.