Σε αυτό το άρθρο, θα παρουσιάσω τη χρήση του πολυμορφισμού στην GraphQL. Πριν ξεκινήσω, ωστόσο, αξίζει να υπενθυμίσω τι είναι ο πολυμορφισμός και η GraphQL.
Πολυμορφισμός
Πολυμορφισμός αποτελεί βασικό συστατικό της αντικειμενοστραφής προγραμματισμός. Για να απλοποιήσουμε τα πράγματα, βασίζεται στο γεγονός ότι τα αντικείμενα διαφορετικών κλάσεων έχουν πρόσβαση στην ίδια διεπαφή, πράγμα που σημαίνει ότι μπορούμε να περιμένουμε από καθένα από αυτά την ίδια λειτουργικότητα, αλλά όχι απαραίτητα υλοποιημένη με τον ίδιο τρόπο. Στο Προγραμματιστές Ruby μπορεί να λάβει πολυμορφισμός με τρεις τρόπους:
Κληρονομικότητα
Κληρονομικότητα συνίσταται στη δημιουργία μιας γονικής κλάσης και κλάσεων-παιδιών (δηλαδή, που κληρονομούν από τη γονική κλάση). Οι υποκλάσεις λαμβάνουν τη λειτουργικότητα της γονικής κλάσης και σας επιτρέπουν επίσης να αλλάξετε και να προσθέσετε μια λειτουργικότητα.
Παράδειγμα:
κλάση Document
attr_reader :name
end
class PDFDocument < Document
def extension
:pdf
end
end
class ODTDocument < Document
def extension
:odt
end
end
Ενότητες
Ενότητες σε Ruby έχουν πολλές χρήσεις. Μία από αυτές είναι τα mixins (διαβάστε περισσότερα για τα mixins στο Η απόλυτη ανάλυση: Python). Τα mixins στη Ruby μπορούν να χρησιμοποιηθούν παρόμοια με τις διασυνδέσεις σε άλλες γλώσσες προγραμματισμού (π.χ. σε Java), για παράδειγμα, μπορείτε να ορίσετε σε αυτές μεθόδους κοινές για τα αντικείμενα που θα περιέχουν ένα συγκεκριμένο mixin. Αποτελεί καλή πρακτική να συμπεριλαμβάνετε μεθόδους μόνο για ανάγνωση στις μονάδες, δηλαδή μεθόδους που δεν θα τροποποιούν την κατάσταση αυτού του αντικειμένου.
Παράδειγμα:
ενότητα Φορολογητέα
def tax
τιμή * 0.23
end
end
κλάση Αυτοκίνητο
include Taxable
attr_reader :price
end
class Book
include Taxable
attr_reader :price
end
Δακτυλογράφηση πάπιας
Αυτό είναι ένα από τα βασικά χαρακτηριστικά των δυναμικά τυποποιημένων γλωσσών. Το όνομα προέρχεται από το διάσημο τεστ αν μοιάζει με πάπια, κολυμπάει σαν πάπια και λαλεί σαν πάπια, τότε μάλλον είναι πάπια. Το προγραμματιστής δεν χρειάζεται να ενδιαφέρεται για το σε ποια κλάση ανήκει το συγκεκριμένο αντικείμενο. Αυτό που έχει σημασία είναι οι μέθοδοι που μπορούν να κληθούν σε αυτό το αντικείμενο.
Χρησιμοποιώντας τις κλάσεις που ορίζονται στο παραπάνω παράδειγμα:
κλάση Αυτοκίνητο
attr_reader :price
def initialize(price)
@price = price
end
end
class 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 είναι μια σχετικά νέα γλώσσα ερωτημάτων για APIs. Στα πλεονεκτήματά της συγκαταλέγεται το γεγονός ότι έχει πολύ απλή σύνταξη και, επιπλέον, ο πελάτης αποφασίζει τι ακριβώς θέλει να πάρει, καθώς κάθε πελάτης παίρνει ακριβώς αυτό που θέλει και τίποτε άλλο.
Αυτό είναι μάλλον το μόνο που χρειάζεται να γνωρίζουμε προς το παρόν. Ας μπούμε λοιπόν στο θέμα.
Παρουσίαση του προβλήματος
Για να κατανοήσουμε καλύτερα το πρόβλημα και τη λύση του, ας δημιουργήσουμε ένα παράδειγμα. Καλό θα ήταν το παράδειγμα να είναι πρωτότυπο και αρκετά προσγειωμένο. Ένα παράδειγμα που ο καθένας μας μπορεί να αντιμετωπίσει κάποια μέρα. Τι θα λέγατε για... ζώα; Ναι! Υπέροχη ιδέα!
Ας υποθέσουμε ότι έχουμε μια backend εφαρμογή γραμμένη σε Ruby on Rails. Έχει ήδη προσαρμοστεί για να χειριστεί το παραπάνω σχήμα. Ας υποθέσουμε επίσης ότι έχουμε ήδη GraphQL διαμορφωμένο. Θέλουμε να δώσουμε τη δυνατότητα στον πελάτη να κάνει μια έρευνα με την ακόλουθη δομή:
{
allZoos : {
zoo: {
name
city
ζώα: {
...
}
}
}
}
Τι θα πρέπει να βάλετε αντί για τις τρεις τελείες για να λάβετε τις πληροφορίες που λείπουν - θα το μάθουμε αργότερα.
Εφαρμογή
Παρακάτω θα παρουσιάσω τα βήματα που απαιτούνται για την επίτευξη του στόχου.
Προσθήκη ερώτησης στο QueryType
Πρώτον, πρέπει να ορίσετε τι ακριβώς σημαίνει το ερώτημα allZoos. Για να το κάνουμε αυτό, πρέπει να επισκεφτούμε το αρχείοapp/graphql/types/query_type.rb και ορίστε το ερώτημα:
ενότητα Τύποι
class QueryType < Types::BaseObject
field :all_zoos, [Types::ZooType], null: false
def all_zoos
Zoo.all
end
end
end
Το ερώτημα έχει ήδη οριστεί. Τώρα ήρθε η ώρα να ορίσουμε τους τύπους επιστροφής.
Ορισμός των τύπων
Ο πρώτος απαιτούμενος τύπος θα είναι ο ZooType. Ας τον ορίσουμε στο αρχείο app/graphql/types/ zoo_type.rb:
ενότητα Τύποι
class ZooType < Types::BaseObject
field :name, String, null: false
field :city, String, null: false
field :animals, [Types::AnimalType], null: false
end
end
Τώρα ήρθε η ώρα να ορίσουμε τον τύπο AnimalType:
ενότητα Τύποι
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
Πρέπει να απαριθμήσουμε όλους τους τύπους που μπορούν να αποτελέσουν μια δεδομένη ένωση. 3.Παρακάμπτουμε τη συνάρτηση self.resolve_object(obj, ctx),η οποία πρέπει να επιστρέφει τον τύπο ενός συγκεκριμένου αντικειμένου.
Το επόμενο βήμα είναι ο καθορισμός των τύπων των ζώων. Ωστόσο, γνωρίζουμε ότι ορισμένα πεδία είναι κοινά για όλα τα ζώα. Ας τα συμπεριλάβουμε στον τύπο AnimalInterface:
ενότητα Τύποι
module AnimalInterface
include Types::BaseInterface
πεδίο :name, String, null: false
πεδίο :age, Ακέραιος αριθμός, null: false
end
end
Έχοντας αυτή τη διεπαφή, μπορούμε να προχωρήσουμε στον καθορισμό των τύπων των συγκεκριμένων ζώων:
ενότητα Τύποι
class ElephantType < Types::BaseObject
υλοποιεί Types::AnimalInterface
πεδίο :trunk_length, Float, null: false
end
end
module Τύποι
class CatType < Types::BaseObject
υλοποιεί Types::AnimalInterface
πεδίο :hair_type, String, null: false
end
end
module Τύποι
class DogType < Types::BaseObject
υλοποιεί Types::AnimalInterface
πεδίο :breed, String, null: false
end
end
Αυτό είναι! Έτοιμοι! Μια τελευταία ερώτηση: πώς μπορούμε να χρησιμοποιήσουμε αυτό που κάναμε από την πλευρά του πελάτη;
Δημιουργία του ερωτήματος
{
allZoos : {
zoo: {
name
city
ζώα: {
__typename
... on ElephantType {
name
age
trunkLength
}
... on CatType {
name
age
hairType
}
... on DogType {
name
age
breed
}
}
}
}
}
Μπορούμε να χρησιμοποιήσουμε ένα πρόσθετο πεδίο __typename εδώ, το οποίο θα επιστρέψει τον ακριβή τύπο ενός συγκεκριμένου στοιχείου (π.χ. CatType). Πώς θα μοιάζει ένα δείγμα απάντησης;
Ένα μειονέκτημα αυτής της προσέγγισης είναι προφανές. Στο ερώτημα, πρέπει να εισάγουμε το όνομα και την ηλικία σε κάθε τύπο, παρόλο που γνωρίζουμε ότι όλα τα ζώα έχουν αυτά τα πεδία. Αυτό δεν είναι ενοχλητικό όταν η συλλογή περιέχει εντελώς διαφορετικά αντικείμενα. Σε αυτή την περίπτωση, όμως, τα ζώα μοιράζονται σχεδόν όλα τα πεδία. Μπορεί να βελτιωθεί με κάποιο τρόπο;
Φυσικά! Κάνουμε την πρώτη αλλαγή στο αρχείο app/graphql/types/zoo_type.rb:
ενότητα Τύποι
class ZooType < Types::BaseObject
field :name, String, null: false
field :city, String, null: false
field :animals, [Types::AnimalInterface], null: false
end
end
Δεν χρειαζόμαστε πλέον την ένωση που έχουμε ορίσει πριν. Αλλάζουμε Τύποι::AnimalType στο Τύποι::AnimalInterface.
Το επόμενο βήμα είναι να προσθέσουμε μια συνάρτηση που επιστρέφει έναν τύπο από το Τύποι :: AnimalInterface και προσθέστε επίσης μια λίστα με τύπους orphan_types, δηλαδή τύπους που δεν χρησιμοποιούνται ποτέ άμεσα:
ενότητα Τύποι
module AnimalInterface
include Types::BaseInterface
πεδίο :name, String, null: false
πεδίο :age, Ακέραιος αριθμός, 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
Χάρη σε αυτή την απλή διαδικασία, το ερώτημα έχει μια λιγότερο περίπλοκη μορφή:
{
allZoos : {
zoo: {
name
city
ζώα: {
__typename
name
age
... on ElephantType {
trunkLength
}
... on CatType {
hairType
}
... on DogType {
breed
}
}
}
}
}
Περίληψη
GraphQL είναι μια πραγματικά εξαιρετική λύση. Αν δεν το ξέρετε ακόμα, δοκιμάστε το. Πιστέψτε με, αξίζει τον κόπο. Κάνει εξαιρετική δουλειά στην επίλυση προβλημάτων που εμφανίζονται, για παράδειγμα, στα REST APIs. Όπως έδειξα παραπάνω, πολυμορφισμός δεν αποτελεί πραγματικό εμπόδιο για αυτό. Παρουσίασα δύο μεθόδους για την αντιμετώπισή του. Υπενθύμιση:
Εάν εργάζεστε σε έναν κατάλογο αντικειμένων με κοινή βάση ή κοινή διεπαφή - χρησιμοποιήστε τις διεπαφές,
Αν χειρίζεστε μια λίστα αντικειμένων με διαφορετική δομή, χρησιμοποιήστε μια διαφορετική διεπαφή - χρησιμοποιήστε την ένωση