VBA Ссылка на контейнерный объект - синтаксис и объектно-ориентированная методология - PullRequest
5 голосов
/ 20 апреля 2011

Для меня это такой же вопрос об изучении объектно-ориентированной методологии, как и о синтаксисе VBA. Предположим, я создаю несколько классов, таких как «Автомобиль», «Грузовик», «Автобус» и т. Д. И я создаю другой класс SpeedCalculator, который будет создавать и содержать экземпляры моих транспортных средств. (Как новичок, позвольте мне заметить, что мне пора уже объявить класс статическим, а не создавать его экземпляр - чего не может сделать vba, я не думаю ...) Теперь этот калькулятор скорости будет нет простого спидометра. Скорее он будет рассчитывать скорость на основе температуры, скорости ветра, оборотов и т. Д. И т. Д. - согласитесь с этим, пожалуйста, только для примера.

Теперь вопрос заключается в том, как содержащийся объект может собирать свои входные данные, которые доступны только в объектах-контейнерах (объекты транспортных средств могут реализовывать интерфейс (если VBA может даже сделать это ...)). «Родитель». неправильно, я в конце концов выяснил, что b / c parent-child - это отношение наследования (которого у VBA опять нет), а не отношение сдерживания, а родительским элементом содержащегося объекта является приложение Excel (не мой объект) , Поэтому было бы неплохо, если бы было другое ключевое слово для ссылки на свойства контейнера. Надеюсь, я не пропустил что-то простое. Или, скорее, такой тип ссылки нарушит принципы объектно-ориентированной инкапсуляции?

Полагаю, вторым подходом было бы передать контейнер содержимому через «Я» в качестве аргумента. Но тогда вам нужно умножить все содержащиеся в нем методы, либо перегрузить их (если VBA может даже сделать это ...), либо с разными именами версий - из-за разных типов контейнеров (можем ли мы быть более идеалистическими и не объявлять как вариант или "Объект"?).

И тогда дверь № 3 будет последней дверью, я полагаю? Который должен был бы передать (раздражающую) кучу аргументов. Определение всего, что будет иметь тенденцию побеждать цель иметь мой аккуратный маленький класс калькулятора?

Ответы [ 2 ]

6 голосов
/ 20 апреля 2011

Из вашего вопроса мне не ясно, знаете ли вы уже VBA и / или OO, и просто спрашиваете, как использовать объектно-ориентированные функции VBA. Если вы новичок как в VBA, так и в OO, см. Ниже несколько соображений о том, почему VBA не очень хороший инструмент для изучения OOD / OOP.

Чтобы ответить на общую часть вашего вопроса, классы VBA могут реализовать интерфейсы. Вот как вы выражаете наследование интерфейса (отношение "is-a") в VBA. Не существует прямого способа выразить наследование реализации в VBA. Вместо этого, чтобы один класс наследовал реализацию другого, у вас есть первый, реализующий интерфейс второго, содержащий экземпляр второго, а затем делегирующий вызовы этому экземпляру. Смотрите этот ответ для более:

VBA наследование, аналог супер

Там есть ссылка, которую я здесь повторю, на Руководство для программиста Visual Studio 6.0:

http://msdn.microsoft.com/en-us/library/aa240846(v=VS.60).aspx

Это короткое вступление так же хорошо, как и любое другое из «пути VBA» ООП (хотя оно написано для VB6, а не VBA).


Теперь к вашему конкретному вопросу о дизайне: «как содержащийся объект может собирать свои входные данные, которые доступны только в объектах-контейнерах».

Вам нужно подумать о том, что вы на самом деле моделируете здесь. Независимо от того, как вы его реализуете, «калькулятор скорости» должен знать только об очень специфическом наборе входных данных, а не обо всем внутреннем состоянии любого транспортного средства, которое его использует. В VBA, как вы заметили, нет статических классов. Вместо этого используйте обычный кодовый модуль и имейте функцию, которую вы вызываете из класса вашего автомобиля:

Public Function calcSpeed(temp, windspeed, rpm)

   'do calc based only on parms passed in...

End Function

Если ему нужно принять zillion параметров, потому что так работает расчет, пусть будет так. Не пытайтесь это скрыть. Конечно, вы можете обернуть их в Type или в классе, если их слишком много.

