Skip to content
Menu
CDhistory
CDhistory

Ruby metódusellenőrzés a Module#prepend használatával

Posted on december 12, 2021 by admin

Tegnap egy Ruby fejlesztői pozícióra voltam interjún egy denveri cégnél, amikor az interjúztatóm egy jó metaprogramozási kihívást tett fel: megkért, hogy írjak egy automatikus metódusellenőrzést.

Leírt egy osztály metódust, amely egy példány metódus nevét veszi, és egy automatikus, naplózott papírnyomot generál minden alkalommal, amikor a célmetódus meghívásra kerül. Valami ilyesmit:

Ez olyan kimenetet eredményezne, ami így nézne ki:

Kitaláltam egy megoldást, ami működött, de a Object#send néhány potenciálisan veszélyes hackeléssel járt. Mielőtt bemutattam volna a megoldásomat, azt mondtam, hogy “ez valószínűleg rossz ötlet, de…”, és igazam volt. A jó ötletek ritkán indulnak így.

A megoldás, amit javasolt, a gyakran figyelmen kívül hagyott Module#prepend felhasználását tartalmazta, és úgy gondoltam, hogy elég menő ahhoz, hogy megérdemeljen egy írást.

A stratégia az volt, hogy a Module#prepend viselkedését felhasználva dinamikusan generált wrapper metódust hozunk létre a Messenger#share számára.

Ebben a wrapperben lesz egy “Performing” üzenet, ami a metódus testének végrehajtása előtt lő ki, maga a metódus végrehajtása, majd egy végső “Exiting” üzenet, miután a metódus végrehajtása sikeresen befejeződött.

De ahhoz, hogy megértsük, hogyan használjuk a Module#prepend-t erre a célra, először is jól meg kell értenünk, hogyan működik a metódus öröklődés a Rubyban.

  • Az ősláncok
  • A modul maga
  • Az egészet összerakva
  • Következtetés

Az ősláncok

Minden Ruby objektum egy úgynevezett őslánc végén helyezkedik el. Ez azoknak az ősobjektumoknak a listája, amelyektől az objektum örököl. A Ruby ezt az ősláncot használja arra, hogy meghatározza, hogy egy metódus melyik verziója (ha egyáltalán van ilyen) kerül végrehajtásra, amikor az objektum üzenetet kap.

Az ősfát bármely objektum esetében ténylegesen megtekinthetjük a Module#ancestors hívásával. Itt van például a Messenger osztályunk őslánca:

Amikor #include vagy #prepend modult illesztünk egy osztályba, a Ruby módosítja az osztályok ősláncát, finomítva, hogy mely metódusokat és milyen sorrendben találjuk meg. A #include és #prepend közötti nagy különbség az, hogy hol történik ez a változtatás. Hozzunk létre egy Auditable nevű modult, amely (végül) azt a kódot fogja tartalmazni, amely az auditálási varázslatunkat végzi:

Ha a Module#include segítségével importáljuk a (jelenleg nem létező) metódusokat a Auditable-ből, a Ruby ezt a modult a Messenger osztályunk őseként fogja bepréselni. Tessék, nézd meg magad:

Amikor meghívunk egy metódust a Messenger-ban, a Ruby megnézi, hogy mi van definiálva a Messenger osztályban, és – ha nem talál a hívásnak megfelelő metódust – felmászik az ősláncban a Auditable-ig, hogy újra megnézze. Ha a Auditable-nél nem találja meg, amit keres, akkor továbbmegy a Object-re és így tovább.

Ez a #include. Ha ehelyett a Module#prepend-t használjuk a modul tartalmának importálására, teljesen más hatást érünk el:

#prepend a Messenger-t a Auditable ősévé teszi. Várjunk csak! Mi?

Ez úgy tűnhet, mintha a Messenger mostantól a Auditable szuperosztálya lenne, de itt nem pontosan ez történik. A Messenger osztály példányai még mindig a Messenger osztály példányai, de a Ruby mostantól a Auditable modulban fogja keresni a Messenger-hoz használható metódusokat, mielőtt magában a Messenger osztályban keresné őket.

És ez az, barátaim, amit ki fogunk használni ennek az auditornak a felépítéséhez: ha létrehozunk egy Auditable#share nevű metódust, a Ruby ezt fogja megtalálni, mielőtt megtalálja a Messenger#share-et. Ezután egy super hívást használhatunk a Auditable#share-ban, hogy elérjük (és futtassuk!) a Messenger-ban definiált eredeti metódust.

