Константа с оператором точки (VBA)
Я хочу иметь каталог постоянных материалов, чтобы я мог использовать код, который выглядит следующим образом:
Dim MyDensity, MySymbol
MyDensity = ALUMINUM.Density
MySymbol = ALUMINUM.Symbol
Очевидно, что плотность и символ для алюминия не должны изменяться, поэтому я хочу, чтобы они были постоянными, но мне нравится точечная запись для простоты.
Я вижу несколько вариантов, но они мне не нравятся.
-
Создайте константы для каждого свойства каждого материала. Это выглядит как слишком много констант, поскольку у меня может быть 20 материалов с 5 свойствами.
Const ALUMINUM_DENSITY As Float = 169.34
Const ALUMINUM_SYMBOL As String = "AL"
-
Определите перечисление со всеми материалами и создайте функции, которые возвращают свойства. Не так очевидно, что плотность постоянна, поскольку ее значение возвращается функцией.
Public Enum Material
MAT_ALUMINUM
MAT_COPPER
End Enum
Public Function GetDensity(Mat As Material)
Select Case Mat
Case MAT_ALUMINUM
GetDensity = 164.34
End Select
End Function
Не похоже, что Const Structs или Const Objects решат эту проблему, но, возможно, я ошибаюсь (их даже нельзя допустить). Есть ли способ лучше?
Ответы
Ответ 1
Сделать VBA эквивалентным "статическому классу". Обычные модули могут иметь свойства, и ничто не говорит о том, что они не могут быть доступны только для чтения. Я также обернул бы плотность и символ в тип:
'Materials.bas
Public Type Material
Density As Double
Symbol As String
End Type
Public Property Get Aluminum() As Material
Dim output As Material
output.Density = 169.34
output.Symbol = "AL"
Aluminum = output
End Property
Public Property Get Iron() As Material
'... etc
End Property
Это очень близко к желаемой семантике использования:
Private Sub Example()
Debug.Print Materials.Aluminum.Density
Debug.Print Materials.Aluminum.Symbol
End Sub
Если вы находитесь в том же проекте, вы можете даже удалить явный квалификатор Materials
(хотя я бы рекомендовал сделать его явным):
Private Sub Example()
Debug.Print Aluminum.Density
Debug.Print Aluminum.Symbol
End Sub
Ответ 2
IMO @Comintern ударил гвоздь по голове; Этот ответ является еще одной возможной альтернативой.
Сделайте интерфейс для этого. Добавьте модуль класса, назовите его IMaterial
; этот интерфейс формализует свойства "только для получения", в которых нуждается Material
:
Option Explicit
Public Property Get Symbol() As String
End Property
Public Property Get Density() As Single
End Property
Теперь откройте Блокнот и вставьте этот заголовок класса:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "StaticClass1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Сохраните его как StaticClass1.cls
и сохраните в папке "часто необходимые файлы кода VBA" (сделайте ее, если у вас ее нет!).
Теперь добавьте реализацию прототипа в текстовый файл:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Material"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Implements IMaterial
Private Const mSymbol As String = ""
Private Const mDensity As Single = 0
Private Property Get IMaterial_Symbol() As String
IMaterial_Symbol = Symbol
End Property
Private Property Get IMaterial_Density() As Single
IMaterial_Density = Density
End Property
Public Property Get Symbol() As String
Symbol = mSymbol
End Property
Public Property Get Density() As Single
Density = mDensity
End Property
Сохраните этот текстовый файл как Material.cls
.
Теперь импортируйте этот класс Material
в ваш проект; переименуйте его в AluminiumMaterial
и заполните поля:
Private Const mSymbol As String = "AL"
Private Const mDensity As Single = 169.34
Снова AnotherMaterial
класс Material
, переименуйте его в AnotherMaterial
, заполните пробелы:
Private Const mSymbol As String = "XYZ"
Private Const mDensity As Single = 123.45
Промойте и повторите для каждого материала: вам нужно указать каждое значение только один раз для каждого материала.
Если вы используете Rubberduck, добавьте аннотацию папки в файл шаблона:
'@Folder("Materials")
И тогда Code Explorer будет аккуратно перегруппировать все классы IMaterial
в папке Materials
.
Наличие "множества модулей" является проблемой только в VBA, потому что VBE Project Explorer делает его довольно неудобным (помещая каждый отдельный класс в одну папку "classes"). Rubberduck Code Explorer не даст VBA иметь пространства имен, но позволяет вам структурировать ваш проект VBA независимо от структуры.
Теперь можно использовать полиморфный код для интерфейса IMaterial
:
Public Sub DoSomething(ByVal material As IMaterial)
Debug.Print material.Symbol, material.Density
End Sub
Или вы можете получить доступ только к свойствам get из открытого экземпляра по умолчанию (который вы получаете из атрибута VB_PredeclaredId = True
модулей):
Public Sub DoSomething()
Debug.Print AluminumMaterial.Symbol, AluminumMaterial.Density
End Sub
И вы можете передать экземпляры по умолчанию в любой метод, который должен работать с IMaterial
:
Public Sub DoSomething()
PrintToDebugPane AluminumMaterial
End Sub
Private Sub PrintToDebugPane(ByVal material As IMaterial)
Debug.Print material.Symbol, material.Density
End Sub
Вверх, вы получаете проверку во время компиляции для всего; типы невозможно использовать неправильно.
С другой стороны, вам нужно много модулей (классов), и если интерфейс должен измениться, это приводит к обновлению большого количества классов, чтобы обеспечить возможность компиляции кода.
Ответ 3
Вы можете создать модуль с именем "ALUMINUM" и поместить в него следующее:
Public Const Density As Double = 169.34
Public Const Symbol As String = "AL"
Теперь в другом модуле вы можете вызывать их так:
Sub test()
Debug.Print ALUMINUM.Density
Debug.Print ALUMINUM.Symbol
End Sub
Ответ 4
Вы можете создать модуль Class - пусть он будет называться Material и определить свойства, которыми материал обладает в качестве открытых членов (переменных), таких как Density, Symbol:
Public Density As Float
Public Symbol As String
Затем в стандартном модуле создайте материалы:
Public Aluminium As New Material
Aluminium.Density = 169.34
Aluminium.Symbol = "AL"
Public Copper As New Material
' ... etc
Добавление поведения
Хорошая вещь о классах заключается в том, что вы можете определять функции в них (методы), которые вы также можете вызывать с точечной нотацией в любом экземпляре. Например, если можно определить в классе:
Public Function AsString()
AsString = Symbol & "(" & Density & ")"
End Function
... тогда с вашим экземпляром Aluminium
(см. ранее) вы можете сделать:
MsgBox Aluminium.AsString() ' => "AL(169.34)"
И всякий раз, когда у вас есть новая функция/поведение для реализации, которая должна быть доступна для всех материалов, вам нужно только реализовать ее в классе.
Другой пример. Определите в классе:
Public Function CalculateWeight(Volume As Float) As Float
CalculateWeight = Volume * Density
End Function
... и теперь вы можете сделать:
Weight = Aluminium.CalculateWeight(50.6)
Создание свойств только для чтения
Если вы хотите быть уверены, что ваш код не присваивает новое значение свойствам Density
и Symbol
, вам потребуется немного больше кода. В классе вы определяете эти свойства с помощью методов получения и установки (используя синтаксис Get
и Set
). Например, Symbol
будет определяться следующим образом:
Private privSymbol as String
Property Get Symbol() As String
Symbol = privSymbol
End Property
Property Set Symbol(value As String)
If privSymbol = "" Then privSymbol = value
End Property
Приведенный выше код позволит установить свойство Symbol только в том случае, если оно отличается от пустой строки. После установки на "AL" его уже нельзя изменить. Вы могли бы даже хотеть выдать ошибку, если такая попытка сделана.
Ответ 5
Мне нравится гибридный подход. Это псевдокод, потому что у меня нет времени, чтобы полностью проработать пример.
Создайте MaterialsDataClass
- посмотрите знания Матье Гиндона о настройке статического класса.
Private ArrayOfSymbols() as String
Private ArrayOfDensity() as Double
Private ArrayOfName() as String
' ....
ArrayOfSymbols = Split("H|He|AL|O|...","|")
ArrayOfDensity = '....
ArrayOfName = '....
Property Get GetMaterialBySymbol(value as Variant) as Material
Dim Index as Long
Dim NewMaterial as Material
'Find value in the Symbol array, get the Index
New Material = SetNewMaterial(ArrayOfSymbols(Index), ArrayofName(Index), ArrayofDensity(Index))
GetMaterialBySymbol = NewMaterial
End Property
Property Get GetMaterialByName(value as string) ' etc.
Сам Material
похож на другие ответы. Я использовал Type
ниже, но я предпочитаю Class
es, а не Type
потому что они предоставляют больше функциональности, и их также можно использовать в циклах "Для каждого".
Public Type Material
Density As Double
Symbol As String
Name as String
End Type
В вашем использовании:
Public MaterialsData as New MaterialsDataClass
Dim MyMaterial as Material
Set MyMaterial = MaterialsDataClass.GetMaterialByName("Aluminium")
Debug.print MyMaterial.Density