How to properly structurate the visibility of this Class?

时光怂恿深爱的人放手 提交于 2019-12-11 17:54:31

问题


I'm trying to improve an old Class that I've wrote to manage an INI file, the Class contains 3 sub-Classes (File, Key, Section) to separate and organize the procedures (procs for the ini in general, procs for manage the keys/values, and procs for manage the section names).

Well, the problem I have is that in the old class all the members were shared (props/vars/objects/methods) and obviouslly that could result in a disimbiguation, then I would like to try to perfectionate the visibility of the members, and there is where I'm stuck.

The current usage of the Class is like this:

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

And the usage that I would like should be like this:

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

Below is the relevant code for this example, I'm stuck on the Exist method of the File class 'cause I cannot access to the FilePath variable which is at the top-level class since I don't set that variable and the Exist method both as Shared like I did on my old Class version...

...So how I can improve this?

NOTE: Please keep in mynd that the other 2 sub-Classes should have a method named Exist and other methods with equal names such as "[Get]", not only in the File Class (I don't know if that could be a problem that could need more retouches).

''' <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:


I think part of the problem is that you are revealing rather than handling too much detail of the hierarchy thru the class. The other part is clinging to 1980's 16bit Windows tech when there are much more robust mechanisms around today.

You have 4 classes (INIManager, File, Section, Key) to manage 2 bits of information (Key and Value). Since INIManager is just to "house" the others, he can be combined with File - especially since there arent many file level operations. You likely do not even need [Sections]. Those only existed so you could store repeated attributes about similar items like:

 [MainDB]
 Path =  
 File =  
 Foo =

 [Report DB] 
 Path =  
 File =  
 Foo =

These were intended to provide INItialization values in a way where you could loop thru a collection of strings like FILE1, FILE2... and read multiple sections in a loop. For a simple set of settings, just use a single [Default] section to simplify the class and its use. Then, you are just down to IniManager and Keys. Purposely exposing the underlying hierarchy doesnt lend itself to usability, IMO.

So to do what you want, you need a SECTIONS property on INIManager which exposes the section related stuff. To support that, you need a INISection class (mainly the methods for Sections) AND a INISectionS collection Class. (Some Comments lead me to surmise that you want to load all the sections all the time and all their keys so they can be deleted etc).

If you really want something like Sections().Keys().Method, you will have to add a Key class and Keys collection class on IniSection. Which would bring the grand total to 5 classes to manage 2 pieces of information. Of course, it can be done with half the code and 1 class. Most of the extra fluff is there to expose the inner workings the way you mentioned. You'll also have fun with Public vs Friend to keep from revealing some things you dont want to.

I dont have anything in the code to do the PInvokes; The question deals with class construction not INI management. Most methods are empty and exist just to see how they end up for the poor user.


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

Sample syntax:

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")

These dont match syntactically because I didnt create a Key class and Keys collection class (5 classes for 2 bits of information is insane). To change it so the setter matches, remove the Keys accessor and add a .ReadKey() and SetKey so it matches syntactically and keep the keys collection internal. You'll end up with:

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

At least they match syntactically

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()


来源:https://stackoverflow.com/questions/24343016/how-to-properly-structurate-the-visibility-of-this-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!