Egyenlőségvizsgálat modellezési kérdései

Objektumok megkülönböztetése, egyenlőségvizsgálata általában a példányra definiált adatattribútumok összehasonlításával történik. Azonban a pusztán csak az adatattribútumokon alapuló egyenlőségvizsgálat nem minden valós helyzetet tud megfelelően modellezni. Egy ilyet mutat be a következő ábra egyszerű, hétköznapi példája.

A történet röviden: Jancsi és Pisti két kisgyerek, akik együtt szoktak játszani a közeli játszótéren. Egyszer Pisti egy szép piros gumilabdával jelent meg és büszkélkedve rugdosta és pattogtatta új játékát. Jancsi persze mint általában a gyerekek többsége, rögtön megirigyelte Pisti új labdáját, és otthon addig nyaggatta az apját, hogy vegyen neki egy ugyanolyan labdát, mígnem az elment a játékboltba és vett egy ránézésre ugyanolyan gumilabdát, vagyis ami színben és méretben (átmérőben) is megegyezett Pistiével. Jancsi nagyon megörült mikor megkapta az apjától, mert most már neki is volt egy ugyanolyan labdája, mint Pistinek. Az öröme azonban nem tartott sokáig. Ugyanis amikor Pistivel a játszótéren újra találkoztak, és mindketten hozták a kinézetre ugyanolyan labdájukat, az első pattogtatások után kiderült, hogy labdája mégsem ugyanolyan, mert Pisti labdája magasabbra tud pattanni, ami nyilván nagyobb játékélményt jelent. Jancsi ezért újra elégedetlen lett, mert mégsem ugyanolyan labdát kapott. Mindez azért történt, mert apja az „ugyanolyan” fogalmán kinézetbeli, kivitelbeli (jelen esetben, anyag, szín és méret) jellemzők egyezését értette, holott – ahhoz, hogy Pisti labdájával a játék, a használat szempontjából is egyező legyen – a labda bizonyos (jelen esetben a visszapattanási) képességét, viselkedését, ill. annak eredményét is tesztelni kellett volna.

A lényeg tehát, hogy e valós helyzet szoftvermodelljében, a labdát képviselő Ball osztály __eq__ metódusában az egyenlőség megállapításához a kivitelt/kinézetet leíró adatattribútumokon (szín, méret) felül egy képességet, viselkedést (visszapattanás) leíró metódust, pontosabban annak eredményét is vizsgálni szükséges.

Egy labda visszapattanási magassága a valóságban több fizikai tényezőtől függ, amelyek közül elsősorban az ejtési magasság (drop height), a labdába fújt levegő nyomása (pressure), valamint a labda anyagának a sűrűsége (density) a meghatározó. A játékboltban a labdavásárló ezekből egyedül az ejtési magasságot határozhatja meg, de a másik két paraméter értéke a vásárló számára nem hozzáférhető, hiszen senki nem vesz úgy játéklabdát, hogy egy nyomás- és anyagsűrűségmérővel felszerelkezve méréssorozatokat végez a labdákon. Ezért a Ball osztályban a nyomás és anyagsűrűség értékek privát adatattribútumok, amelyek értéke a példányosításkor (ami a valóságban a labda gyártásának felel meg) kerül meghatározásra.

A visszapattanási magasság tehát az ejtési magasság, a nyomás és a sűrűség függvénye, vagyis a Ball osztályban a visszapattanási magasságot meghatározó bounce() metódus ezeket használja, ahol az ejtési magasság a metódus argumentuma. Bár a valóságban nem pontosan igaz, de az egyszerűség kedvéért feltételezzük, hogy a visszapattanási magasság e három tényezővel egyenesen arányos.

A kérdés az, hogy a Ball osztályban implementált, egyenlőségvizsgálatra szolgáló __eq__ metódusban hogyan állapítsuk meg – a szín és méret egyezőségén felül – a visszapattanási magasság egyezőségét egy másik Ball példánnyal összevetve?