A modul maga

Ténylegesen nem fogunk létrehozni egy Auditable#share nevű metódust. Miért nem? Mert azt akarjuk, hogy ez egy rugalmas segédprogram legyen. Ha keményen kódoljuk a Auditable#share metódust, akkor csak olyan metódusokra tudjuk majd használni, amelyek implementálják a #share metódust. Vagy ami még rosszabb, újra kellene implementálnunk ugyanazt az auditor mintát minden olyan metódushoz, amelyet valaha is auditálni szeretnénk. Nem, köszönöm.

Ehelyett tehát dinamikusan fogjuk definiálni a metódusunkat, és a audit_method osztály metódusát fogjuk kilőni:

Amikor a audit_method-t meghívjuk egy implementáló osztályban, a Auditable modulban létrejön egy metódus, amelynek neve megegyezik az auditálandó metódus nevével. Esetünkben egy Auditable#share nevű metódust hoz létre. Mint említettük, a Ruby előbb fogja megtalálni ezt a metódust, mint az eredeti Messenger metódust, mivel az implementáló osztályban a Auditable modult előlegezzük meg.

Ez azt jelenti, hogy egy szuperhívással feljebb juthatunk az ősláncban, és végre tudjuk hajtani a Messenger#send metódust. Amikor ezt megtesszük, az összegyűjtött argumentumokat (*arguments) is továbbítjuk felfelé a láncban.

Amikor meghívtuk az eredeti metódust, kiírjuk a kilépési üzenetünket, és végeztünk. Szép munka, banda!

Az egészet összerakva

Most már csak prepend kell ezt a modult a Messenger osztályunkba illeszteni, és máris készen állunk:

És a jó ég áldja meg, hogy működik:

Az itteni következmények óriásiak az auditálás szempontjából, de ez a trükk még többről szól. A prependinget használhatod az objektumok viselkedésének megváltoztatására anélkül, hogy magukat az objektumokat megváltoztatnád. Ez egy adaptálható és egyértelmű módja annak, hogy magasabb rendű komponenseket hozzunk létre Rubyban. Teljesítménytesztelés, hibakezelés. Sok mindent megtehetsz itt.

Következtetés

A Ruby modulok bonyolultabbak, mint azt a legtöbben gondolják. Nem olyan egyszerű, mint a kód “dömpingelése” egyik helyről a másikra, és ezeknek a különbségeknek a megértése néhány igazán ügyes eszközt nyit meg a segédeszköz-öved számára.

Megfigyelhetted, hogy ma csak a Module#include-ről és a Module#prepend-ről beszéltem, és hogy a Module#extend-hoz nem nyúltam. Ez azért van, mert nagyon másképp működik, mint az unokatestvérei. Hamarosan írok egy részletes magyarázatot a Module#extend-ról, hogy teljes legyen a készlet.

Előre, ha többet szeretnél megtudni, ajánlom a Ruby modulok elolvasását: Include vs Prepend vs Extend című könyvét Léonard Hetsch-től. Ez nagyon hasznos volt abban, hogy mindezt összerakjam.

Vélemény, hozzászólás? Kilépés a válaszból

Az e-mail-címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

Legutóbbi bejegyzések

  • Az Acela visszatért: New York vagy Boston 99 dollárért
  • OMIM bejegyzés – # 608363 – CHROMOSOME 22q11.2 DUPLICATION SYNDROME
  • Kate Albrecht szülei – Tudj meg többet apjáról Chris Albrechtről és anyjáról Annie Albrechtről
  • Temple Fork Outfitters
  • Burr (regény)

Archívum

  • 2022 február
  • 2022 január
  • 2021 december
  • 2021 november
  • 2021 október
  • 2021 szeptember
  • 2021 augusztus
  • 2021 július
  • 2021 június
  • 2021 május
  • 2021 április
  • DeutschDeutsch
  • NederlandsNederlands
  • SvenskaSvenska
  • DanskDansk
  • EspañolEspañol
  • FrançaisFrançais
  • PortuguêsPortuguês
  • ItalianoItaliano
  • RomânăRomână
  • PolskiPolski
  • ČeštinaČeština
  • MagyarMagyar
  • SuomiSuomi
  • 日本語日本語
©2022 CDhistory | Powered by WordPress & Superb Themes