Selles artiklis tutvustan polümorfismi kasutamist GraphQLis. Enne alustamist tasub aga meenutada, mis on polümorfism ja GraphQL.
Polümorfism
Polümorfism on üks põhikomponente objektorienteeritud programmeerimine. Lihtsustuseks põhineb see asjaolul, et erinevate klasside objektidel on juurdepääs samale liidesele, mis tähendab, et me võime igaühelt neist oodata sama funktsionaalsust, kuid mitte tingimata samamoodi rakendatuna. Veebilehel Ruby arendajad võib saada polümorfism kolmel viisil:
Pärimine
Pärimine seisneb vanema klassi ja lasteklasside loomises (st pärimine vanema klassist). Alamklassid saavad vanemklassi funktsionaalsuse ja võimaldavad ka funktsionaalsust muuta ja lisada.
Näide:
klass Dokument
attr_reader :name
end
class PDFDocument < Document
def extension
:pdf
end
end
class ODTDocument < Document
def extension
:odt
end
end
Moodulid
Moodulid Ruby on palju kasutusvõimalusi. Üks neist on mixins (lugege mixins'idest lähemalt dokumendis Lõplik lagunemine: Ruby vs. Python). Ruby's saab mixins'e kasutada sarnaselt liidestega teistes programmeerimiskeeltes (nt. Java), näiteks saab neis defineerida meetodid, mis on ühised objektidele, mis sisaldavad antud mixin'i. Hea tava on lisada moodulitesse ainult lugemiseks mõeldud meetodeid, seega meetodeid, mis ei muuda selle objekti olekut.
Näide:
moodul Maksustatav
def tax
hind * 0.23
end
end
klass Auto
include Taxable
attr_reader :price
end
class Book
include Taxable
attr_reader :price
end
Pardi kirjutamine
See on üks dünaamiliselt tüpiseeritud keelte peamisi omadusi. Nimi tuleneb kuulsast testist: kui see näeb välja nagu part, ujub nagu part ja vikiseb nagu part, siis on see tõenäoliselt part. Veebileht programmeerija ei pea olema huvitatud sellest, millisesse klassi antud objekt kuulub. Olulised on meetodid, mida saab selle objektile kutsuda.
Kasutades ülaltoodud näites määratletud klassid:
klass Auto
attr_reader :price
def initialize(price)
@hind = hind
end
end
class Book
attr_reader :price
def initialize(price)
@hind = hind
end
end
car = Car.new(20.0)
book = Book.new(10.0)
[auto, raamat].map(&:price
GrapQL
GraphQL on suhteliselt uus APIde päringukeel. Selle eeliste hulka kuulub asjaolu, et selle süntaks on väga lihtne ja lisaks sellele otsustab klient ise, mida ta täpselt tahab saada, sest iga klient saab täpselt seda, mida ta tahab, ja mitte midagi muud.
See on ilmselt kõik, mida meil on hetkel vaja teada. Niisiis, lähme asja juurde.
Probleemi esitamine
Probleemi ja selle lahenduse paremaks mõistmiseks loome näite. Oleks hea, kui näide oleks nii originaalne kui ka üsna maalähedane. Selline, millega igaüks meist võib kunagi kokku puutuda. Kuidas oleks näiteks... loomad? Jah! Suurepärane idee!
Oletame, et meil on backend-rakendus, mis on kirjutatud keeles Ruby on Rails. See on juba kohandatud eespool nimetatud skeemi käsitlemiseks. Oletame ka, et meil on juba GraphQL konfigureeritud. Me tahame võimaldada kliendil teha päringu järgmise struktuuri raames:
{
allZoos : {
zoo: {
name
linn
loomad: {
...
}
}
}
}
Mida tuleks panna kolme punkti asemele, et saada puuduolevat teavet - saame teada hiljem.
Rakendamine
Järgnevalt tutvustan eesmärgi saavutamiseks vajalikke samme.
Päringu lisamine QueryType'ile
Kõigepealt tuleb määratleda, mida täpselt tähendab päring allZoos. Selleks peame külastama failiapp/graphql/types/query_type.rb ja määratleda päring:
moodul Tüübid
class QueryType < Types::BaseObject
väli :all_zoos, [Types::ZooType], null: false
def all_zoos
Zoo.all
end
end
end
Päring on juba määratletud. Nüüd on aeg määratleda tagastustüübid.
Tüüpide määratlus
Esimene nõutav tüüp on ZooType. Määratleme selle failis app/graphql/types/ zoo_type.rb:
moodul Tüübid
class ZooType < Types::BaseObject
väli :name, String, null: false
väli :city, String, null: false
väli :animals, [Types::AnimalType], null: false
end
end
Nüüd on aeg defineerida tüüp AnimalType:
moodul Tüübid
class AnimalType < Types::BaseUnion
possible_types ElephantType, CatType, DogType
def self.resolve_type(obj, ctx)
if obj.is_a?(Elephant)
ElephantType
elsif obj.is_a?(Cat)
CatType
elsif obj.is_a?(Dog)
DogType
end
end
end
end
Me peame loetlema kõik tüübid, mis võivad moodustada konkreetse liidu. 3.Me ületame funktsiooni self.resolve_object(obj, ctx),mis peab tagastama antud objekti tüübi.
Järgmine samm on loomade liikide määratlemine. Me teame siiski, et mõned väljad on kõigile loomadele ühised. Lisame need tüüpi AnimalInterface:
moodul Tüübid
moodul AnimalInterface
include Types::BaseInterface
väli :name, String, null: false
väli :age, täisarv, null: false
end
end
Selle liidese olemasolul saame jätkata konkreetsete loomade tüüpide määratlemist:
moodul Tüübid
class ElephantType < Types::BaseObject
implementeerib Types::AnimalInterface
väli :trunk_length, Float, null: false
end
end
moodul Tüübid
class CatType < Types::BaseObject
implementeerib Types::AnimalInterface
väli :hair_type, String, null: false
end
end
moodul Tüübid
class DogType < Types::BaseObject
implementeerib Types::AnimalInterface
väli :breed, String, null: false
end
end
See on kõik! Valmis! Üks viimane küsimus: kuidas me saame kasutada seda, mida me oleme teinud kliendi poolelt?
Päringu koostamine
{
allZoos : {
zoo: {
name
linn
loomad: {
__typename
... on ElephantType {
name
age
trunkLength
}
... on CatType {
name
age
hairType
}
... on DogType {
name
age
breed
}
}
}
}
}
Me võime siin kasutada täiendavat __typename-välja, mis tagastab antud elemendi täpse tüübi (nt CatType). Milline näeb välja näidisvastus?
Selle lähenemisviisi üks puudus on ilmne. Päringus peame iga tüübi puhul sisestama nime ja vanuse, kuigi me teame, et need väljad on kõigil loomadel olemas. See ei ole häiriv, kui kollektsioon sisaldab täiesti erinevaid objekte. Antud juhul on aga loomadel peaaegu kõik väljad ühised. Kas seda saab kuidagi parandada?
Loomulikult! Teeme failis esimese muudatuse app/graphql/types/zoo_type.rb:
moodul Tüübid
class ZooType < Types::BaseObject
väli :name, String, null: false
väli :city, String, null: false
väli :animals, [Types::AnimalInterface], null: false
end
end
Me ei vaja enam seda liitu, mida me varem määratlesime. Me muudame Tüübid::AnimalType aadressile Tüübid::AnimalInterface.
Järgmine samm on lisada funktsioon, mis tagastab tüübist Tüübid :: AnimalInterface ja lisage ka nimekiri orphan_types, st tüübid, mida ei kasutata kunagi otseselt:
moodul Tüübid
moodul AnimalInterface
include Types::BaseInterface
väli :name, String, null: false
väli :age, täisarv, null: false
definition_methods do
def resolve_type(obj, ctx)
if obj.is_a?(Elephant)
ElephantType
elsif obj.is_a?(Cat)
CatType
elsif obj.is_a?(Dog)
DogType
end
end
end
orphan_types Types::ElephantType, Types::CatType, Types::DogType
end
end
Tänu sellele lihtsale protseduurile on päring vähem keerulises vormis:
{
allZoos : {
zoo: {
name
linn
loomad: {
__typename
name
age
... on ElephantType {
trunkLength
}
... on CatType {
hairType
}
... on DogType {
breed
}
}
}
}
}
Kokkuvõte
GraphQL on tõesti suurepärane lahendus. Kui te seda veel ei tea, proovige seda. Uskuge mind, see on seda väärt. See teeb suurepärast tööd näiteks REST API-des ilmnevate probleemide lahendamisel. Nagu ma eespool näitasin, polümorfism ei ole selle jaoks tõeline takistus. Esitasin kaks meetodit selle lahendamiseks. Meeldetuletus:
Kui töötate ühise baasiga või ühise liidesega objektide loeteluga - kasutage liideseid,
Kui te töötate erineva struktuuriga objektide loendiga, kasutage teistsugust liidest - kasutage liitu union