Как правильно структурировать видимость этого класса?

Я пытаюсь улучшить старый класс, который я написал для управления файлом INI, класс содержит 3 подкласса (File, Key, Section) для разделения и организации процедур (процедур для ini в целом, процедур для управления ключами / значениями и процедур для управления именами разделов).

Ну, у меня проблема в том, что в старом классе все члены были общими (props / vars / objects / method) и, очевидно, это могло привести к разногласиям, тогда я хотел бы попытаться улучшить видимость членов, и вот где я застрял.

Текущее использование класса выглядит следующим образом:

INIFileManager.FilePath = "ini filepath"
dim iniexist as boolean = INIFileManager.File.Exist

И использование, которое я хотел бы, должно быть таким:

dim ini as new inifilemanager("ini filepath", textencoding)
dim iniexist as boolean = ini.file.exist

dim another_ini as new inifilemanager("another ini filepath without any kind of conflict with the first instance", textencoding)
dim another_iniexist as boolean = another_ini.file.exist

Ниже приведен соответствующий код для этого примера, я застрял на Exist метод File класс, потому что я не могу получить доступ к FilePath переменная, которая находится в классе верхнего уровня, так как я не устанавливаю эту переменную и Exist метод как Shared как я делал на моей старой версии класса...

... Так как я могу улучшить это?

ПРИМЕЧАНИЕ. Пожалуйста, имейте в виду, что у двух других подклассов должен быть метод с именем Exist и другие методы с такими же именами, как "[Get]", не только в File Класс (я не знаю, может ли это быть проблемой, которая может потребовать больше ретуши).

''' <summary>
''' Manages an INI file and it's sections to load/save values.
''' </summary>
Public Class INIFileManager

#Region " Properties "

    ''' <summary>
    ''' Indicates the initialization file location.
    ''' </summary>
    Private Property FilePath As String = String.Empty

    ''' <summary>
    ''' Indicates the initialization file encoding to read/write.
    ''' </summary>
    Private Property TextEncoding As System.Text.Encoding = System.Text.Encoding.Default

#End Region

#Region " Constructors "

    ''' <summary>
    ''' Initializes a new instance of the <see cref="INIFileManager" /> class.
    ''' </summary>
    ''' <param name="IniFile">
    ''' Indicates the initialization file location.
    ''' </param>
    ''' <param name="TextEncoding">Indicates a textencoding to read/write the iniinitialization file.</param>
    Public Sub New(Optional ByVal IniFile As String = Nothing,
                   Optional ByVal TextEncoding As System.Text.Encoding = Nothing)

        If Not String.IsNullOrEmpty(IniFile) Then
            Me.FilePath = IniFile
        Else
            Me.FilePath = IO.Path.Combine(Application.StartupPath,
                                          Process.GetCurrentProcess().ProcessName & ".ini")
        End If

        If Not TextEncoding Is Nothing Then
            Me.TextEncoding = TextEncoding
        End If

    End Sub

