Polymorphismus

Polymorphismus ist eine Schlüsselkomponente von objektorientierte Programmierung. Der Einfachheit halber basiert sie auf der Tatsache, dass Objekte verschiedener Klassen Zugriff auf dieselbe Schnittstelle haben, was bedeutet, dass wir von jeder von ihnen dieselbe Funktionalität erwarten können, die aber nicht unbedingt auf dieselbe Weise implementiert ist. In Rubinrot Entwickler erhalten können Polymorphismus auf drei Arten:

Vererbung

Vererbung besteht darin, eine übergeordnete Klasse und untergeordnete Klassen (d. h., die von der übergeordneten Klasse erben) zu erstellen. Unterklassen erhalten die Funktionalität der übergeordneten Klasse und ermöglichen es Ihnen auch, eine Funktionalität zu ändern und hinzuzufügen.

Beispiel:

Klasse Dokument
  attr_leser :name
end

class PDFDocument < Dokument
  def extension
    :pdf
  end
end

class ODTDocument < Dokument
  def Erweiterung
    :odt
  end
end

Module

Module in Rubinrot haben viele Verwendungsmöglichkeiten. Eine davon sind Mixins (lesen Sie mehr über Mixins in Die ultimative Aufschlüsselung: Ruby vs. Python). Mixins können in Ruby ähnlich wie Schnittstellen in anderen Programmiersprachen verwendet werden (z. B. in Java) können Sie zum Beispiel Methoden definieren, die allen Objekten gemeinsam sind, die ein bestimmtes Mixin enthalten werden. Es ist eine gute Praxis, schreibgeschützte Methoden in Module aufzunehmen, also Methoden, die den Zustand des Objekts nicht verändern.

Beispiel:

Modul steuerpflichtig
  Def-Steuer

     Preis * 0,23
  end
end

Klasse Auto
  include steuerpflichtig
 attr_leser :preis
end

class Buch
  include steuerpflichtig

 attr_leser :preis
end

Ente tippen

Dies ist eine der wichtigsten Eigenschaften dynamisch typisierter Sprachen. Der Name stammt von dem berühmten Test: Wenn es wie eine Ente aussieht, wie eine Ente schwimmt und wie eine Ente quakt, dann ist es wahrscheinlich eine Ente. Die Programmierer muss sich nicht dafür interessieren, zu welcher Klasse das gegebene Objekt gehört. Wichtig sind die Methoden, die auf diesem Objekt aufgerufen werden können.

Verwenden Sie die im obigen Beispiel definierten Klassen:

Klasse Auto
  attr_leser :preis

 def initialize(preis)
    @Preis = Preis
   end
end

class Buch
  attr_leser :preis

 def initialize(preis)
    @Preis = Preis
  end
end

Auto = Auto.neu(20.0)
Buch = Buch.neu(10.0)

