Šiame straipsnyje pristatysiu polimorfizmo naudojimą GraphQL. Tačiau prieš pradedant verta priminti, kas yra polimorfizmas ir GraphQL.
Polimorfizmas
Polimorfizmas yra pagrindinė sudedamoji į objektus orientuotas programavimas. Kad būtų paprasčiau, ji grindžiama tuo, kad skirtingų klasių objektai turi prieigą prie tos pačios sąsajos, o tai reiškia, kad iš kiekvieno iš jų galime tikėtis tos pačios funkcijos, tačiau nebūtinai įgyvendintos tuo pačiu būdu. Svetainėje Ruby kūrėjai gali gauti polimorfizmas trimis būdais:
Paveldėjimas
Paveldėjimas sukuriama tėvinė klasė ir antrinės klasės (t. y. paveldimos iš tėvinės klasės). Pakartotinės klasės perima tėvinės klasės funkcionalumą, taip pat leidžia keisti ir papildyti funkcionalumą.
Pavyzdys:
klasė Document
attr_reader :name
pabaiga
klasė PDFDocument < Dokumentas
def plėtinys
:pdf
end
end
klasė ODTDocument < Dokumentas
def plėtinys
:odt
end
end
Moduliai
Moduliai Ruby turi daugybę panaudojimo būdų. Vienas iš jų - mišiniai (daugiau apie mišinius skaitykite Galutinis suskirstymas: Python vs. Python). Ruby mišinius galima naudoti panašiai kaip sąsajas kitose programose. programavimo kalbos (pvz. Java), pavyzdžiui, juose galite apibrėžti metodus, bendrus objektams, kuriuose bus tam tikras mišinys. Gera praktika yra į modulius įtraukti tik skaitymo metodus, t. y. metodus, kurie nekeis šio objekto būsenos.
Pavyzdys:
modulis Apmokestinamasis
def tax
kaina * 0,23
pabaiga
end
klasė Automobilis
include Taxable
attr_reader :price
end
klasė Book
include Taxable
attr_reader :price
end
Ančių spausdinimas
Tai viena iš pagrindinių dinamiškai tipizuotų kalbų savybių. Pavadinimas kilo iš garsaus testo, jei atrodo kaip antis, plaukia kaip antis ir klykia kaip antis, vadinasi, tikriausiai yra antis. Adresas programuotojas nereikia domėtis, kuriai klasei priklauso duotas objektas. Svarbu, kokius metodus galima iškviesti šiam objektui.
klasė Automobilis
attr_reader :price
def initialize(price)
@price = price
end
end
klasė Book
attr_reader :price
def initialize(price)
@price = price
end
end
car = Car.new(20.0)
book = Book.new(10.0)
[car, book].map(&:price
GrapQL
GraphQL yra palyginti nauja API užklausų kalba. Jos privalumai - labai paprasta sintaksė, be to, klientas pats sprendžia, ką tiksliai nori gauti, nes kiekvienas klientas gauna būtent tai, ko nori, ir nieko daugiau.
Šiuo metu tai tikriausiai viskas, ką mums reikia žinoti. Taigi pereikime prie esmės.
Problemos pristatymas
Kad geriau suprastume problemą ir jos sprendimą, sukurkime pavyzdį. Būtų gerai, kad pavyzdys būtų originalus ir gana žemiškas. Toks, kad kiekvienas iš mus kada nors gali susidurti. Kaip dėl... gyvūnų? Taip! Puiki idėja!
Tarkime, kad turime galinę programą, parašytą Ruby on Rails. Ji jau pritaikyta pirmiau nurodytai schemai tvarkyti. Taip pat darykime prielaidą, kad jau turime GraphQL sukonfigūruotas. Norime, kad klientas galėtų pateikti užklausą pagal tokią struktūrą:
{
allZoos : {
zoologijos sodas: {
pavadinimas
miestas
gyvūnai: {
...
}
}
}
}
Ką reikėtų įrašyti vietoj trijų taškų, kad gautume trūkstamą informaciją - sužinosime vėliau.
Įgyvendinimas
Toliau pateiksiu žingsnius, kurių reikia šiam tikslui pasiekti.
Užklausos pridėjimas prie QueryType
Pirmiausia reikia apibrėžti, ką tiksliai reiškia užklausa allZoos. Norėdami tai padaryti, turime apsilankyti faileapp/graphql/types/query_type.rb ir apibrėžkite užklausą:
modulių tipai
klasė QueryType < Types::BaseObject
field :all_zoos, [Types::ZooType], null: false
def all_zoos
Zoo.all
end
end
end
Užklausa jau apibrėžta. Dabar laikas apibrėžti grąžinimo tipus.
Tipų apibrėžimas
Pirmasis reikalaujamas tipas bus ZooType. Apibrėžkime jį faile app/graphql/types/ zoo_type.rb:
modulių tipai
klasė ZooType < Types::BaseObject
field :name, String, null: false
laukas :city, String, null: false
laukas :animals, [Types::AnimalType], null: false
end
end
Dabar laikas apibrėžti tipą AnimalType:
modulių tipai
klasė 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
Turime išvardyti visus tipus, kurie gali sudaryti tam tikrą sąjungą. 3.Mes pakeičiame funkciją self.resolve_object(obj, ctx),kuris turi grąžinti duoto objekto tipą.
Kitas žingsnis - apibrėžti gyvūnų tipus. Tačiau žinome, kad kai kurie laukai yra bendri visiems gyvūnams. Įtraukime juos į tipą AnimalInterface:
modulių tipai
modulis AnimalInterface
include Types::BaseInterface
field :name, String, null: false
field :age, Integer, null: false
end
end
Turėdami šią sąsają, galime apibrėžti konkrečių gyvūnų tipus:
modulių tipai
klasė ElephantType < Types::BaseObject
įgyvendina Types::AnimalInterface
field :trunk_length, Float, null: false
end
end
modulis Types
klasė CatType < Types::BaseObject
įgyvendina Types::AnimalInterface
field :hair_type, String, null: false
end
end
modulis Tipai
klasė DogType < Types::BaseObject
įgyvendina Types::AnimalInterface
field :breed, String, null: false
end
end
Štai ir viskas! Paruošta! Paskutinis klausimas: kaip galime panaudoti tai, ką padarėme iš kliento pusės?
Užklausos kūrimas
{
allZoos : {
zoologijos sodas: {
pavadinimas
miestas
gyvūnai: {
__typename
... on ElephantType {
pavadinimas
amžius
kamieno ilgis
}
... on CatType {
vardas
amžius
hairType
}
... on DogType {
vardas
amžius
veislė
}
}
}
}
}
Čia galime naudoti papildomą lauką __typename, kuris grąžins tikslų duoto elemento tipą (pvz., CatType). Kaip atrodys pavyzdinis atsakymas?
Akivaizdus vienas šio metodo trūkumas. Užklausoje turime įvesti kiekvieno tipo pavadinimą ir amžių, nors žinome, kad visi gyvūnai turi šiuos laukus. Tai netrukdo, kai kolekcijoje yra visiškai skirtingų objektų. Tačiau šiuo atveju gyvūnai turi beveik visus bendrus laukus. Ar tai galima kaip nors patobulinti?
Žinoma! Atliekame pirmąjį failo pakeitimą app/graphql/types/zoo_type.rb:
modulių tipai
klasė ZooType < Types::BaseObject
field :name, String, null: false
laukas :city, String, null: false
laukas :animals, [Types::AnimalInterface], null: false
end
end
Mums nebereikia sąjungos, kurią apibrėžėme anksčiau. Keičiame Tipai::AnimalType į Tipai::AnimalInterface.
Kitas žingsnis - pridėti funkciją, kuri grąžina tipą iš Tipai :: AnimalInterface taip pat pridėkite "orphan_types" sąrašą, t. y. tipų, kurie niekada nėra tiesiogiai naudojami:
modulių tipai
modulis AnimalInterface
include Types::BaseInterface
field :name, String, null: false
field :age, Integer, 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
Dėl šios paprastos procedūros užklausos forma nėra tokia sudėtinga:
{
allZoos : {
zoologijos sodas: {
pavadinimas
miestas
gyvūnai: {
__typename
vardas
amžius
... on ElephantType {
kamieno ilgis
}
... on CatType {
hairType
}
... on DogType {
veislė
}
}
}
}
}
Santrauka
GraphQL yra tikrai puikus sprendimas. Jei dar nežinote, išbandykite jį. Patikėkite manimi, verta. Jis puikiai sprendžia problemas, kylančias, pavyzdžiui, REST API. Kaip parodžiau pirmiau, polimorfizmas nėra tikra kliūtis. Pateikiau du būdus, kaip ją įveikti. Primename:
Jei dirbate su objektų, turinčių bendrą pagrindą arba bendrą sąsają, sąrašu, naudokite sąsajas,
Jei dirbate su kitokios struktūros objektų sąrašu, naudokite kitą sąsają - naudokite sąjungą