Теперь, каждый ли автомобиль различного типа вычисляет скорость одинаковым образом из одного и того же набора параметров состояния? Если так, то имейте свойство или метод speed, который реализуется вашим классом «базового транспортного средства», и вызывайте calcSpeed оттуда.

Но, возможно, это тот случай, когда разные типы транспортных средств имеют разные параметры состояния, или используют разные методы расчета, или же расчеты одинаковы, но не каждый тип автомобиля предоставляет каждый параметр. В этом случае поместите метод speed в базовом транспортном средстве interface , но "переопределите" его, как необходимо при реализации каждого подкласса. (Может быть, тогда calcSpeed будет слишком упрощенным, и вы получите библиотеку вспомогательных функций для расчета скорости.)

Одна вещь, которую я бы не сделал , это общий класс SpeedCalculator, который принимает аргумент Vehicle, а затем запрашивает его для определения его состояния для выполнения вычисления. Причина не очень хорошо выражена в этих классических статьях:

http://media.pragprog.com/articles/may_04_oo1.pdf

http://pragprog.com/articles/tell-dont-ask

http://www.cmcrossroads.com/bradapp/docs/demeter-intro.html

Также есть это:

http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf

цитата, которая мне нравится:

Итак, что же такого плохого в этом коде (помимо того, что ужасно надуманный пример)? Хорошо, давайте переведем то, что код на самом деле делает в реальный язык:

Видимо, когда газетчик останавливается и требует оплаты, клиент просто собирается повернуть вокруг, пусть газетчик возьмет кошелек из его заднего кармана, и достать два доллара.

Я не знаю о Вы, но я редко позволяю кому-то справиться мой бумажник. Есть ряд проблемы "реального мира" с этим, а не упомянуть мы доверяем газетчику честно говоря и просто вынуть то, что он должен. Если наш будущий объект кошелек держит кредитные карты, разносчик газет доступ к тем тоже ... но основной Проблема в том, что «разносчик газет подвергается большему количеству информации, чем он должно быть".

Это важно концепция ... Класс «Paperboy» сейчас «знает», что у клиента есть Вашингтони может манипулировать этим. Когда мы скомпилируйте класс Paperboy, он будет нужен класс клиента и кошелек учебный класс. Эти три класса сейчас 'тесно связаны'. Если мы изменим Класс кошелька, нам, возможно, придется сделать изменения в обоих других классах.

ДОБАВЛЕНО КАК ОБЕЩАНО В КОММЕНТАРИИ:

Дело не в том, что вы не могли легко получить экземпляр класса Speedometer, содержащийся в ваших Vehicle s. (Мой пример простой функции может быть слишком упрощенным. Возможно, вам нужен класс для моделирования других вещей, связанных со спидометрами - они имеют массу, занимают пространство и т. Д.) Это то, как эти два класса зависят друг от друга. В этом примере Vehicle нужно знать о Speedometer. Но почему обратное должно быть правдой? Если Speedometer принимает Vehicle в качестве параметра, а затем запрашивает его о конкретных вещах, которые необходимо знать для вычисления скорости, код, безусловно, будет работать . Однако вы связали Speedometer с Vehicle более плотно, чем необходимо.

Одна из причин использования ОО-подхода в первую очередь заключается в том, что он позволяет вам быть более точным в отношении того, как концепции связаны друг с другом. Лучше иметь Vehicle сказать Speedometer: «Вот некоторые факты о мире. Верните мне скорость», а не «Вот я, Me, Vehicle, который вас окружает. Спросите». мне все, что вам нужно обо всем, что связано со мной, а затем верните мне скорость. " (Обратите внимание, что независимо от того, являются ли «факты о мире» необработанными темпом, скоростью ветра и т. Д., Или экземпляром какого-либо типа / класса SpeedometerInput, проблема не в этом. Дело в том, что спидометрам не нужно знать все о транспортных средствах. )

Использование самого точного интерфейса, с которым вы можете обойтись, не делает ничего сложного в простом примере. Но это становится огромным, когда складывается из-за многих дизайнерских решений.