[Auto, Buch].map(&:Preis

GrapQL

GraphQL ist eine relativ neue Abfragesprache für APIs. Zu ihren Vorteilen gehört die Tatsache, dass sie eine sehr einfache Syntax hat und außerdem der Kunde entscheidet, was genau er erhalten möchte, da jeder Kunde genau das bekommt, was er möchte und nichts anderes.

Beispielabfrage in GraphQL:

{
  allUsers {
     Benutzer {
        id
        Anmeldung
        E-Mail

       }
     }
   }

Beispielhafte Antwort:

{
  "allUsers": {
    "users": [
     {
        "id": 1,
        "login": "user1",
        "email": "[email protected]"
      },
      {
        "id": 2,
        "login": "user2",
        "email": "[email protected]"
      },
    ]
  }
}

Das ist wahrscheinlich alles, was wir im Moment wissen müssen. Also, kommen wir zur Sache.

Darstellung des Problems

To best understand the problem and its solution, let’s create an example. It would be good if the example was both original and fairly down-to-earth. One that each of us can encounter someday. How about… animals? Yes! Great idea!

ruby und grapql polymorphismus - tiere

Angenommen, wir haben eine Backend-Anwendung, die in Ruby on Rails. Sie ist bereits an das obige Schema angepasst. Nehmen wir außerdem an, dass wir bereits über GraphQL konfiguriert. Wir möchten dem Kunden die Möglichkeit geben, eine Anfrage in der folgenden Struktur zu stellen:

{
 allZoos : {
    zoo: {
      Name:
      Stadt:
      Tiere: {
        ...
      }
    }
  }
}

Was anstelle der drei Punkte zu setzen ist, um die fehlenden Informationen zu erhalten, werden wir später herausfinden.

Umsetzung

Im Folgenden werde ich die zur Erreichung dieses Ziels erforderlichen Schritte erläutern.

Hinzufügen einer Abfrage zu QueryType

Zunächst muss definiert werden, was genau die Abfrage allZoos bedeutet. Zu diesem Zweck müssen wir die Dateiapp/graphql/types/query_type.rb und definieren Sie die Abfrage:

   Modul Typen
      Klasse QueryType < Types::BaseObject
       Feld :all_zoos, [Types::ZooType], null: false

       def all_zoos
          Zoo.alle
       end
    end
 end

Die Abfrage ist bereits definiert. Nun ist es an der Zeit, die Rückgabetypen zu definieren.

Definition der Typen

Der erste erforderliche Typ ist ZooType. Definieren wir ihn in der Datei app/graphql/types/ zoo_type.rb:

Modul Typen
  Klasse ZooType < Typen::BaseObject
    Feld :name, String, null: false
    feld :stadt, String, null: false
    feld :tiere, [Typen::TierTyp], null: false
  end
end

Nun ist es an der Zeit, den Typ AnimalType zu definieren:

Modul Typen
  Klasse AnimalType < Types::BaseUnion
   mögliche_Typen ElefantTyp, KatzeTyp, HundTyp

     def self.resolve_type(obj, ctx)
       if obj.is_a?(Elefant)
          ElefantTyp
       elsif obj.is_a?(Katze)
         CatType
       elsif obj.is_a?(Hund)
        HundTyp
      end
    end
  end
end

Was sehen wir in der Code oben?

  1. Der AnimalType erbt von Typen::BaseUnion.
  2. Wir müssen alle Typen auflisten, die eine bestimmte Gewerkschaft bilden können.
    3. wir überschreiben die Funktion self.resolve_object(obj, ctx),die den Typ eines bestimmten Objekts zurückgeben muss.

Der nächste Schritt besteht darin, die Tierarten zu definieren. Wir wissen jedoch, dass einige Felder für alle Tiere gleich sind. Nehmen wir sie in den Typ AnimalInterface auf:

Modul Typen
  modul AnimalInterface
    include Typen::BaseInterface

    feld :name, String, null: false
    feld :alter, Ganzzahl, null: false
  end
end

Anhand dieser Schnittstelle können wir nun die einzelnen Tierarten definieren:

Modul Typen
  Klasse ElefantTyp < Types::BaseObject
    implementiert Types::AnimalInterface

    Feld :trunk_length, Float, null: false
  end
end

Modul Typen
  Klasse CatType < Types::BaseObject
   implementiert Types::AnimalInterface

   Feld :hair_type, String, null: false
  end
end

Modul Typen
  Klasse DogType < Types::BaseObject
    implementiert Types::AnimalInterface

     Feld :Rasse, String, null: false
  end
end

Das war's! Fertig! Eine letzte Frage: Wie können wir das, was wir auf der Kundenseite gemacht haben, nutzen?

Erstellung der Abfrage

{
 allZoos : {
   zoo: {
      Name:
      Stadt:
      Tiere: {
        __typenname

        ... on ElefantTyp {
          Name
          Alter
          rüsselLänge
        }

         ... on CatType {
          Name
          Alter
          hairType
         }
         ... on DogType {
          Name
          Alter
          Rasse
         }
       }
     }
   }
 }

Wir können hier ein zusätzliches __typename-Feld verwenden, das den genauen Typ eines bestimmten Elements zurückgibt (z. B. CatType). Wie sieht eine Beispielantwort aus?

{
  "allZoos": [

   {
      "name": "Natura Artis Magistra",
      "Stadt": "Amsterdam",
      "Tiere": [
        {
          "__typenname": "ElefantTyp"
          "name": "Franco",
          "Alter": 28,
          "trunkLength": 9.27
         },
         {
         "__typenname": "DogType"
         "name": "Jack",
         "Alter": 9,
         "Rasse": "Jack Russell Terrier"
        },
      ]
    }
  ]
} 

Analyse

Ein Nachteil dieses Ansatzes ist offensichtlich. In der Abfrage müssen wir bei jedem Typ den Namen und das Alter eingeben, obwohl wir wissen, dass alle Tiere diese Felder haben. Dies ist nicht weiter störend, wenn die Sammlung völlig unterschiedliche Objekte enthält. In diesem Fall haben die Tiere jedoch fast alle Felder gemeinsam. Kann das irgendwie verbessert werden?

Ja, natürlich! Wir nehmen die erste Änderung in der Datei vor app/graphql/types/zoo_type.rb:

Modul Typen
  Klasse ZooType < Typen::BaseObject
    Feld :name, String, null: false
    feld :stadt, String, null: false
    feld :tiere, [Types::AnimalInterface], null: false
  end
end

Wir brauchen nicht mehr die Vereinigung, die wir vorher definiert haben. Wir ändern Typen::AnimalType zu Typen::AnimalInterface.

Der nächste Schritt besteht darin, eine Funktion hinzuzufügen, die einen Typ von Typen :: AnimalInterface und fügen Sie auch eine Liste von orphan_types hinzu, also Typen, die nie direkt verwendet werden:

Modul Typen
  modul AnimalInterface
    include Typen::BaseInterface

   feld :name, String, null: false
   feld :alter, Ganzzahl, null: false

   definition_methods do
      def resolve_type(obj, ctx)
        if obj.is_a?(Elefant)
          ElefantTyp
        elsif obj.is_a?(Katze)
          CatType
        elsif obj.is_a?(Hund)
          HundTyp
        end
      end
    end
    orphan_types Typen::ElefantTyp, Typen::KatzeTyp, Typen::HundTyp
  end
end

Dank dieses einfachen Verfahrens hat die Abfrage eine weniger komplexe Form:

{
  allZoos : {
   zoo: {
      Name:
      Stadt:
      Tiere: {
        __typenname
        Name
        Alter

       ... on ElefantTyp {
          RüsselLänge

       }
       ... on CatType {
          HaarTyp

       }
       ... on DogType {
          Rasse

        }
      }
    }
  }
}

Zusammenfassung

GraphQL ist eine wirklich großartige Lösung. Wenn Sie sie noch nicht kennen, probieren Sie sie aus. Glauben Sie mir, das ist es wert. Sie leistet großartige Arbeit bei der Lösung von Problemen, die z. B. bei REST-APIs auftreten. Wie ich oben gezeigt habe, Polymorphismus ist kein wirkliches Hindernis für sie. Ich habe zwei Methoden vorgestellt, um das Problem zu lösen.
Zur Erinnerung:

Mehr lesen

GraphQL Ruby. Wie sieht es mit der Leistung aus?

Eisenbahnen und andere Transportmittel

Rails-Entwicklung mit TMUX, Vim, Fzf + Ripgrep

de_DEGerman