Практическое применение наследования, полиморфизма, интерфейсов, дженериков и делегатов на примерах в Visual Basic .NET
На этой страничке я опишу пять практических приемов обьектного программирования на Visual Basic .NET:
- Interface - Комплексный пример типизации нерегулярных данных в обьекты с общим интерфейсом
- Generic - единый алгоритм, который должен отработать на различных обьектах
- Polimorfism - полиморфные функции, когда движок сам выбирает нужный вызов функции в зависимости от заданных при вызове параметров
- Inherits - построение пирамиды классов, когда каждый следующий класс пользуется обьектами, определенными в нижестоящих классах
- Delegate - библиотечные функции, позволяющие пользователю определять собственные callback-функции, вызываемым при обработке каждой строки данных
Interface
В этом разделея опишу сначала один свой промежуточный класс в одном своем сайте. Этот класс является одним из примерно сотни классов этого моего сайта, в котором есть много чего - начиная от виртуальных клавиатур, программируемых гугловских карт, динамически формируемых флеш-роликов и много-много еще чего. Из всего этого большого сайта я тут опишу маленький, но весьма поучительный компонент. Полагаю, для начинающих это будет весьма полезное чтиво - ибо в нем есть все - начиная от XQUERY выражений на уровне SQL и кончая интерфейсами и дженериками.
Как всегда - этот класс был написан достаточно быстро - за один вечер. Правда для этого потребовалась вся предыдущая жизнь.
Итак, вначале постановка задачи на этот промеджуточный класс.
На входе мы имеем сайт киберплата. На сайте Киберплата можно сгрузить этот список организаций, с которыми заключил договора Киберплат (в виде XML). Далее некий мой парсер (который мы тут рассматривать не будем) - парсит этот XML и загружает его в базу примерно вот в таком виде.
Для чего он это делает? А для скорости. Ну не будем же мы парсить огромнейший XML при каждом реквесте. Значит нужна предварительная обработка этого XML. Это и делает тот мой парсер, описание которого здесь приведено не будет - он сгружает WebClient.zip с сайта киберплата, раскрывает его и превращает его в тот вид, что вы увидели. Правда, вы увидели совсем сырой вид в базе - работать с ним совершенно невозможно.
А вот если мы посмотрим на этот мусор через призму вот такой XML-вьюшки, то все сразу меняется - вместо кучи XML-мусора мы получим отличную реляционную базу, вполне подходящую для работы любого сайта. И база эта будет как раз в отличном и удобном виде перечень всех организаций, с которыми киберплат заключил договора (и платежи в адрес которых мы можем принять со своего сайта).
Итак, мы увидели, что описываемый тут класс имеет на входе. Теперь посмотрим, что бы нам хотелось бы от него получить.
Надо понимать, что вообще-то даже этот небольшой платежный фрагмент этого моего сайта состоит не только из парсера, описание которого мы опустили, но и из множества-множества других компонентов, например модуля приложения цифровой подписи - который тут тоже описан не будет. Именно прилагаемая цифровая подпись позволит киберплату понять, от кого именно он получил требование на перевод денег. И при наличии депозита в нужном размере - киберплат выполнит перевод.
Итак, наш класс - принимая на вход переработанные определения с сайта КИберплата, должен сформировать на выходе некую хитро сформированную строку, которая в дальнейшем будет скормлена модулю приложения цифровой подписи, и уже с ней все это будет скормлено следующим компонентам. Технология формирования этой хитрой строки, которая скармливается модулю формирования цифровой подписи - тоже описана в тех же самых полученных от киберплата XML-данных.
Теперь, когда понятно место этого класса в информационном потоке - надо понять поток управления. А именно откуда и кто вызывает этот класс.
Этот класс вызывается со страничек платежного сайта. Конечно сами странички мы тут рассматривать не будем. Это громоздкие программы, в которых накручено тоже много чего, начиная от специфической аутентификации и кончая специфическими виртуальными клавиатурами. Мы не будем рассматривать также взаимодествие страниц сайта между собой и структуру постбеков внутри каждой сранички - это все тоже нетривиально сделано и, в принципе, является коммерческой тайной, как и все остальное что мы тут опустили - начиная от импортера, кончая модулем приложения цифровой подписи. Но мы посмотрим те самые несколько срок на страничках, которые управляют описываемым тут классом. И позволяют ему сформировать требуемую выходную строку на основе входных данных по обращениям со страниц сайта.
И прежде всего нам придется определится с важнейшим требованием к нашему классу - он должен ТИПИЗИРОВАННО работать с нечеткими структурами, описанными в XML. Именно это и является определяющим при программировании этого класса - ибо все остальное здесь - это простая тривиальная тягомотина.
Итак, класс должен хранить В СЕБЕ все, что позволит ему в какой-то момент сериализовать это в строку в нужном нам формате. Значит, в этом плане - это бизнес-обьект, более всего похожий на вот такой или вот такой.
C другой стороны, класс должен хранить В СЕБЕ всю технологию работы с этим самым XML, содержащим все определения киберплата (ну не будем же мы размазывать работу с этим XML по множеству приложений и множеству человек?). С третьей стороны, поскольку как становится понятно из определений киьерплата - описание платежа включает поля типа ENUM и INT, поля типа MASKED и TEXT. Следовательно, класс должен хранить в себе определение каждого из этих типов полей. И понятное дело, таких полей любого из этих четырех типов может быть множество.
Вот и вся постановка задачи на программирование. Остается только СДЕЛАТЬ это типизированно и УДОБНО для вызовов со страничек.
Итак, мы подошли собственно к технологии построение такого рода кода. Обратите внимание, что все начинается с определения четырех типо необходимых нам полей. Далее мы определяем общий интерфейс для каждого из типов полей. Если этого не сделать - то вызов со страничек нешего класса не будет похожим для любого из типов полей. И мы не получим Intellisense. Далее каждое из типов полей просто наследует интерфейс CyberCommonFields. Обратите внимание также на полиморфный конструтор - который сработает по своей ветке NEW в зависимости от типа поля CyberFieldsType. И далее собственно в каждом из типо реализуем метод хранения заданных со старнички данных:
00217: ''' <summary> 00218: ''' все начитается с четырех типов полей киберплата 00219: ''' </summary> 00220: Public Enum CyberFieldsType 00221: _Enum_ = 1 00222: _Int_ = 2 00223: _Masked_ = 3 00224: _Text_ = 4 00225: End Enum 00226: 00227: ''' <summary> 00228: ''' все определения полей киберплата делают это 00229: ''' </summary> 00230: Public Interface CyberCommonFields 00231: Function SetUserValue(ByVal Text As String) As Boolean 00232: ReadOnly Property ID() As Integer 00233: ReadOnly Property Name() As String 00234: ReadOnly Property UserValue() As String 00235: End Interface 00236: 00237: ''' <summary> 00238: ''' Определение одного поля параметра платежа партнера киберплата 00239: ''' </summary> 00240: Public Class OneCyberField 00241: Public ReadOnly FieldType As CyberFieldsType 00242: Public ReadOnly FieldDefinition As CyberCommonFields 00243: Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberInt) 00244: FieldType = _FieldType 00245: FieldDefinition = _FieldDefinition 00246: End Sub 00247: Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberMasked) 00248: FieldType = _FieldType 00249: FieldDefinition = _FieldDefinition 00250: End Sub 00251: Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberText) 00252: FieldType = _FieldType 00253: FieldDefinition = _FieldDefinition 00254: End Sub 00255: Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberEnum) 00256: FieldType = _FieldType 00257: FieldDefinition = _FieldDefinition 00258: End Sub 00259: End Class 00260: 00261: ''' <summary> 00262: ''' определение целого числа как параметра платежа и сохраненненное сюда значение целого числа, введенного юзером 00263: ''' </summary> 00264: Public Class CyberInt 00265: Implements CyberCommonFields 00266: Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID 00267: Get 00268: Return ID_ 00269: End Get 00270: End Property 00271: Public ReadOnly Property Name() As String Implements CyberCommonFields.Name 00272: Get 00273: Return Name_ 00274: End Get 00275: End Property 00276: Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue 00277: Get 00278: Return UserValue_ 00279: End Get 00280: End Property 00281: 00282: Dim ID_ As Integer 00283: Dim Name_ As String 00284: Public ReadOnly MinLength As Integer 00285: Public ReadOnly MaxLength As Integer 00286: Public ReadOnly Comment As String 00287: Dim UserValue_ As String 00288: 00289: Public Sub New(ByVal _ID As Integer, ByVal _Name As String, ByVal _MinLength As Integer, ByVal _MaxLength As Integer, ByVal _Comment As String) 00290: ID_ = _ID 00291: Name_ = _Name 00292: MinLength = _MinLength 00293: MaxLength = _MaxLength 00294: Comment = _Comment 00295: End Sub 00296: 00297: Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue 00298: If IsNumeric(Text) Then 00299: Dim X As Integer = CInt(Text) 00300: If X.ToString.Length <= MaxLength And X.ToString.Length >= MinLength Then 00301: UserValue_ = X.ToString 00302: Return True 00303: Else 00304: Return False 00305: End If 00306: Else 00307: Return False 00308: End If 00309: End Function 00310: End Class 00311: 00312: ''' <summary> 00313: ''' определение маскированного поля как параметра платежа и сохраненненное сюда значение, введенного юзером 00314: ''' </summary> 00315: Public Class CyberMasked 00316: Implements CyberCommonFields 00317: 00318: Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID 00319: Get 00320: Return ID_ 00321: End Get 00322: End Property 00323: Public ReadOnly Property Name() As String Implements CyberCommonFields.Name 00324: Get 00325: Return Name_ 00326: End Get 00327: End Property 00328: Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue 00329: Get 00330: Return UserValue_ 00331: End Get 00332: End Property 00333: 00334: Dim ID_ As Integer 00335: Dim Name_ As String 00336: Public ReadOnly Mask As String 00337: Public ReadOnly Comment As String 00338: Dim UserValue_ As String 00339: 00340: Public Sub New(ByVal _ID As Integer, ByVal _Name As String, ByVal _Mask As String, ByVal _Comment As String) 00341: ID_ = _ID 00342: Name_ = _Name 00343: Mask = _Mask 00344: Comment = _Comment 00345: End Sub 00346: 00347: ''' <summary> 00348: ''' cырой перенос текста с маской в значение поля 00349: ''' </summary> 00350: Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue 00351: UserValue_ = Text 00352: End Function 00353: 00354: ''' <summary> 00355: ''' вставка символов в маску выполняется этим методом 00356: ''' </summary> 00357: Public Function SetUserValue1(ByVal RawText As String) As Boolean 00358: Dim Mask1 As New StringBuilder(Mask) 00359: Dim j As Integer = Mask.IndexOf("*") 'индекс по маске 00360: For i As Integer = 0 To RawText.Length 'индекс по переданной текстовой строке 00361: Asteric: 00362: If Mask1(j) = "*" Then 00363: Mask1.Replace("*", RawText(i), j, 1) 00364: If j >= Mask1.Length - 1 Then 00365: Exit For 00366: Else 00367: j += 1 00368: End If 00369: Else 00370: If j >= Mask1.Length - 1 Then 00371: Exit For 00372: Else 00373: j += 1 00374: GoTo Asteric 00375: End If 00376: End If 00377: Next 00378: UserValue_ = Mask1.ToString 00379: Return True 00380: End Function 00381: 00382: End Class 00383: 00384: 00385: 00386: ''' <summary> 00387: ''' определение текстового поля как параметра платежа и сохраненненный сюда текст, введенный юзером 00388: ''' </summary> 00389: Public Class CyberText 00390: Implements CyberCommonFields 00391: 00392: Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID 00393: Get 00394: Return ID_ 00395: End Get 00396: End Property 00397: Public ReadOnly Property Name() As String Implements CyberCommonFields.Name 00398: Get 00399: Return Name_ 00400: End Get 00401: End Property 00402: Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue 00403: Get 00404: Return UserValue_ 00405: End Get 00406: End Property 00407: 00408: Dim ID_ As Integer 00409: Dim Name_ As String 00410: Public ReadOnly Klava As String 00411: Public ReadOnly Comment As String 00412: Dim UserValue_ As String 00413: 00414: Public Sub New(ByVal _ID As Integer, ByVal _Name As String, ByVal _Klava As String, ByVal _Comment As String) 00415: ID_ = _ID 00416: Name_ = _Name 00417: Klava = _Klava 00418: Comment = _Comment 00419: End Sub 00420: 00421: Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue 00422: UserValue_ = Text 00423: End Function 00424: 00425: End Class 00426: 00427: ''' <summary> 00428: ''' определение перечисления как параметра платежа партнера киберплата 00429: ''' </summary> 00430: Public Class CyberEnum 00431: Implements CyberCommonFields 00432: 00433: Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID 00434: Get 00435: Return ID_ 00436: End Get 00437: End Property 00438: Public ReadOnly Property Name() As String Implements CyberCommonFields.Name 00439: Get 00440: Return Name_ 00441: End Get 00442: End Property 00443: Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue 00444: Get 00445: Return UserValue_ 00446: End Get 00447: End Property 00448: 00449: Dim ID_ As Integer 00450: Dim Name_ As String 00451: Dim UserValue_ As String 00452: Public ReadOnly EnumFields As System.Collections.Generic.List(Of CyberEnumItem) 00453: 00454: Public Sub New(ByVal _ID As Integer, ByVal _Name As String) 00455: EnumFields = New System.Collections.Generic.List(Of CyberEnumItem) 00456: ID_ = _ID 00457: Name_ = _Name 00458: End Sub 00459: 00460: Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue 00461: For Each One As CyberEnumItem In EnumFields 00462: If Text = One.Value Then 00463: UserValue_ = Text 00464: Return True 00465: End If 00466: Next 00467: Return False 00468: End Function 00469: End Class 00470: 00471: ''' <summary> 00472: ''' определение одного элемента перечисляемого поля 00473: ''' </summary> 00474: Public Class CyberEnumItem 00475: Public ReadOnly Text As String 00476: Public ReadOnly Value As String 00477: Public Sub New(ByVal _Text As String, ByVal _Value As String) 00478: Text = _Text 00479: Value = _Value 00480: End Sub 00481: End Class 00482:
Теперь нам нужен метод, который возьмет в статические переменные нашего класса то, что было возможно распарсить предварительным парсером и что теперь видно через XML-вьюшку. Как уже говорилось - теперь парсить весь огромный XML не требуется и мы берем из реляционной структуры одну сроку с номером оператора:
Я определил этот класс конструктором - ибо пока нету определения полей, требуемых для платежа неким конкретным оператором - нету и темы работы этого класса вообще.
00001: ''' <summary> 00002: ''' Представляет собой описание платежа в адрес ОДНОГО заданного контрагента, 00003: ''' (зарегистрированного в КиберПлата и описанного в operators.xml) 00004: ''' </summary> 00005: Public Class CyberPlat 00006: 'До этого уровня описание партнеров Киберплата распарсил сам SQL 00007: ' 00008: 'select ID,GroupID,CyberID, 00009: 'Node.value('/operator[1]/name[1]', 'nvarchar(256)') as [name], 00010: 'Node.value('/operator[1]/name_for_cheque[1]', 'nvarchar(256)') as [name_for_cheque], 00011: 'Node.value('/operator[1]/inn_for_cheque[1]', 'nvarchar(256)')as [inn_for_cheque], 00012: 'Node.value('/operator[1]/comission[1]', 'nvarchar(256)')as [comission], 00013: 'Node.value('/operator[1]/image[1]', 'nvarchar(256)')as [image], 00014: 'Node.value('/operator[1]/rootmenuimage[1]', 'nvarchar(256)')as [rootmenuimage], 00015: 'Node.query('/operator/fields')as [fields], 00016: 'Node.query('/operator/processor')as [processor] 00017: 'from dbo.MobileOperator 00018: ' 00019: 'эти поля напрямую переносим в описание порядка платежа Киберплата 00020: ' 00021: Public ReadOnly ID As Integer 00022: Public ReadOnly GroupID As Integer 00023: Public ReadOnly CyberID As Integer 00024: Public ReadOnly Name As String 00025: Public ReadOnly Name_for_cheque As String 00026: Public ReadOnly Inn_for_cheque As String 00027: Public ReadOnly Comission As String 00028: Public ReadOnly MinSumm As Decimal 00029: Public ReadOnly MaxSumm As Decimal 00030: Public PaySumm As Decimal 'сумма платежа 00031: Public ReadOnly Image As String 00032: Public ReadOnly RootMenuImage As String 00033: Public ReadOnly Fields_RAW As New System.Xml.XmlDocument 00034: Public ReadOnly Processor_RAW As New System.Xml.XmlDocument 00035: 00036: Public Sub New(ByVal OperatorID As Integer) 00037: Dim ReadOneDescriptionsCMD As VBNET2000.ExecSQL_RDR = New VBNET2000.ExecSQL_RDR("@id", OperatorID) 00038: Dim RDR1 As Data.SqlClient.SqlDataReader = ReadOneDescriptionsCMD.ExecSQL("select * from GISIS.dbo.GetOperator where [ID]=" & OperatorID, Data.CommandType.Text, Data.CommandBehavior.SingleRow) 00039: If RDR1.Read Then 00040: ID = IIf(IsDBNull(RDR1("ID")), 0, RDR1("ID")) 00041: GroupID = IIf(IsDBNull(RDR1("GroupID")), 0, RDR1("GroupID")) 00042: CyberID = IIf(IsDBNull(RDR1("CyberID")), 0, RDR1("CyberID")) 00043: Name = IIf(IsDBNull(RDR1("Name")), "", RDR1("Name")) 00044: Name_for_cheque = IIf(IsDBNull(RDR1("Name_for_cheque")), "", RDR1("Name_for_cheque")) 00045: Inn_for_cheque = IIf(IsDBNull(RDR1("Inn_for_cheque")), "", RDR1("Inn_for_cheque")) 00046: Comission = IIf(IsDBNull(RDR1("Comission")), "", RDR1("Comission")) 00047: MinSumm = IIf(IsDBNull(RDR1("min")), "", RDR1("min")) 00048: MaxSumm = IIf(IsDBNull(RDR1("max")), "", RDR1("max")) 00049: Image = IIf(IsDBNull(RDR1("Image")), "", RDR1("Image")) 00050: RootMenuImage = IIf(IsDBNull(RDR1("RootMenuImage")), "", RDR1("ID")) 00051: Fields_RAW.LoadXml(IIf(IsDBNull(RDR1("Fields")), "", RDR1("Fields"))) 00052: Processor_RAW.LoadXml(IIf(IsDBNull(RDR1("Processor")), "", RDR1("Processor"))) 00053: End If 00054: ReadOneDescriptionsCMD.Close() 00055: End Sub
Теперь нам нужен будет еще метод, который глубоко распрсит XML - cодержащий определение требуемых полей платежа. Тех, что должен ввести пользователь нашего сайта.
00057: Public Function LoadFieldsDefinition() As Collections.Generic.List(Of OneCyberField) 00058: Dim AllFields As System.Xml.XmlNodeList = Fields_RAW.SelectNodes("/fields/field") 00059: For Each OneField As System.Xml.XmlNode In AllFields 00060: Dim FieldType As String = OneField.Attributes("type").InnerText 00061: Dim CurrentNode As CyberFieldsType 00062: Select Case FieldType 00063: Case "text" : CurrentNode = CyberFieldsType._Text_ 00064: Dim ID As Integer 00065: Dim Name As String 00066: Dim Klava As String 00067: Dim Comment As String 00068: If OneField.Attributes("id") IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0 00069: If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = "" 00070: If OneField.Attributes("klava") IsNot Nothing Then Klava = OneField.Attributes("klava").InnerText Else Klava = "" 00071: If OneField.SelectSingleNode("comment") IsNot Nothing Then Comment = OneField.SelectSingleNode("comment").InnerText Else Comment = "" 00072: Dim OneTextFiekds As New CyberText(ID, Name, Klava, Comment) 00073: Fields.Add(New OneCyberField(CurrentNode, OneTextFiekds)) 00074: Case "integer" : CurrentNode = CyberFieldsType._Int_ 00075: Dim ID As Integer 00076: Dim Name As String 00077: Dim MinLength As Integer 00078: Dim MaxLength As Integer 00079: Dim Comment As String 00080: If OneField.Attributes("id").InnerText IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0 00081: If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = "" 00082: If OneField.Attributes("maxlength") IsNot Nothing Then MaxLength = OneField.Attributes("maxlength").InnerText Else MaxLength = 0 00083: If OneField.Attributes("minlength") IsNot Nothing Then MinLength = OneField.Attributes("minlength").InnerText Else MinLength = 0 00084: If OneField.SelectSingleNode("comment") IsNot Nothing Then Comment = OneField.SelectSingleNode("comment").InnerText Else Comment = "" 00085: Dim OneIntFields As New CyberInt(ID, Name, MinLength, MaxLength, Comment) 00086: Fields.Add(New OneCyberField(CurrentNode, OneIntFields)) 00087: Case "masked" : CurrentNode = CyberFieldsType._Masked_ 00088: Dim ID As Integer 00089: Dim Name As String 00090: Dim Mask As String 00091: Dim Comment As String 00092: If OneField.Attributes("id") IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0 00093: If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = "" 00094: If OneField.SelectSingleNode("mask") IsNot Nothing Then Mask = OneField.SelectSingleNode("mask").InnerText Else Mask = "" 00095: If OneField.SelectSingleNode("comment") IsNot Nothing Then Comment = OneField.SelectSingleNode("comment").InnerText Else Comment = "" 00096: Dim OneMaskedField As New CyberMasked(ID, Name, Mask, Comment) 00097: Fields.Add(New OneCyberField(CurrentNode, OneMaskedField)) 00098: Case "enum" : CurrentNode = CyberFieldsType._Enum_ 00099: Dim ID As Integer 00100: Dim Name As String 00101: If OneField.Attributes("id") IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0 00102: If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = "" 00103: Dim OneEnumFields As New CyberEnum(ID, Name) 00104: Dim EnumNodes As System.Xml.XmlNodeList = OneField.SelectNodes("enum/item") 00105: For Each OneEnum As System.Xml.XmlNode In EnumNodes 00106: Dim Text As String = OneEnum.InnerText 00107: Dim Value As String = OneEnum.Attributes("value").Value.ToString 00108: Dim OneCyberEnumItem As New CyberEnumItem(Text, Value) 00109: OneEnumFields.EnumFields.Add(OneCyberEnumItem) 00110: Next 00111: Fields.Add(New OneCyberField(CurrentNode, OneEnumFields)) 00112: End Select 00113: Next 00114: Return Fields 00115: End Function
Вот здесь-то весь ключик этого класса. Этот метод возвращает КОЛЛЕКЦИЮ ТЕХ САМЫХ АТОМАРНЫХ КЛАССОВ, которые мы определили как первичные элементарные хранители информации в полях ENUM-TEXT-MASKED-INT. А поскольку там был определен ЕДИНЫЙ INterface для каждого из четырех типов полей - здесь у нас получится определить GENERIC-коллекцию:
00117: ''' <summary> 00118: ''' Это собственно распарсенное поле параметров платежей в адрес одного оператора КиберПлата 00119: ''' </summary> 00120: Public ReadOnly Fields As New Collections.Generic.List(Of OneCyberField)
Далее нам потребуется хранитель итератора по элементам этой коллекции
00121: ''' <summary> 00122: ''' Количество уже обработанных полей в коллекции Fields. В обработанных полях содержатся введенные значения 00123: ''' </summary> 00124: Public ReadOnly Property EnterFieldsCount() As Integer 00125: Get 00126: Return EnterFieldsCount_ 00127: End Get 00128: End Property 00129: Dim EnterFieldsCount_ As Integer 00130: ''' <summary> 00131: ''' переход к заполнению следующего поля 00132: ''' </summary> 00133: Public Function NextFields() As Boolean 00134: If EnterFieldsCount < Fields.Count - 1 Then 00135: EnterFieldsCount_ += 1 00136: Return True 00137: Else 00138: Return False 00139: End If 00140: 00141: End Function 00142:
И наконец функция, которая выполнит сериализацию хранимых в полях CyberField данныъ по правилам, которые мы вычитали конструктором данного класса при создания определения платежа по данному оператору.
00155: ''' <summary> 00156: ''' Функция упаковывающая параметры платежа в формат необходимый процедуре 00157: ''' </summary> 00158: Public Function PackParam() As String 00159: Dim Result As New System.Text.StringBuilder 00160: Dim AllProperty As System.Xml.XmlNodeList = Processor_RAW.SelectNodes("/processor/payment/request-property") 00161: 'каждый узел здесь может либо сразу ссылатся на Field либо представлять собой сборное поле из непосредственных и ссылочных полей 00162: For Each OneProperty As System.Xml.XmlNode In AllProperty 00163: Dim PropertyName As String = OneProperty.Attributes("name").InnerText 00164: Dim ImediateRefAttr As System.Xml.XmlAttribute = OneProperty.Attributes("field-id") 00165: If ImediateRefAttr IsNot Nothing Then 00166: 'это поле включаем в результат сразу - только найти надо источник его данных 00167: Result.Append(PropertyName & "=") 00168: For Each OneFields As OneCyberField In Fields 00169: If OneFields.FieldDefinition.ID = CInt(ImediateRefAttr.InnerText) Then 00170: Result.Append(OneFields.FieldDefinition.UserValue) 00171: GoTo OK1 00172: End If 00173: Next 00174: Throw New Exception("Ошибка. Полю " & PropertyName & " не сопоставлено определения (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.") 00175: OK1: 00176: Else 00177: 'в таком поле могут быть непосредственные и референсные поля 00178: Result.Append(PropertyName & "=") 00179: For Each OneFixed As System.Xml.XmlNode In OneProperty.ChildNodes 00180: Dim FixedType As String = OneFixed.Name 00181: If FixedType = "field-ref" Then 00182: Dim FixedImediateRef As String = OneFixed.Attributes("id").InnerText 00183: If FixedImediateRef = "" Then 00184: Throw New Exception("Ошибка. Поле " & PropertyName & " не содержит ID ссылки на поле (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.") 00185: Else 00186: 'это поле можно включить в результат - только найти надо источник его данных 00187: For Each OneFields As OneCyberField In Fields 00188: If OneFields.FieldDefinition.ID = CInt(FixedImediateRef) Then 00189: Result.Append(OneFields.FieldDefinition.UserValue) 00190: GoTo OK2 00191: End If 00192: Next 00193: Throw New Exception("Ошибка. Полю " & PropertyName & " не сопоставлено определения (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.") 00194: OK2: ' 00195: End If 00196: ElseIf FixedType = "fixed-text" Then 00197: 'а содержимое такого поля включаем сразу непосредственно из его определения 00198: Dim FixedText As String = OneFixed.Attributes("id").InnerText 00199: If FixedText = "" Then 00200: Throw New Exception("Ошибка. Поле " & PropertyName & " содержит пустое значение в качестве непосредственно включаемого параметра (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.") 00201: Else 00202: Result.Append(FixedText) 00203: End If 00204: Else 00205: Throw New Exception("Ошибка. Поле " & PropertyName & " содержит необрабатываемый тип параметра (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.") 00206: End If 00207: Next 00208: End If 00209: Result.Append("\n") 00210: Next 00211: Result.Append("\n") 00212: Return Result.ToString 00213: End Function 00214: 00215: End Class 00216:
Ну и небольшая отладочная функция, которая позволит нам снаружи проконтролировать - что же там происходит в недрах этого класса.
00143: ''' <summary> 00144: ''' Выполнение платежа 00145: ''' </summary> 00146: Public Function TestPay() As String 00147: Dim PayParm As New StringBuilder("(") 00148: For Each One As OneCyberField In Fields 00149: PayParm.Append("FieldType='" & One.FieldType.ToString & "': Name='" & One.FieldDefinition.Name & "', Value='" & One.FieldDefinition.UserValue & "'; ") 00150: Next 00151: PayParm.Append(")") 00152: Return ("Платеж " & PaySumm.ToString & " руб, OperatorID=" & ID & ", Name='" & Name & "', Fields.Count=" & Fields.Count & ", Введено полей='" & EnterFieldsCount & "', Parm=" & PayParm.ToString) 00153: End Function 00154:
Как видите - в чем тут ключик? В создании ЕДИНОГО ИНТЕРФЕЙСА для любого из четырех типов полей (строки 230-235). С одной стороны это позволяет нам определять КОЛЛЕКЦИЮ (типа GENERIC) на полях соверщенно разных типов (строка 120). А далее двигатся по элементам этой коллекции единым образом (этот фрагмент перехода по требуемым полями платежа - элементам нашей GENERIC-коллекции - вы можете рассмотреть на рисунке ниже). Все определения каждого типа являюся реализацией этого интрерфейса (строки 264-482). Такое решение дает нам ПОДСКАЗКУ на страничках и единый интерфейс для обращения к нашему классу со страниц - в котором как раз и видны наши общие, определенные в интерфейсе методы:
Generic
И теперь мы рассмотрим второй весьма поучительный пример применения дженериков. Пример, в котором узнаем что такое КОНТРЕЙНТСЫ, и как их находить. В этом примере мы не будем создавать несколько GENERIC-обьектов от своего интерфейса (как в предыдущем примере - где мы потом из этих обьектов собрали коллекцию) - а мы попробуем использовать УЖЕ написанные микрософтавскими индусами классы.
Эта задача тоже чисто практическая. Существует масса форм (и у меня таких форм горы в каждом проекте) - где надо ОДНИ И ТЕ ЖЕ ДАННЫЕ вывести в различном виде на форму. В чуть-чуть различном. Неуловимо различном. Настолько, что писать две отдельные проги было бы полнейшим идиотизмом. например некую таблу с данными мы ИНОГДА выводим в GridView, а иногда в DataList.
Отличия этих двух компонентов - в ASP коде просто фантастические - например GridView выводит все только в один столбец, а DataList выводит в несколько столбцов. Как правило - всем-всем-всем нужно, чтобы например как только юзер залогинился - появился новый функционал - его много - и он еле-еле вообще в сроку влазит, как только юзер разлогинился - куча функционала пропадает - и элементы на форме пресуются по несколько штук в строку. Это один пример. Можно десятки подобных примеров привести, когда одни и те же данные надо запихать в разные контролы в зависимости от динамически возникающих условий.
Идиотизм ситуации однако состоит в том, что и DataList и GridView совершенно одинаково предствлены в итоговом HTML-коде. Здесь мы в сущности упираемся вообще в идиотизм настолько строгой типизации (и вообще в Недостатки объектно-ориентированного программирования в WEB.). Но деватся некуда - раз MS лишила нас препроцессора и преварительного прохода макропроцессора выхода другого нет никакого в рамках MS-идеологии. И это решение прекрасно демонстрирует применение дженериков и интерфейсов.
Итак, есть на одной и той же форме есть Multiview - в одной части которого лежит Gridview, в другой DataList.
Есть несколько запросов на форме - GetUserDataStrip (тяжелый), GetLinkedStory (не очень тяжелый, но это распределенная транзакция на совершенно другой сервер) и еще есть базовая страничка VotpuskBasePage. Базовая страничка, как вы видите, тоже нехилой сложности - содержит в себе кучу криптографии, ссылку на юзера, парссеры URL, сслылки на профили. Все это много раз понадобится на каждой страничке и по идее до начала реквеста должно быть подготовлено. В сущности - это небольшой личный фреймворк - в рамках которого идет каждый реквест этого проекта.
Кроме того, есть темплейты - те шаблоны DataList и GridView. И это все, что у нас есть в постановке задачи.
Наша задача - разобраться, как все это ухватить в одно место и добится, чтобы требуемые данные легли на формы - в эти такие разные GridView и DataList (один из которых даже имеет встроенный микрософтовский язва-скрипт пейджер, а во втором работает мой собственный пейджер). Иными словами - как нам сделать такую прогу, которую можно было бы вызвать, выдав и GridView1.DataBind() и DataList1.DataBind(). Что у нас дано - я перечислял - пара рекордсетов, возвращенных из SQL, базовая страничка и шаблоны DataList и GridView.
Решение состоит в том, чтобы создать такой класс, который в сущности примет на вход и DataList и Gridview, а также результаты наши тяжелых запросов в SQL и результат отработки базовой странички - которая дает нам весь рабочий фреймворк - криптографию, профили и так далее. Именно такой класс и будет нашим GENERIC-классом:
Это ключевой момент. Видите, как я передал ссылку на свой фреймворк - это ME, а сам итоговый экземпляр класса будет создан либо на элементе данных (и шаблоне) Of Web.UI.WebControls.GridViewRow, либо на элементе данных (и шаблоне) Web.UI.WebControls.DataListItem. Оба этих класса являются различной реализацией интерфейса System.Web.UI.IDataItemContainer. Это важнейшая концепция - здесь System.Web.UI.IDataItemContainer является той самой точкой пересечения, или контрейнтсом - через которые связаны элементы данных и шаблоны внутри Gridview с одной строны и DataList с другой стороны. Кстати, реализацией этого же интерфейса является тот самый DATABINDER.EVAL - который мы так часто пишем в декларативной части форм, а также режеприменяемые FormView/DetailsView.
Теперь вы должны уловить связь с предыдущим проектом, где я сам определил такой же интерфейс CyberCommonFields и реализовал его четырьмя способами - CyberInt, CyberMasked, CyberText, CyberEnum (ну а уже потом собрал из них коллекцию - которую заполнил вручную путем распарсивания XML-определения параметров платежа). Здесь ровно то же самое, только и определение интерфейсов и их реализацию сделали индусы, нанятые микрасофтом. Но в том, проекте нам надо было создать GENIRIC-класс из этих (таких разных) обьектов, который можно было бы собрать в коллекцию и получить подсказку на форме и строгую типизацию, а в этом проекте нам надо просто воспользоваться уже сделанными индусами кирпичиками и заполнить сетку (для таких различных обьектов).
А значит, начало нашей проги будет выглядеть вот так:
00156: Protected Class MyBinding(Of TypeOfBindingControl As {System.Web.UI.Control, System.Web.UI.IDataItemContainer}) 00157: ''' <summary> 00158: ''' Привязка шаблона универсальная - для любого типа сетки 00159: ''' SQLdatasource надо в этот класс передавать извне, тк они в этом динамическом классе статические со странички 00160: ''' </summary> 00161: Public Sub CreateOneItem(ByRef OneItem As TypeOfBindingControl, ByVal PP8 As VBNET2000.ParmProtector8, _ 00162: ByVal GetUserDataStrip As SqlDataSource, ByVal GetLinkedStory As SqlDataSource, ByVal RefToPage As VotpuskBasePage) 00163: 00164: If OneItem.DataItem IsNot Nothing Then 00165: 'взяли данные из текущего элемента данных 00166: Dim GroupName As String 00167: If IsDBNull(OneItem.DataItem("GroupName")) Then 00168: ....
А дальше мы вытаскиваем из нашего OneItem (который является в некоторых вызовах фактически Web.UI.WebControls.GridViewRow, а иногда Web.UI.WebControls.DataListItem) - вытаскивается и заполяется-то он одинаково! И пару тысяч примерно вот таких строк - для заполнения (причем совершенно одинакового!) элементов шаблона сеток - данными
00814: 00815: 00816: 00817: Dim LinkToSlideGroup As ImageButton = CType(OneItem.FindControl("LinkToSlideGroup"), System.Web.UI.WebControls.ImageButton) 00818: If LinkToSlideGroup IsNot Nothing Then 00819: LinkToSlideGroup.PostBackUrl = "~/user_foto_slide.aspx?i=" & HttpContext.Current.Request.QueryString("i") & "&g=" & CryptoGroupNumber 00820: End If
Думаю, эта страничка не одному заинтересованному человеку поможет. Остается только добавить, что есть много-много альтернативных путей для решения этой же задачи. Например мне более всего импонирует макропроцессор, который был у меня при программировании на макроассемблере. Но... он запредельно прост cам и код, им произведенный, выходит совершенно открытым - а это не соответствует целям, которые ставит MS, паразитируя в информационной индустрии. Вот и получается, что даже просто заполнить одну и ту же сетку сетку в тегах <tr> <td> - нужно досконально разбиратся в описанной на этой страничке концепции интерфейсов и дженериков. И первый описанный на этой страничке проект тоже был бы невозможным без описанного тут класса на основе дженериков - а в нем таких замороченных классов с дженериками из сотни - около десятка. Так что без этой технологии - никуда.
Polimorfism
Я решил добавить на эту страничку еще пару примеров применения специализированных техник обьектного программирования, чтобы этот обзор получился более интересным. Во первых, я с момента создания пользуюсь ad-hoc полиморфизмом. Это такие функции, которые могут принять различное число параметров. Эта функция у меня существует для MySQL - ADO.NET обвязка для работы с MySQL в ASP.NET-сайтах под Windows.
Lля знакомства c тестом этой полиморфной функции для MS SQL отсылаю вас к своей заметке 2006-го года Асинхронные странички. Большинство людей написало бы эту фукнцию с помощью ParamArray, но не я. Посмотрите трассировку IL - и вы поймете, почему более длинный код в этом случае оправдан.
Аналогичный подход у меня использован в моем сервисе WCF_CLIENT - клиент Web-сервиса.
Для меня было удивительным, что полиморфищм активно используется в языке SQL. Пример полиморфной функции GetDisk для PostgreSQL у меня описан на этой страничке - Фрагмент реальной BLL на PostgeSQL.
И вот еще один примерчик наследования - гораздо более поздний, чем написана вся эта страничка. В 2010-м году я заинтересовался использованием PostgreSQL и решил опубликовать несколько примеров работы с Postgres. Первый же опубликованный мною примерчик BLL получился довольно поучительным в плане использования специфической техники объектного программирования - наследования. Классы наследуются от базового класса и добавляют новые методы и свойства к определениям базового класса:
Inherits
Еще один пример наследования вы можете посмотреть в опубликованной мною проге ComDetector - утилита поиска COM-оборудования - там класс работы с принтером VKP80 добавляет свои специфические методы и свойства к общему классу работы COM-портом.
Недавно я нашел на своем сайте еще один поучительный примерчик использования наследования (а заодно и событий на нестандартных делегатах) - Шлюз к 1С по протоколу Битрикс.
И последняя специфическая техника обьектного программирования, на которой мы остановимся на этой страничке - это делегаты. Делегат - это callback, те программка обратного вызова, которая в некотором библиотечном коде лишь декларируется. Потом при вызове библиотечной функции пользователь подсовывает адрес своего калбека, и библиотечная функция вызывает калбек пользователя например в своем внутреннем цикле или при некоторых возникающих ситуациях. У меня на сайте есть несколько примеров использования делегатов:
Delegate
На этой же страничке с описанием вариантов встраивания общего кода в ASP.NET сайты - вы найдете также пример использования ключевых слов Overridable / Overrides.
|