Наконец, если у вас есть выбор, я бы не использовал бы VBA в качестве средства для изучения объектно-ориентированного проектирования или программирования. Вы можете сделать «ООП» в VBA, но специфичным для Microsoft / COM способом, который буквально является пережитком середины 1990-х годов. Вы можете просмотреть стекировочный поток для множества примеров того, что обычно делается на языках программирования ОО (и с их намного лучшими библиотеками), которые являются громоздкими и хитрыми в VBA. Вот несколько советов, которые я либо спрашивал, либо отвечал:

Есть ли способ перегрузить процедуру конструктора / инициализации для класса в VBA?

Можно ли написать тест на равенство для класса VBA с закрытыми членами, не раскрывая знания о существовании этих закрытых членов?

Ограничение типа в коллекции внутри модуля класса

Excel-VBA - Есть ли что-то похожее на контейнер Javas Set в VBA?

Итак, если вы не обязаны учиться с VBA, потому что вы не можете установить на свой компьютер ничего, кроме MS Office, или вы планируете много работать с VBA, потому что вы используете Excel, Access и т. Д. И есть некоторые проблемы, где ООП может помочь, я бы посмотрел в другом месте. Python , .NET или Java - все они доступны бесплатно в Windows и имеют массу ресурсов для начинающих.

4 голосов
/ 20 апреля 2011

Я не уверен, что это то, что вы ищете, но я попробую. Если класс Car содержит один класс спидометра, Car содержит свойства скорости ветра и ускорения, спидометр содержит свойство массы, а скорость определяется как время ускорения скорости ветра, деленное на массу, то вот как я бы это настроил.

В классе CCar

Private mlCarID As Long
Private mdWindSpeed As Double
Private mdAcceleration As Double
Private mclsSpeedometer As CSpeedometer

'Getters and setters
Public Property Get CarID() As Long: CarID = mlCarID: End Property
Public Property Let CarID(ByVal lCarID As Long): mlCarID = lCarID: End Property
Public Property Get Acceleration() As Double: Acceleration = mdAcceleration: End Property
Public Property Let Acceleration(ByVal dAcceleration As Double): mdAcceleration = dAcceleration: End Property
Public Property Get WindSpeed() As Double: WindSpeed = mdWindSpeed: End Property
Public Property Let WindSpeed(ByVal dWindSpeed As Double): mdWindSpeed = dWindSpeed: End Property

'read only property to the speedometer class
Public Property Get Speedometer() As CSpeedometer
    Set Speedometer = mclsSpeedometer
End Property

'create the child and set the parent property
Private Sub Class_Initialize()
    Set mclsSpeedometer = New CSpeedometer
    Set mclsSpeedometer.Parent = Me
End Sub

Private Sub Class_Terminate()
    Set mclsSpeedometer.Parent = Nothing
    Set mclsSpeedometer = Nothing
End Sub

'pass through property
Public Property Get Speed() As Double
    Speed = Me.Speedometer.Speed
End Property

В классе CSpeedometer

Private mdMass As Double

Private mclsParent As CCar

Public Property Get Mass() As Double: Mass = mdMass: End Property
Public Property Let Mass(ByVal dMass As Double): mdMass = dMass: End Property

Public Property Get Parent() As CCar
    Set Parent = mclsParent
End Property

Public Property Set Parent(clsCar As CCar)
    Set mclsParent = clsCar
End Property

Public Property Get Speed() As Double
    'references to parent properties
    Speed = Me.Parent.WindSpeed * Me.Parent.Acceleration / Me.Mass
End Property

В стандартном модуле

Sub GetSpeed()

    Dim clsCar As CCar

    Set clsCar = New CCar

    clsCar.CarID = 1
    clsCar.WindSpeed = 10
    clsCar.Acceleration = 5
    clsCar.Speedometer.Mass = 100

    Debug.Print clsCar.Speed

End Sub

Вы должны убедиться, что правильно уничтожили отношения между родителями и детьми, иначе у вас будет утечка памяти. Я использую CopyMemory, чтобы установить родительские свойства, чтобы избежать этой конкретной проблемы. Это описано здесь http://www.dailydoseofexcel.com/archives/2007/12/28/terminating-dependent-classes/#comment-29661

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...