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)


Можете да обвиете CompiledMethod на RealObjectUnderTest директно, като използвате библиотеката 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: на шпионина ще бъде извикан за всички съобщения и след това можете напр. регистрира неговия аргумент (който е обект на съобщение) и го изпраща на оригиналния обект.

Ако прегледате изпълнителите на 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