Smalltalk: как изменить свое поведение

Я переношу свою любимую библиотеку Java/JavaScript Mocktito на Smalltalk. В настоящее время я нахожусь на этапе внедрения Spy для заглушки реальных объектов. Моя проблема возникает, когда шпионский объект вызывает свой собственный метод, который заглушен. Вместо:

self aMethod.

Я бы предпочел делегировать вызов шпионскому объекту:

spyObject aMethod.

Вот сценарный тест ожидаемого поведения:

realObject := RealObjectForTesting new.
spyedObject := Spy new: realObject.
spyedObject when: #accesorWhichReturnsValue thenReturn: 'stubbed value'.

spyedObject accesorWhichCallsSelf.

self assert: (spyedObject verify: #accesorWhichReturnsValue).

Любое предложение?


person Zsolt    schedule 31.10.2012    source источник


Ответы (3)


Вы можете обернуть RealObjectUnderTest CompiledMethod напрямую, используя библиотеку ObjectsAsMethodsWrapper. Это обеспечивает удобный API для установки и удаления оболочек вместе с некоторыми удобными предопределенными оболочками.

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

Хотя мой пример показывает, как запомнить метод call, не касаясь исходного кода, он должен предоставить вам базовые знания, необходимые для имитации вызовов методов.

Этот конкретный метод имеет ограничение: он перехватывает самоотправку сообщений, которые определяет сам класс. Таким образом, если Foo является подклассом Bar и вы устанавливаете обертки на Foo, вы не будете перехватывать сообщения, являющиеся частью протокола Bar (если, конечно, вы не обернете и их).

Вы не сможете перехватить ifTrue:ifFalse:, timesRepeat или подобные сообщения в образах Squeak или Pharo (и, вероятно, также в GNU Smalltalk), потому что это не отправка сообщений: преобразования во время компиляции встраивают эти сообщения отправляет в байт-коды перехода. (Иллюзия отправки сообщения относительно убедительна, потому что Decompiler знает, как отменить преобразование байт-кодов обратно в ifTrue:ifFalse: или что-то еще.)

person Frank Shearar    schedule 01.11.2012
comment
-1? Действительно? Если это на самом деле неверно, вам лучше объяснить, почему. - person Frank Shearar; 03.11.2012
comment
+1 - Это наиболее полный способ сделать то, что было запрошено, и, на мой взгляд, лучшее решение проблемы. Фрэнк довольно хорошо объяснил все за и против, и я бы предположил, что это лучший способ. Он спас меня от написания того же самого. Сказав это, это продвинутая техника Smalltalk. Если вам нужна дополнительная информация о том, как это сделать, просто спросите. Спасибо, Фрэнк. - person David Buck; 06.11.2012
comment
+ 1 Да, это решение, которое я бы тоже выбрал. - person akuhn; 17.11.2012

Вы делаете своего «шпиона» объектом-оболочкой, который реализует только doesNotUnderstand:, а затем заменяете его реальным объектом, используя become:.

Шпионский метод doesNotUnderstand: будет вызываться для всех сообщений, и тогда вы можете, например, зарегистрируйте свой аргумент (который является объектом Message) и отправьте его исходному объекту.

Если вы просмотрите реализации doesNotUnderstand: в своем образе Smalltalk, вы можете найти пару примеров (например, в Squeak есть MessageCatcher и ObjectViewer).

person Vanessa Freudenberg    schedule 31.10.2012

В Smalltalk нет встроенного механизма для перехвата сообщений самому себе, поэтому вам придется прибегнуть к экзотическим мерам.

Вероятно, самый простой способ сделать это — динамически украсть методы из исходного объекта. Ваш шпион заменит цель, используя #become:, как предлагает Берт. Затем, когда шпион получает сообщение, вместо того, чтобы пересылать сообщение исходному объекту, вы должны искать селектор в его классе и выполнять его со шпионом в качестве получателя. Механизм выполнения скомпилированного метода для произвольного получателя будет варьироваться от диалекта к диалекту. В Squeak это CompiledMethod class>>receiver:withArguments:executeMethod:.

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

person Colin Putney    schedule 31.10.2012
comment
кроме того, я бы сказал, что это не имеет смысла для меня: - person Igor Stasenko; 01.11.2012