Константа с оператором точки (VBA)

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

Dim MyDensity, MySymbol
MyDensity = ALUMINUM.Density
MySymbol = ALUMINUM.Symbol

Очевидно, что плотность и символ для алюминия не должны изменяться, поэтому я хочу, чтобы они были постоянными, но мне нравится точечная запись для простоты.

Я вижу несколько вариантов, но они мне не нравятся.

  1. Создайте константы для каждого свойства каждого материала. Это выглядит как слишком много констант, поскольку у меня может быть 20 материалов с 5 свойствами.

    Const ALUMINUM_DENSITY As Float = 169.34
    Const ALUMINUM_SYMBOL As String = "AL"
    
  2. Определите перечисление со всеми материалами и создайте функции, которые возвращают свойства. Не так очевидно, что плотность постоянна, поскольку ее значение возвращается функцией.

    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