Šajā rakstā es iepazīstināšu ar polimorfisma izmantošanu GraphQL. Tomēr, pirms es sāku, ir vērts atgādināt, kas ir polimorfisms un GraphQL.
Polimorfisms
Polimorfisms ir svarīgs komponents objektorientēta programmēšana. Lai vienkāršotu lietas, tā pamatā ir fakts, ka dažādu klašu objektiem ir piekļuve vienam un tam pašam interfeisam, kas nozīmē, ka no katra no tiem varam sagaidīt vienu un to pašu funkcionalitāti, bet ne vienmēr tā ir īstenota vienādi. In Rubīns izstrādātāji var iegūt polimorfisms trīs veidos:
Mantojums
Mantojums ir izveidot vecāku klasi un bērnu klases (t. i., mantot no vecāku klases). Apakšklases saņem vecākās klases funkcionalitāti, kā arī ļauj mainīt un pievienot funkcionalitāti.
Piemērs:
klase Dokuments
attr_reader :name
beigas
klase PDFDocument < Document
def paplašinājums
:pdf
end
end
klase ODTDocument < Dokuments
def paplašinājums
:odt
end
end
Moduļi
Moduļi Rubīns ir daudz pielietojumu. Viens no tiem ir miksīni (vairāk par miksīniem lasiet sadaļā Galīgais sadalījums: Rubīns vs Python). Sajaukumus Ruby valodā var izmantot līdzīgi kā saskarnes citās valodās. programmēšanas valodas (piem. Java), piemēram, tajos var definēt metodes, kas ir kopīgas objektiem, kuri satur konkrēto miksīnu. Laba prakse ir moduļos iekļaut tikai lasīšanai paredzētas metodes, t.i., metodes, kas nemainīs šī objekta stāvokli.
Piemērs:
modulis Ar nodokli apliekamais
def tax
cena * 0,23
beigas
end
klase Automašīna
include Taxable
attr_reader :price
end
klase Book
include Taxable
attr_reader :price
end
Pīles drukāšana
Tā ir viena no dinamiski tipizētu valodu galvenajām iezīmēm. Nosaukums cēlies no slavenā pārbaudījuma, ja tas izskatās kā pīle, peld kā pīle un krakšķ kā pīle, tad tas, iespējams, ir pīle. . programmētājs nav jāinteresējas, kurai klasei pieder dotais objekts. Svarīgas ir metodes, kuras var izsaukt uz šo objektu.
Izmantojot iepriekš minētajā piemērā definētās klases:
klase Automobilis
attr_reader :price
def initialize(price)
@price = price
end
end
klase 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 ir salīdzinoši jauna API pieprasīšanas valoda. Tās priekšrocības ir tādas, ka tai ir ļoti vienkārša sintakse, turklāt klients pats izlemj, ko tieši viņš vēlas saņemt, jo katrs klients saņem tieši to, ko vēlas, un neko citu.
Iespējams, tas ir viss, kas mums šobrīd ir jāzina. Pārejam pie lietas būtības.
Problēmas izklāsts
Lai labāk izprastu problēmu un tās risinājumu, izveidosim piemēru. Būtu labi, ja šis piemērs būtu gan oriģināls, gan diezgan piezemēts. Tāds, lai katrs no mums kādu dienu var sastapties. Kā ir ar... dzīvniekiem? Jā! Lieliska ideja!
Pieņemsim, ka mums ir backend lietojumprogramma, kas rakstīta valodā Ruby on Rails. Tā jau ir pielāgota iepriekš minētās shēmas izmantošanai. Pieņemsim arī, ka mums jau ir GraphQL konfigurēts. Mēs vēlamies, lai klients varētu veikt pieprasījumu ar šādu struktūru:
{
allZoos : {
zooloģiskais dārzs: {
nosaukums
pilsēta
dzīvnieki: {
...
}
}
}
}
Ko vajadzētu ievietot trīs punktu vietā, lai iegūtu trūkstošo informāciju - mēs uzzināsim vēlāk.
Īstenošana
Turpmāk izklāstīšu soļus, kas nepieciešami mērķa sasniegšanai.
Vaicājuma pievienošana QueryType
Vispirms ir jādefinē, ko tieši nozīmē vaicājums allZoos. Lai to izdarītu, mums jāapmeklē failsapp/graphql/types/query_type.rb un definēt vaicājumu:
moduļu veidi
klase QueryType < Types::BaseObject
field :all_zoos, [Types::ZooType], null: false
def all_zoos
Zoo.all
end
end
end
Vaicājums jau ir definēts. Tagad ir pienācis laiks definēt atdeves tipus.
Veidu definīcijas
Pirmais nepieciešamais tips būs ZooType. Definēsim to failā app/graphql/types/ zoo_type.rb:
moduļu veidi
klase ZooType < Types::BaseObject
field :name, String, null: false
lauks :city, String, null: false
lauks :animals, [Types::AnimalType], null: false
end
end
Tagad ir pienācis laiks definēt tipu AnimalType:
moduļu veidi
klase 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
Mums ir jāuzskaita visi veidi, kas var veidot konkrēto savienību. 3.We override funkciju self.resolve_object(obj, ctx),kam jāatgriež dotā objekta tips.
Nākamais solis ir noteikt dzīvnieku veidus. Tomēr mēs zinām, ka daži lauki ir kopīgi visiem dzīvniekiem. Iekļausim tos tipā AnimalInterface:
moduļu veidi
modulis AnimalInterface
include Types::BaseInterface
field :name, String, null: false
field :age, Integer, null: false
end
end
Izmantojot šo saskarni, mēs varam turpināt definēt konkrētu dzīvnieku tipus:
moduļu veidi
klase ElephantType < Types::BaseObject
implementē Types::AnimalInterface
field :trunk_length, Float, null: false
end
end
modulis Types
klase CatType < Types::BaseObject
implementē Types::AnimalInterface
field :hair_type, String, null: false
end
end
modulis Types
klase DogType < Types::BaseObject
implementē Types::AnimalInterface
field :breed, String, null: false
end
end
Tas ir viss! Gatavs! Pēdējais jautājums: kā mēs varam izmantot to, ko esam izdarījuši no klienta puses?
Vaicājuma izveide
{
allZoos : {
zooloģiskais dārzs: {
nosaukums
pilsēta
dzīvnieki: {
__typename
... on ElephantType {
nosaukums
vecums
stumbra garums
}
... on CatType {
nosaukums
vecums
hairType
}
... on DogType {
nosaukums
vecums
šķirne
}
}
}
}
}
Šeit mēs varam izmantot papildu lauku __typename, kas atgriezīs precīzu dotā elementa tipu (piemēram, CatType). Kā izskatīsies atbildes paraugs?
Viens šīs pieejas trūkums ir acīmredzams. Pieprasījumā katram tipam ir jāievada vārds un vecums, lai gan mēs zinām, ka šie lauki ir visiem dzīvniekiem. Tas nav traucējoši, ja kolekcijā ir pilnīgi atšķirīgi objekti. Tomēr šajā gadījumā dzīvniekiem ir gandrīz visi kopīgi lauki. Vai to var kaut kā uzlabot?
Protams! Mēs veicam pirmās izmaiņas failā app/graphql/types/zoo_type.rb:
moduļu veidi
klase ZooType < Types::BaseObject
field :name, String, null: false
lauks :city, String, null: false
lauks :animals, [Types::AnimalInterface], null: false
end
end
Mums vairs nav vajadzīga tā savienība, ko esam definējuši iepriekš. Mēs mainām Types::AnimalType uz Types::AnimalInterface.
Nākamais solis ir pievienot funkciju, kas atgriež tipu no Veidi :: AnimalInterface un pievienot arī sarakstu ar orphan_types, t.i., tipiem, kas nekad netiek tieši izmantoti:
moduļu veidi
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
Pateicoties šai vienkāršai procedūrai, pieprasījumam ir mazāk sarežģīta forma:
{
allZoos : {
zooloģiskais dārzs: {
nosaukums
pilsēta
dzīvnieki: {
__typename
nosaukums
vecums
... on ElephantType {
stumbra garums
}
... on CatType {
hairType
}
... on DogType {
šķirne
}
}
}
}
}
Kopsavilkums
GraphQL ir patiešām lielisks risinājums. Ja vēl nezināt, izmēģiniet to. Ticiet man, tas ir tā vērts. Tas lieliski risina problēmas, kas rodas, piemēram, REST API. Kā es parādīju iepriekš, polimorfisms nav reāls šķērslis tam. Es iepazīstināju ar divām metodēm, kā to risināt. Atgādinājums:
Ja darbojaties ar objektu sarakstu ar kopīgu bāzi vai kopīgu saskarni - izmantojiet saskarnes,
Ja darbojaties ar objektu sarakstu ar atšķirīgu struktūru, izmantojiet citu saskarni - izmantojiet union