#End Region

    ''' <summary>
    ''' Contains a set of procedures to manage the INI file in a general way.
    ''' </summary>
    Private Class [File]

        ''' <summary>
        ''' Checks whether the initialization file exist.
        ''' </summary>
        ''' <returns>True if initialization file exist, otherwise False.</returns>
        Public Function Exist() As Boolean
            Return IO.File.Exists(MyBase.FilePath)
        End Function

        ' More irrelevant methods here that need to access to props and vars of the top-level class...

    End Class

    ' another class here...

    ' and another class here...

End Class

1 ответ

Решение

Я думаю, что часть проблемы в том, что вы раскрываете, а не обрабатываете слишком много деталей иерархии через класс. Другая часть цепляется за 16-битную технологию Windows 1980-х годов, когда сегодня существуют гораздо более надежные механизмы.

У вас есть 4 класса (INIManager, File, Section, Key) для управления 2 битами информации (Key и Value). Поскольку INIManager предназначен только для "размещения" остальных, его можно объединить с File, тем более что операций на уровне файлов не так много. Вы, вероятно, даже не нуждаетесь в [Разделах]. Они существовали только для того, чтобы вы могли хранить повторяющиеся атрибуты для похожих предметов, таких как:

 [MainDB]
 Path =  
 File =  
 Foo =

 [Report DB] 
 Path =  
 File =  
 Foo =

Они были предназначены для обеспечения значений INItialization таким образом, чтобы вы могли циклически проходить через набор строк, таких как FILE1, FILE2... и читать несколько разделов в цикле. Для простого набора настроек просто используйте один раздел [Default], чтобы упростить класс и его использование. Тогда вы просто до IniManager и ключей. Преднамеренное разоблачение лежащей в основе иерархии не поддается юзабилити, ИМО.

Таким образом, чтобы сделать то, что вы хотите, вам нужно свойство SECTIONS на INIManager который выставляет раздел, связанный с материалом. Чтобы поддержать это, вам нужно INISection класс (в основном методы для разделов) И INISectionS Класс коллекции. (Некоторые комментарии заставляют меня предположить, что вы хотите загружать все разделы все время и все их ключи, чтобы их можно было удалять и т. Д.).

Если вы действительно хотите что-то вроде Sections().Keys().Method, вам нужно будет добавить Key класс и Keys collection class на IniSection, Что принесло бы общее количество 5 классам, чтобы управлять 2 частями информации. Конечно, это можно сделать с половиной кода и 1 классом. Большая часть лишнего пуха существует для того, чтобы раскрыть внутреннюю работу, как вы упомянули. Вам также будет весело с Public vs Friend, чтобы не показывать то, что вы не хотите.

У меня нет ничего в коде, чтобы сделать PInvokes; Вопрос касается классового строительства, а не управления INI. Большинство методов пустые и существуют только для того, чтобы увидеть, как они оказываются для плохого пользователя.


Public Class INIManager

    ' all the gory PInvokes go here

    Friend cfgFile As String
    Public Property INIExists As Boolean

    ' this is the bit you seemed to be missing
    ' A Collection property exposed at the IniMgr level
    ' containing a collection of Sections.  Like matryoshka dolls, inside
    ' each is a collection of Keys and Values
    Public Property Sections As IniSections

    ' no reason for INI mgr to even exist without a file
    Public Sub New(iniFile As String)
        cfgFile = iniFile
        _INIExists = System.IO.File.Exists(cfgFile)

        _Sections = New IniSections(cfgFile)
    End Sub

    ' only worthwhile thing I can think of that a "File"
    ' class would ever do.  
    Public Sub RemoveFile()

    End Sub

    Public Sub Save()
         ' i think you need to delete the file first so any
        ' deleted sections disappear. of course sections the code
        ' does ask for doesnt do any harm either

        ' iterate IniSections to call a Save there,
        ' which iterates the keys one by one to save them
        Sections.Save(cfgFile)
    End Sub

    ' ****** INISections Class Collection
    Public Class IniSections
        'Inherits Collection(Of IniSection)
        Private Items As Collection(Of IniSection)

        Private cfgFile As String

        Friend Sub New(file As String)
            cfgFile = file

            ' I am assuming from some comments that you are probably
            ' loading the entire file to manage it.  for that:

            If System.IO.File.Exists(cfgFile) Then
                ' load from GetPrivateProfileSectionNames into the collection
                ' mybase.Items.Add(section_name)...then

                For Each s As IniSection In Items
                    s.LoadKeyValues(cfgFile)
                Next

            End If

        End Sub

        ' FRIEND!
        Friend Sub Save(cfgfile As String)
            For Each s As IniSection In Items
                ' instruct each section to write the kvps
                s.Save(cfgfile)
            Next
        End Sub

        ' I dont know why an empty accessor is showing up in Intellisense
        Default Public ReadOnly Property Item(name As String) As IniSection
            Get
                If IndexOfSection(name) = -1 Then
                    Items.Add(New IniSection(name))
                End If
                Return Items(IndexOfSection(name))
            End Get

        End Property

        ' add a section
        Public Function Add(name As String) As IniSection
            Dim sec As New IniSection(name)
            Items.Add(sec)
            Return sec
        End Function

        ' remove a section
        Public Sub Remove(name As String)

            Items.RemoveAt(IndexOfSection(name))

    ' the only real way to remove a section is to rewrite the file! 
    ' so to support this method we have to load all sections and all keys
    ' all the time even if we dont need them so that we can write the
    ' out the whole file omitting removed keys and sections.
    '
    ' Seriously sir, this kind of junk went to the dustbin with Rubik's Cubes

        End Sub

        Public Function Exists(secName As String)
            Return IndexOfSection(secName) <> -1
        End Function

        Private Function IndexOfSection(name As String) As Integer
            For n As Integer = 0 To Items.Count - 1
                ' s/b ToLowerInvariant - that makes the screen scroll
                If Items(n).SectionName.ToLower = name.ToLower Then
                    Return n
                End If
            Next
            Return -1
        End Function

    End Class
End Class

' ************** INISection item class
Public Class IniSection
    ' mostly methods go here for sections,
    ' but is the "host" for the keys collections

    Private myKeys As Dictionary(Of String, String)

    ' for a .Keys collection (WHY would the calling code WANT to
    ' mess with the whole collection???), change to add a Key Class
    ' and Keys Collection

    ' interface for Keys
    Public Property Keys(name As String) As String
        Get
            If myKeys.ContainsKey(name) Then
                Return myKeys(name)
            Else
                Return ""
            End If
        End Get
        Set(value As String)
            If myKeys.ContainsKey(value) Then
                myKeys(value) = value
            Else
                myKeys.Add(value, value)
            End If
        End Set
    End Property

    Public Property SectionName As String

    Public Sub New(name As String)
        SectionName = name
        myKeys = New Dictionary(Of String, String)
    End Sub

    Public Sub RemoveKey(name As String)
        If myKeys.ContainsKey(name) Then
            myKeys.Remove(name)
        End If
    End Sub

    Friend Sub Save(inifile As String)
        ' iterate keys writitng the kvps to the ini

    End Sub

    ' note FRIEND called by the INISection class not the user
    Friend Function LoadKeyValues(inifile As String) As Integer
        '  presumably call GetPrivateProfileSection  
        '   for this SectionName and parse it to 
        ' get the current key=value pairs into myKeys
        Return myKeys.Count
    End Function

End Class

Пример синтаксиса:

ini = New INIManager("C:\Temp\Ziggy.INI")
Dim foo As String = ini.Sections("foo").Keys("bar")

ini.Sections("ziggy").Keys("foo") = "zoey"
ini.Sections("ziggy").RemoveKey("zacky")

Они не совпадают синтаксически, потому что я не создал класс Key и класс коллекции Keys (5 классов для 2 битов информации безумны). Чтобы изменить его так, чтобы установщик соответствовал, удалите средство доступа Keys и добавьте .ReadKey() а также SetKey таким образом, он соответствует синтаксису и сохраняет внутреннюю коллекцию ключей. В итоге вы получите:

ini.Sections("ziggy").RemoveKey("zacky")
ini.Sections("ziggy").ReadKey("ziggy")
ini.Sections("ziggy").SetKey(keyName, "zoey")

По крайней мере, они совпадают синтаксически

ini.Sections.Add("ziggy")
ini.Sections.Remove("zoey")
If ini.Sections.Exists("zacky") Then
    Console.Beep()
End If

' causes a cascade from INI -> Sections -> keys to save
ini.Save()
Другие вопросы по тегам