问题
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