众所周知,在面向对象的软件开发中,通过对类的封装和抽象,可以对类进行继承,从而实现代码复用和增加软件的可维护性。那么,窗体能不能继承呢?在重构机房收费系统的时候遇到了如下问题:
相似的几个功能,窗体布局一模一样,如果使用以前的方法,只能复制、粘贴这些窗体和控件,可是,控件可以复制,名字却不能复制;如果改其中的一项数据的话,需要修改好几个窗体,可维护性差;U层、B层、D层代码相似度很高,代码复用性太低等等。怎么解决这些问题呢?
类可以进行抽象、封装,然后可以继承以复用,窗体能不能看做一个特殊的类?窗体和控件的属性是这个窗体类的属性,窗体和控件的事件就是这个窗体类的部分方法。完成了窗体类的抽象,就可以对这个窗体进行继承了。
具体实现过程:
1 按照正常步骤创建父窗体;创建原则是把需要复用的控件布置好;需要复用的代码也写到父类中;不需要复用的代码即子窗体不相同的数据需要在父窗体中写成虚函数。
父窗体如下:
2实现窗体复用功能代码:(以收取金额和退换金额为例)
首先是实体层代码,实体层代码包括所有父窗体和子窗体需要使用到的变量(不一定跟数据库相同)。这里就不再啰嗦。
U层代码:实现接收用户输入数据和输出数据功能。
Public Class frmMDICheckCash '退出 Private Sub btnQuit_Click(sender As Object, e As EventArgs) Handles btnQuit.Click Me.Close() End Sub '导出为Excel,这里将导出EXCEL功能做成了一个模块,直接调用即可 Private Sub btnExport_Click(sender As Object, e As EventArgs) Handles btnExport.Click Call ExportExcel.ExportExcel(DataGridView1) End Sub '查询数据方法 Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click '实例化一个实体类 Dim enmdicheckcash As New Charge.Entity.MDICheckCash enmdicheckcash.StartTime = CStr(DateTimePicker1.Text.Trim) '获得两个日期字符串 enmdicheckcash.OverTime = CStr(DateTimePicker2.Text.Trim) '将表名作为一个虚函数 enmdicheckcash.SearchTable = GetTable() '实例化B层类 Dim mdicheckcash As New Charge.BLL.MDICheckCash Dim myList As List(Of Charge.Entity.MDICheckCash) myList = mdicheckcash.CheckCash(enmdicheckcash) '通过B层返回值判断是否查询成功 If myList.Count = 0 Then MsgBox("信息查询错误,请重新选择!") Else DataGridView1.DataSource = myList End If End Sub '将数据库的表名作为一个虚函数,因为子窗体需要查询的数据表不同 Protected Overridable Function GetTable() As String Return "" '子窗体需要继承并实现这个方法 End Function End Class
代码分析:两个子窗体功能不同,需要使用的数据表也不一样,所以要把表名作为一个变量,变量需要在U层赋值,所以需要做成实体。U层把获得表名作为一个虚方法,运行的时候进行赋值,然后将表名放到实体中,在D层使用。
因为B层、接口层和工厂层代码没有特殊之处,这里不再展示。
接下来是D层代码:
Imports System.Data.SqlClient Imports System.Configuration Imports Charge.DAL.sqlHelper Public Class SQLServerMDICheckCash : Implements Charge.IDAL.IMDICheckCash Public Function SelectInfo(enmdicheckcash As Charge.Entity.MDICheckCash) As List(Of Charge.Entity.MDICheckCash) Implements IDAL.IMDICheckCash.SelectInfo Dim help As New Charge.DAL.sqlHelper Dim strSQL As String Dim median As DataTable '存储过程调用 strSQL = "PROC_MDICheckCash" Dim sqlPara As SqlParameter() = { New SqlParameter("@TableName", enmdicheckcash.SearchTable), New SqlParameter("@StartTime", enmdicheckcash.StartTime), New SqlParameter("@OverTime", enmdicheckcash.OverTime) } median = help.ParaSelect(strSQL, CommandType.StoredProcedure, sqlPara) '将表格转换为实体集合返回B层 Dim myList As List(Of Charge.Entity.MDICheckCash) Dim mhelp As New Charge.Entity.ModeHelper myList = mhelp.convertToList(Of Charge.Entity.MDICheckCash)(median) Return myList End Function End Class
代码分析:D层带式实现了数据库查询功能,这里调用了sqlhelper和实体转换模块,并且使用存储过程来实现数据查询功能。存储过程使用了三个参数,表名和两个日期,最后返回一个实体集合。
创建子窗体:在创建子窗体的时候需要选择“继承的窗体”,然后选择继承的父窗体即可。子窗体代码:
Public Class frmCollectCash Dim enmdicheckcash As New Charge.Entity.MDICheckCash Protected Overrides Function GetTable() As String ' 给实体赋值 enmdicheckcash.SearchTable = "dbo.Recharge_Info" Return enmdicheckcash.SearchTable End Function End Class
代码分析:子窗体只需要实现父窗体中的虚方法即可,这里只有一个虚方法(获得表名),直接复制即可实现。
存储过程代码不予展示。
总结:窗体的继承大大减少了工作量,增加了系统的可维护性,减少了代码的冗余,但是也使系统的结构更加复杂。
思想:遇到问题不可怕,只要分析得当,总会找到解决方法的。