Gondolhatnánk arra, hogy mivel a bounce() determinisztikus függvénye a nyomásnak és a sűrűségnek, ezért a szín és méret adatattribútumokhoz hasonlóan össze kell vetni a nyomás és sűrűség privát adatattribútumokat is. Ha ezek megegyeznek az összehasonlított példányéval, akkor a visszapattanási magasságok is egyezők, és ha a szín és méret is azonos, akkor a két példány biztos, hogy egyenlő. Ezzel a gondolatmenettel kialakított Ball osztály definícióját és tesztelését láthatjuk alább:

Ez a megoldás azonban nem teljesen kielégítő. Mégpedig azért, mert akkor, és csak akkor tekinti visszapattanási magasság szempontjából a két labda példányt egyenlőnek, ha a nyomás és sűrűség egyenlő. Ez a teszteredményekből is látható. Holott ugyanakkora visszapattanási magasság a nyomás és sűrűség kombinációinál is kiadódhat, mégpedig minden olyan nyomás- sűrűség pár esetén, amelyeknél a szorzat az inicializálási értékek szorzatával egyező (pl. kétszeres nyomás, fele akkora sűrűség, vagy fordítva feleakkora nyomás és kétszeres sűrűség esetén).

Az eddigi megközelítés tehát nem egészen jó; helyette az egyenlőségvizsgálatban a bounce() metódust kell meghívni, és a visszatérési értékeket kell összehasonlítani. Az ennek megfelelően módosított __eq__ metódussal a Ball osztály a teszteredményekkel a következő. Ezek közül az utolsó kettő is az elvárásoknak megfelelő, ellentétben az előző változatéval.

Kérdésként merülhet fel, hogy a bounce() metódust milyen ejtési magasság értékre vagy értékekre kell meghívni az __eq__ metódusban. Általánosabban megfogalmazva a kérdés az, hogy ha az egyenlőségvizsgálatban metódust is meg kell hívni, és annak van paramétere, akkor milyen argumentummal kell azt meghívni? A válasz, hogy ez eset és modell függő.

Maradva a labdás példánál, semmi értelme, hogy az összehasonlításkor a bounce() argumentuma mondjuk 10 méternél több legyen, mert jelen példában kisgyerekekről van szó, és ők vagy a saját magasságukból dobják le a labdát, vagy egy mászóka tetejéről ejtik le, vagy felrúgják, ami kisgyerek esetén nem megy 10 méternél magasabbra. Ha megvan ez a felső limit, akkor az is csupán a követelményspecifikáció kérdése, hogy elég egyetlen ejtési magasságot figyelembe venni az összevetésnél, vagy többre van szükség. Ha a jelen példa valóságát vesszük, akkor elég egy érték, mert a szülők, amikor megveszik a labdát tesztelés céljából egy-kétszer lepattintják a boltban, de általában nem különböző magasságokból, hanem kb. csípő- vagy mellmagasságból.

Ha az egyéni __eq__ implementációban metódusok visszatérési értékét is össze kell hasonlítani, akkor célszerű a metódushívásokat az adatattribútumok összehasonlítását követően megtenni, hogy minél kisebb legyen a költsége az egyenlőségvizsgálatnak. Ugyanis, ha a balról jobbra történő kiértékeléskor először az adatattribútumok összevetése történik meg, akkor az esetleg futási időben költséges metódushívás csak akkor történik meg, ha minden adatattribútum megegyezik.

Lehetőség van persze arra, hogy az egyenlőségvizsgálatot a példányokat használó kliens kódban a példányok adatattribútumai alapján végezzük és az egyenlőség megállapításhoz még szükséges metódushívásokat, és ezek eredményének összehasonlítását is itt hajtjuk végre, majd a két kiadódó logikai érték alapján döntünk a példányok egyenlőségéről. Ez azonban a kód több pontján történő vizsgálat esetén kódismétlést eredményez, ami a hibalehetőséget növeli.

Egyenlőségvizsgálatról, valamint az __eq__ metódus, és általában az összehasonlító műveleteket megvalósító metódusok egyéni igényre szabásáról a Python tudásépítés lépésről lépésre című e-könyvben a „Kapcsolatban – relációk objektumok között” fejezet „Ami egyenlő az nem feltétlen azonos” alfejezetében, illetve a „Mágikus metódusok egyedi igényre szabott osztályokban” fejezet „Összehasonlító operátorok” alfejezetében kaphatunk további részleteket.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.