Kotlin-Bootcamp für Programmierer 4: Objektorientierte Programmierung

Dieses Codelab ist Teil des Kotlin-Bootcamps für Programmierer. Sie können diesen Kurs am besten nutzen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Je nach Ihrem Wissen können Sie einige Abschnitte möglicherweise überfliegen. Dieser Kurs richtet sich an Programmierer, die eine objektorientierte Sprache kennen und Kotlin lernen möchten.

Einführung

In diesem Codelab erstellen Sie ein Kotlin-Programm und lernen Klassen und Objekte in Kotlin kennen. Vieles davon ist Ihnen vielleicht schon aus anderen objektorientierten Sprachen bekannt. Kotlin weist jedoch einige wichtige Unterschiede auf, die die Menge an Code, die Sie schreiben müssen, reduzieren. Außerdem erfahren Sie mehr über abstrakte Klassen und die Delegierung von Schnittstellen.

In diesem Kurs wird keine einzelne Beispiel-App entwickelt. Stattdessen sollen die Lektionen Ihr Wissen erweitern, sind aber weitgehend unabhängig voneinander, sodass Sie Abschnitte, mit denen Sie vertraut sind, überspringen können. Um die Beispiele zu veranschaulichen, wird in vielen ein Aquarium verwendet. Wenn Sie die ganze Geschichte des Aquariums sehen möchten, können Sie sich den Udacity-Kurs Kotlin Bootcamp for Programmers ansehen.

Was Sie bereits wissen sollten

  • Die Grundlagen von Kotlin, einschließlich Typen, Operatoren und Schleifen
  • Kotlin-Funktionssyntax
  • Grundlagen der objektorientierten Programmierung
  • Grundlagen einer IDE wie IntelliJ IDEA oder Android Studio

Lerninhalte

  • Klassen erstellen und auf Attribute in Kotlin zugreifen
  • Klassenkonstruktoren in Kotlin erstellen und verwenden
  • Unterklasse erstellen und Funktionsweise der Übernahme
  • Abstrakte Klassen, Schnittstellen und Schnittstellendelegierung
  • Datenklassen erstellen und verwenden
  • Singletons, Enums und versiegelte Klassen verwenden

Aufgaben

  • Klasse mit Attributen erstellen
  • Konstruktor für eine Klasse erstellen
  • Unterklasse erstellen
  • Beispiele für abstrakte Klassen und Schnittstellen ansehen
  • Einfache Datenklasse erstellen
  • Informationen zu Singletons, Enums und versiegelten Klassen

Die folgenden Programmierbegriffe sollten Ihnen bereits bekannt sein:

  • Klassen sind Baupläne für Objekte. Eine Aquarium-Klasse ist beispielsweise der Bauplan für die Erstellung eines Aquariums.
  • Objekte sind Instanzen von Klassen. Ein Aquarium-Objekt ist ein tatsächliches Aquarium.
  • Eigenschaften sind Merkmale von Klassen, z. B. Länge, Breite und Höhe eines Aquarium.
  • Methoden, auch Memberfunktionen genannt, sind die Funktionalität der Klasse. Methoden sind die Aktionen, die Sie mit dem Objekt ausführen können. Sie können beispielsweise ein fillWithWater()-Objekt Aquarium.
  • Eine Schnittstelle ist eine Spezifikation, die eine Klasse implementieren kann. Die Reinigung ist beispielsweise für andere Objekte als Aquarien üblich und erfolgt im Allgemeinen auf ähnliche Weise für verschiedene Objekte. Sie könnten also eine Schnittstelle mit dem Namen Clean haben, die eine clean()-Methode definiert. Die Klasse Aquarium könnte die Schnittstelle Clean implementieren, um das Aquarium mit einem weichen Schwamm zu reinigen.
  • Pakete sind eine Möglichkeit, zusammengehörigen Code zu gruppieren, um ihn zu organisieren oder eine Codebibliothek zu erstellen. Nachdem ein Paket erstellt wurde, können Sie seinen Inhalt in eine andere Datei importieren und den Code und die Klassen darin wiederverwenden.

In dieser Aufgabe erstellen Sie ein neues Paket und eine Klasse mit einigen Attributen und einer Methode.

Schritt 1: Paket erstellen

Mit Paketen können Sie Ihren Code organisieren.

  1. Klicken Sie im Bereich Project (Projekt) unter dem Projekt Hello Kotlin (Hallo Kotlin) mit der rechten Maustaste auf den Ordner src.
  2. Wählen Sie Neu > Paket aus und nennen Sie es example.myapp.

Schritt 2: Klasse mit Attributen erstellen

Klassen werden mit dem Keyword class definiert und Klassennamen beginnen üblicherweise mit einem Großbuchstaben.

  1. Klicken Sie mit der rechten Maustaste auf das Paket example.myapp.
  2. Wählen Sie New > Kotlin File / Class (Neu > Kotlin-Datei / -Klasse) aus.
  3. Wählen Sie unter Art die Option Kurs aus und geben Sie einen Namen für den Kurs ein Aquarium. IntelliJ IDEA fügt den Paketnamen in die Datei ein und erstellt eine leere Aquarium-Klasse für Sie.
  4. Definieren und initialisieren Sie in der Klasse Aquarium die var-Attribute für Breite, Höhe und Länge (in Zentimetern). Initialisieren Sie die Attribute mit Standardwerten.
package example.myapp

class Aquarium {
    var width: Int = 20
    var height: Int = 40
    var length: Int = 100
}

Im Hintergrund erstellt Kotlin automatisch Getter- und Setter-Methoden für die Attribute, die Sie in der Klasse Aquarium definiert haben. Sie können also direkt auf die Attribute zugreifen, z. B. myAquarium.length.

Schritt 3: main()-Funktion erstellen

Erstellen Sie eine neue Datei mit dem Namen main.kt, in der die Funktion main() enthalten ist.

  1. Klicken Sie im Bereich Project (Projekt) auf der linken Seite mit der rechten Maustaste auf das Paket example.myapp.
  2. Wählen Sie New > Kotlin File / Class (Neu > Kotlin-Datei / -Klasse) aus.
  3. Lassen Sie im Drop-down-Menü Art die Auswahl Datei bei und geben Sie der Datei den Namen main.kt. IntelliJ IDEA enthält den Paketnamen, aber keine Klassendefinition für eine Datei.
  4. Definieren Sie eine buildAquarium()-Funktion und erstellen Sie darin eine Instanz von Aquarium. Um eine Instanz zu erstellen, verweisen Sie auf die Klasse, als wäre sie eine Funktion: Aquarium(). Dadurch wird der Konstruktor der Klasse aufgerufen und eine Instanz der Aquarium-Klasse erstellt, ähnlich wie bei der Verwendung von new in anderen Sprachen.
  5. Definieren Sie eine main()-Funktion und rufen Sie buildAquarium() auf.
package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium()
}

fun main() {
    buildAquarium()
}

Schritt 4: Methode hinzufügen

  1. Fügen Sie in der Klasse Aquarium eine Methode zum Ausgeben der Dimensionsattribute des Aquariums hinzu.
    fun printSize() {
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }
  1. Rufen Sie in main.kt in buildAquarium() die Methode printSize() für myAquarium auf.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
}
  1. Führen Sie Ihr Programm aus, indem Sie auf das grüne Dreieck neben der Funktion main() klicken. Sehen Sie sich das Ergebnis an.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
  1. Fügen Sie in buildAquarium() Code hinzu, um die Höhe auf 60 festzulegen und die geänderten Dimensionseigenschaften auszugeben.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
    myAquarium.height = 60
    myAquarium.printSize()
}
  1. Führen Sie das Programm aus und beobachten Sie die Ausgabe.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 100 cm Height: 60 cm 

In dieser Aufgabe erstellen Sie einen Konstruktor für die Klasse und arbeiten weiter mit Properties.

Schritt 1: Konstruktor erstellen

In diesem Schritt fügen Sie der Klasse Aquarium, die Sie in der ersten Aufgabe erstellt haben, einen Konstruktor hinzu. Im vorherigen Beispiel wird jede Instanz von Aquarium mit denselben Dimensionen erstellt. Sie können die Abmessungen nach dem Erstellen durch Festlegen der Eigenschaften ändern. Es ist jedoch einfacher, die richtige Größe von Anfang an festzulegen.

In einigen Programmiersprachen wird der Konstruktor definiert, indem eine Methode in der Klasse erstellt wird, die denselben Namen wie die Klasse hat. In Kotlin definieren Sie den Konstruktor direkt in der Klassendeklaration selbst und geben die Parameter in Klammern an, als wäre die Klasse eine Methode. Wie bei Funktionen in Kotlin können diese Parameter Standardwerte enthalten.

  1. Ändern Sie in der zuvor erstellten Klasse Aquarium die Klassendefinition so, dass sie drei Konstruktorparameter mit Standardwerten für length, width und height enthält, und weisen Sie sie den entsprechenden Eigenschaften zu.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}
  1. In Kotlin können Sie die Eigenschaften kompakter direkt mit dem Konstruktor definieren, indem Sie var oder val verwenden. Außerdem werden die Getter und Setter automatisch erstellt. Anschließend können Sie die Attributdefinitionen im Text der Klasse entfernen.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
  1. Wenn Sie ein Aquarium-Objekt mit diesem Konstruktor erstellen, können Sie entweder keine Argumente angeben und die Standardwerte verwenden, nur einige Argumente angeben oder alle Argumente angeben und ein Aquarium-Objekt mit einer vollständig benutzerdefinierten Größe erstellen. Probieren Sie in der Funktion buildAquarium() verschiedene Möglichkeiten aus, ein Aquarium-Objekt mit benannten Parametern zu erstellen.
fun buildAquarium() {
    val aquarium1 = Aquarium()
    aquarium1.printSize()
    // default height and length
    val aquarium2 = Aquarium(width = 25)
    aquarium2.printSize()
    // default width
    val aquarium3 = Aquarium(height = 35, length = 110)
    aquarium3.printSize()
    // everything custom
    val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
    aquarium4.printSize()
}
  1. Führen Sie das Programm aus und sehen Sie sich die Ausgabe an.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 25 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 110 cm Height: 35 cm 
Width: 25 cm Length: 110 cm Height: 35 cm 

Sie mussten den Konstruktor nicht überladen und für jeden dieser Fälle (und einige weitere für die anderen Kombinationen) eine andere Version schreiben. Kotlin erstellt das, was benötigt wird, aus den Standardwerten und benannten Parametern.

Schritt 2: Initialisierungsblöcke hinzufügen

In den Beispielkonstruktoren oben werden nur Eigenschaften deklariert und ihnen wird der Wert eines Ausdrucks zugewiesen. Wenn Ihr Konstruktor mehr Initialisierungscode benötigt, kann er in einem oder mehreren init-Blöcken platziert werden. In diesem Schritt fügen Sie der Klasse Aquarium einige init-Blöcke hinzu.

  1. Fügen Sie in der Klasse Aquarium einen init-Block hinzu, um auszugeben, dass das Objekt initialisiert wird, und einen zweiten Block, um das Volumen in Litern auszugeben.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}
  1. Führen Sie das Programm aus und sehen Sie sich die Ausgabe an.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm 
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm 

Die init-Blöcke werden in der Reihenfolge ausgeführt, in der sie in der Klassendefinition aufgeführt sind. Alle werden ausgeführt, wenn der Konstruktor aufgerufen wird.

Schritt 3: Sekundäre Konstruktoren

In diesem Schritt erfahren Sie mehr über sekundäre Konstruktoren und fügen Ihrer Klasse einen hinzu. Zusätzlich zu einem primären Konstruktor, der einen oder mehrere init-Blöcke haben kann, kann eine Kotlin-Klasse auch einen oder mehrere sekundäre Konstruktoren haben, um die Überladung von Konstruktoren zu ermöglichen, d. h. Konstruktoren mit unterschiedlichen Argumenten.

  1. Fügen Sie der Klasse Aquarium einen sekundären Konstruktor hinzu, der mit dem Schlüsselwort constructor eine Anzahl von Fischen als Argument akzeptiert. Erstelle eine val-Tank-Property für das berechnete Volumen des Aquariums in Litern basierend auf der Anzahl der Fische. Rechnen Sie mit 2 Litern (2.000 cm³) Wasser pro Fisch und etwas zusätzlichem Platz, damit das Wasser nicht verschüttet wird.
constructor(numberOfFish: Int) : this() {
    // 2,000 cm^3 per fish + extra room so water doesn't spill
    val tank = numberOfFish * 2000 * 1.1
}
  1. Behalten Sie im sekundären Konstruktor die Länge und Breite bei, die im primären Konstruktor festgelegt wurden, und berechnen Sie die Höhe, die erforderlich ist, um das angegebene Volumen zu erreichen.
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. Fügen Sie in der Funktion buildAquarium() einen Aufruf hinzu, um mit dem neuen sekundären Konstruktor ein Aquarium zu erstellen. Geben Sie die Größe und das Volumen an.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
  1. Führen Sie das Programm aus und beobachten Sie die Ausgabe.
⇒ aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Beachten Sie, dass die Lautstärke zweimal ausgegeben wird: einmal vom init-Block im primären Konstruktor, bevor der sekundäre Konstruktor ausgeführt wird, und einmal vom Code in buildAquarium().

Sie hätten das Keyword constructor auch in den primären Konstruktor aufnehmen können, aber in den meisten Fällen ist das nicht erforderlich.

Schritt 4: Neuen Property-Getter hinzufügen

In diesem Schritt fügen Sie einen expliziten Property-Getter hinzu. In Kotlin werden Getter und Setter automatisch definiert, wenn Sie Attribute definieren. Manchmal muss der Wert für ein Attribut jedoch angepasst oder berechnet werden. Im Beispiel oben haben Sie das Volumen von Aquarium ausgegeben. Sie können das Volume als Attribut verfügbar machen, indem Sie eine Variable und einen Getter dafür definieren. Da volume berechnet werden muss, muss der Getter den berechneten Wert zurückgeben. Das können Sie mit einer einzeiligen Funktion tun.

  1. Definieren Sie in der Klasse Aquarium eine Int-Property mit dem Namen volume und eine get()-Methode, die in der nächsten Zeile das Volumen berechnet.
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. Entfernen Sie den init-Block, mit dem das Volume ausgegeben wird.
  2. Entfernen Sie den Code in buildAquarium(), der das Volume ausgibt.
  3. Fügen Sie in der Methode printSize() eine Zeile hinzu, um das Volume auszugeben.
fun printSize() {
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm "
    )
    // 1 l = 1000 cm^3
    println("Volume: $volume l")
}
  1. Führen Sie das Programm aus und beobachten Sie die Ausgabe.
⇒ aquarium initializing
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Die Dimensionen und das Volumen sind dieselben wie zuvor, aber das Volumen wird nur einmal ausgegeben, nachdem das Objekt sowohl vom primären als auch vom sekundären Konstruktor vollständig initialisiert wurde.

Schritt 5: Property-Setter hinzufügen

In diesem Schritt erstellen Sie einen neuen Property-Setter für das Volume.

  1. Ändern Sie in der Klasse Aquarium volume in var, damit sie mehr als einmal festgelegt werden kann.
  2. Fügen Sie einen Setter für die volume-Eigenschaft hinzu, indem Sie unter dem Getter eine set()-Methode hinzufügen, die die Höhe basierend auf der angegebenen Wassermenge neu berechnet. Der Name des Setter-Parameters lautet üblicherweise value. Sie können ihn aber auch ändern.
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }
  1. Fügen Sie in buildAquarium() Code hinzu, um das Volumen des Aquariums auf 70 Liter festzulegen. Drucken Sie das Bild in der neuen Größe.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
  1. Führen Sie das Programm noch einmal aus und beobachten Sie die geänderte Höhe und Lautstärke.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm 
Volume: 70 l

Bisher gab es im Code keine Sichtbarkeitsmodifikatoren wie public oder private. Das liegt daran, dass in Kotlin standardmäßig alles öffentlich ist. Das bedeutet, dass auf alles überall zugegriffen werden kann, einschließlich Klassen, Methoden, Eigenschaften und Membervariablen.

In Kotlin können Klassen, Objekte, Schnittstellen, Konstruktoren, Funktionen, Attribute und ihre Setter Sichtbarkeitsmodifizierer haben:

  • public bedeutet, dass die Variable außerhalb der Klasse sichtbar ist. Standardmäßig ist alles öffentlich, einschließlich Variablen und Methoden der Klasse.
  • internal bedeutet, dass es nur in diesem Modul sichtbar ist. Ein Modul ist eine Gruppe von Kotlin-Dateien, die zusammen kompiliert werden, z. B. eine Bibliothek oder Anwendung.
  • private bedeutet, dass sie nur in dieser Klasse (oder Quelldatei, wenn Sie mit Funktionen arbeiten) sichtbar ist.
  • protected ist dasselbe wie private, ist aber auch für alle abgeleiteten Klassen sichtbar.

Weitere Informationen finden Sie in der Kotlin-Dokumentation unter Sichtbarkeitsmodifizierer.

Member-Variablen

Attribute innerhalb einer Klasse oder Mitgliedsvariablen sind standardmäßig public. Wenn Sie sie mit var definieren, sind sie veränderbar, d. h. lesbar und schreibbar. Wenn Sie sie mit val definieren, sind sie nach der Initialisierung schreibgeschützt.

Wenn Sie eine Eigenschaft benötigen, die Ihr Code lesen oder schreiben kann, auf die aber nur von außen zugegriffen werden kann, können Sie die Eigenschaft und ihren Getter als öffentlich deklarieren und den Setter als privat, wie unten dargestellt.

var volume: Int
    get() = width * height * length / 1000
    private set(value) {
        height = (value * 1000) / (width * length)
    }

In dieser Aufgabe erfahren Sie, wie Unterklassen und Vererbung in Kotlin funktionieren. Sie ähneln den in anderen Sprachen verfügbaren Funktionen, es gibt aber einige Unterschiede.

In Kotlin können Klassen standardmäßig nicht als abgeleitete Klasse verwendet werden. Ebenso können Eigenschaften und Mitgliedsvariablen nicht von Unterklassen überschrieben werden (sie können jedoch aufgerufen werden).

Sie müssen eine Klasse als open markieren, damit sie abgeleitet werden kann. Ebenso müssen Sie Attribute und Membervariablen als open markieren, damit sie in der abgeleiteten Klasse überschrieben werden können. Das Schlüsselwort open ist erforderlich, um zu verhindern, dass Implementierungsdetails versehentlich als Teil der Klassenschnittstelle offengelegt werden.

Schritt 1: Aquarium-Klasse öffnen

In diesem Schritt machen Sie die Klasse Aquarium zu open, damit Sie sie im nächsten Schritt überschreiben können.

  1. Kennzeichnen Sie die Klasse Aquarium und alle zugehörigen Attribute mit dem Schlüsselwort open.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }
  1. Fügen Sie ein offenes shape-Attribut mit dem Wert "rectangle" hinzu.
   open val shape = "rectangle"
  1. Fügen Sie eine offene water-Eigenschaft mit einer Getter-Methode hinzu, die 90% des Volumens von Aquarium zurückgibt.
    open var water: Double = 0.0
        get() = volume * 0.9
  1. Fügen Sie der Methode printSize() Code hinzu, um die Form und die Wassermenge als Prozentsatz des Volumens auszugeben.
fun printSize() {
    println(shape)
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm ")
    // 1 l = 1000 cm^3
    println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
  1. Ändern Sie in buildAquarium() den Code so, dass ein Aquarium mit width = 25, length = 25 und height = 40 erstellt wird.
fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
}
  1. Führen Sie das Programm aus und sehen Sie sich die neue Ausgabe an.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)

Schritt 2: Unterklasse erstellen

  1. Erstellen Sie eine Unterklasse von Aquarium namens TowerTank, die anstelle eines rechteckigen Tanks einen Tank mit abgerundeten Zylindern implementiert. Sie können TowerTank unter Aquarium hinzufügen, da Sie in derselben Datei wie die Klasse Aquarium eine weitere Klasse hinzufügen können.
  2. Überschreiben Sie in TowerTank das Attribut height, das im Konstruktor definiert ist. Verwenden Sie das Schlüsselwort override in der abgeleiteten Klasse, um eine Property zu überschreiben.
  1. Lassen Sie den Konstruktor für TowerTank ein diameter annehmen. Verwenden Sie diameter sowohl für length als auch für width, wenn Sie den Konstruktor in der Aquarium-Superklasse aufrufen.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
  1. Überschreiben Sie die Volume-Eigenschaft, um einen Zylinder zu berechnen. Die Formel für einen Zylinder lautet: Pi mal Radius zum Quadrat mal Höhe. Sie müssen die Konstante PI aus java.lang.Math importieren.
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }
  1. Überschreiben Sie in TowerTank das Attribut water mit 80% der Lautstärke.
override var water = volume * 0.8
  1. Überschreibt die shape mit "cylinder".
override val shape = "cylinder"
  1. Ihre endgültige TowerTank-Klasse sollte in etwa so aussehen:

Aquarium.kt:

package example.myapp

import java.lang.Math.PI

... // existing Aquarium class

class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }

    override var water = volume * 0.8
    override val shape = "cylinder"
}
  1. Erstelle in buildAquarium() ein TowerTank mit einem Durchmesser von 25 cm und einer Höhe von 45 cm. Geben Sie die Größe an.

main.kt:

package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium(width = 25, length = 25, height = 40)
    myAquarium.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
  1. Führen Sie das Programm aus und beobachten Sie die Ausgabe.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
aquarium initializing
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

Manchmal möchten Sie gemeinsames Verhalten oder gemeinsame Eigenschaften definieren, die von einigen zugehörigen Klassen gemeinsam genutzt werden sollen. Dafür gibt es in Kotlin zwei Möglichkeiten: Schnittstellen und abstrakte Klassen. In dieser Aufgabe erstellen Sie eine abstrakte Klasse AquariumFish für Eigenschaften, die allen Fischen gemeinsam sind. Sie erstellen eine Schnittstelle namens FishAction, um das Verhalten zu definieren, das allen Fischen gemeinsam ist.

  • Weder eine abstrakte Klasse noch eine Schnittstelle können eigenständig instanziiert werden. Das bedeutet, dass Sie keine Objekte dieser Typen direkt erstellen können.
  • Abstrakte Klassen haben Konstruktoren.
  • Schnittstellen dürfen keine Konstruktorlogik enthalten und keinen Status speichern.

Schritt 1: Abstrakte Klasse erstellen

  1. Erstellen Sie unter example.myapp eine neue Datei mit dem Namen AquariumFish.kt.
  2. Erstellen Sie eine Klasse mit dem Namen AquariumFish und markieren Sie sie mit abstract.
  3. Fügen Sie eine String-Property (color) hinzu und kennzeichnen Sie sie mit abstract.
package example.myapp

abstract class AquariumFish {
    abstract val color: String
}
  1. Erstellen Sie zwei Unterklassen von AquariumFish, Shark und Plecostomus.
  2. Da color abstrakt ist, müssen die abgeleiteten Klassen sie implementieren. Stellen Sie Shark auf Grau und Plecostomus auf Gold ein.
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. Erstellen Sie in main.kt eine makeFish()-Funktion, um Ihre Klassen zu testen. Instanziieren Sie ein Shark und ein Plecostomus und geben Sie dann die Farbe der einzelnen Objekte aus.
  2. Löschen Sie den vorherigen Testcode in main() und fügen Sie einen Aufruf von makeFish() hinzu. Ihr Code sollte in etwa so aussehen:

main.kt:

package example.myapp

fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()

    println("Shark: ${shark.color}")
    println("Plecostomus: ${pleco.color}")
}

fun main () {
    makeFish()
}
  1. Führen Sie das Programm aus und beobachten Sie die Ausgabe.
⇒ Shark: gray 
Plecostomus: gold

Das folgende Diagramm zeigt die Klassen Shark und Plecostomus, die von der abstrakten Klasse AquariumFish abgeleitet werden.

Ein Diagramm, das die abstrakte Klasse „AquariumFish“ und zwei abgeleitete Klassen, „Shark“ und „Plecostumus“, zeigt.

Schritt 2: Schnittstelle erstellen

  1. Erstellen Sie in AquariumFish.kt eine Schnittstelle namens FishAction mit einer Methode eat().
interface FishAction  {
    fun eat()
}
  1. Fügen Sie jeder der Unterklassen FishAction hinzu und implementieren Sie eat() so, dass ausgegeben wird, was der Fisch tut.
class Shark: AquariumFish(), FishAction {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus: AquariumFish(), FishAction {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Lassen Sie in der Funktion makeFish() jeden erstellten Fisch etwas fressen, indem Sie eat() aufrufen.
fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()
    println("Shark: ${shark.color}")
    shark.eat()
    println("Plecostomus: ${pleco.color}")
    pleco.eat()
}
  1. Führen Sie das Programm aus und beobachten Sie die Ausgabe.
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

Das folgende Diagramm zeigt die Klasse Shark und die Klasse Plecostomus, die beide aus der Schnittstelle FishAction bestehen und diese implementieren.

Wann sollten abstrakte Klassen und wann Schnittstellen verwendet werden?

Die obigen Beispiele sind einfach. Wenn Sie jedoch viele zusammenhängende Klassen haben, können abstrakte Klassen und Schnittstellen Ihnen helfen, Ihr Design übersichtlicher, besser organisiert und einfacher zu verwalten.

Wie oben erwähnt, können abstrakte Klassen Konstruktoren haben, Schnittstellen jedoch nicht. Ansonsten sind sie sich sehr ähnlich. Wann sollten Sie die einzelnen Funktionen verwenden?

Wenn Sie Schnittstellen zum Erstellen einer Klasse verwenden, wird die Funktionalität der Klasse durch die darin enthaltenen Klasseninstanzen erweitert. Durch Komposition lässt sich Code in der Regel leichter wiederverwenden und nachvollziehen als durch die Vererbung von einer abstrakten Klasse. Sie können auch mehrere Schnittstellen in einer Klasse verwenden, aber nur von einer abstrakten Klasse ableiten.

Die Komposition führt oft zu einer besseren Kapselung, einer geringeren Kopplung (Abhängigkeit), übersichtlicheren Schnittstellen und einem besser nutzbaren Code. Aus diesen Gründen ist die Verwendung von Komposition mit Schnittstellen das bevorzugte Design. Andererseits ist die Vererbung von einer abstrakten Klasse für einige Probleme oft eine natürliche Lösung. Sie sollten also die Komposition bevorzugen. Wenn die Vererbung jedoch sinnvoll ist, können Sie sie in Kotlin auch verwenden.

  • Verwenden Sie eine Schnittstelle, wenn Sie viele Methoden und ein oder zwei Standardimplementierungen haben, z. B. wie in AquariumAction unten.
interface AquariumAction {
    fun eat()
    fun jump()
    fun clean()
    fun catchFish()
    fun swim()  {
        println("swim")
    }
}
  • Verwenden Sie eine abstrakte Klasse, wenn Sie eine Klasse nicht vollständig implementieren können. Wenn wir beispielsweise zur Klasse AquariumFish zurückkehren, können wir alle AquariumFish FishAction implementieren lassen und eine Standardimplementierung für eat bereitstellen, während color abstrakt bleibt, da es keine Standardfarbe für Fische gibt.
interface FishAction  {
    fun eat()
}

abstract class AquariumFish: FishAction {
   abstract val color: String
   override fun eat() = println("yum")
}

In der vorherigen Aufgabe wurden abstrakte Klassen, Schnittstellen und das Konzept der Komposition eingeführt. Schnittstellendelegierung ist eine fortgeschrittene Technik, bei der die Methoden einer Schnittstelle von einem Hilfs- oder Delegatobjekt implementiert werden, das dann von einer Klasse verwendet wird. Diese Technik kann nützlich sein, wenn Sie eine Schnittstelle in einer Reihe von unabhängigen Klassen verwenden: Sie fügen die benötigte Schnittstellenfunktionalität einer separaten Hilfsklasse hinzu und jede der Klassen verwendet eine Instanz der Hilfsklasse, um die Funktionalität zu implementieren.

In dieser Aufgabe verwenden Sie die Schnittstellendelegierung, um einer Klasse Funktionen hinzuzufügen.

Schritt 1: Neue Schnittstelle erstellen

  1. Entfernen Sie in AquariumFish.kt die Klasse AquariumFish. Anstatt von der Klasse AquariumFish zu erben, implementieren Plecostomus und Shark Schnittstellen für die Fischaktion und ihre Farbe.
  2. Erstellen Sie eine neue Schnittstelle, FishColor, die die Farbe als String definiert.
interface FishColor {
    val color: String
}
  1. Ändern Sie Plecostomus, um zwei Schnittstellen, FishAction, und eine FishColor zu implementieren. Sie müssen color aus FishColor und eat() aus FishAction überschreiben.
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Ändern Sie Ihre Shark-Klasse so, dass sie auch die beiden Schnittstellen FishAction und FishColor implementiert, anstatt von AquariumFish zu erben.
class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. Der fertige Code sollte in etwa so aussehen:
package example.myapp

interface FishAction {
    fun eat()
}

interface FishColor {
    val color: String
}

class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}

class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

Schritt 2: Singleton-Klasse erstellen

Als Nächstes implementieren Sie die Einrichtung für den Delegierungsteil, indem Sie eine Hilfsklasse erstellen, die FishColor implementiert. Sie erstellen eine einfache Klasse namens GoldColor, die FishColor implementiert. Sie gibt lediglich an, dass die Farbe Gold ist.

Es ist nicht sinnvoll, mehrere Instanzen von GoldColor zu erstellen, da sie alle genau dasselbe tun würden. In Kotlin können Sie eine Klasse deklarieren, von der nur eine Instanz erstellt werden kann. Verwenden Sie dazu das Schlüsselwort object anstelle von class. Kotlin erstellt diese eine Instanz und auf diese Instanz wird über den Klassennamen verwiesen. Alle anderen Objekte können dann einfach diese eine Instanz verwenden. Es gibt keine Möglichkeit, andere Instanzen dieser Klasse zu erstellen. Wenn Sie mit dem Singleton-Muster vertraut sind, wissen Sie, wie Sie Singletons in Kotlin implementieren.

  1. Erstellen Sie in AquariumFish.kt ein Objekt für GoldColor. Überschreiben Sie die Farbe.
object GoldColor : FishColor {
   override val color = "gold"
}

Schritt 3: Schnittstellendelegierung für FishColor hinzufügen

Jetzt können Sie die Schnittstellendelegierung verwenden.

  1. Entfernen Sie in AquariumFish.kt die Überschreibung von color aus Plecostomus.
  2. Ändern Sie die Plecostomus-Klasse, damit ihre Farbe von GoldColor übernommen wird. Fügen Sie dazu der Klassendeklaration by GoldColor hinzu, um die Delegation zu erstellen. Das bedeutet, dass Sie anstelle von FishColor die von GoldColor bereitgestellte Implementierung verwenden sollten. Jedes Mal, wenn auf color zugegriffen wird, wird der Zugriff an GoldColor delegiert.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

In der aktuellen Version der Klasse sind alle Antennenwelse goldfarben. Diese Fische gibt es aber in vielen Farben. Sie können dieses Problem beheben, indem Sie einen Konstruktorparameter für die Farbe mit GoldColor als Standardfarbe für Plecostomus hinzufügen.

  1. Ändern Sie die Klasse Plecostomus so, dass sie ein übergebenes fishColor mit ihrem Konstruktor akzeptiert, und legen Sie den Standardwert auf GoldColor fest. Ändern Sie die Delegation von by GoldColor in by fishColor.
class Plecostomus(fishColor: FishColor = GoldColor):  FishAction,
       FishColor by fishColor {
   override fun eat() {
       println("eat algae")
   }
}

Schritt 4: Schnittstellendelegierung für FishAction hinzufügen

Auf dieselbe Weise können Sie die Schnittstellendelegierung für FishAction verwenden.

  1. Erstelle in AquariumFish.kt eine PrintingFishAction-Klasse, die FishAction implementiert und einen String und einen food akzeptiert. Gib dann aus, was der Fisch frisst.
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. Entfernen Sie in der Klasse Plecostomus die Überschreibungsfunktion eat(), da Sie sie durch eine Delegation ersetzen.
  2. Delegieren Sie in der Deklaration von Plecostomus den Parameter FishAction an PrintingFishAction und übergeben Sie "eat algae".
  3. Da alles delegiert wird, gibt es keinen Code im Hauptteil der Plecostomus-Klasse. Entfernen Sie daher {}, da alle Überschreibungen durch die Schnittstellendelegierung verarbeitet werden.
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

Im folgenden Diagramm werden die Klassen Shark und Plecostomus dargestellt, die beide aus den Schnittstellen PrintingFishAction und FishColor bestehen, aber die Implementierung an diese delegieren.

Die Schnittstellendelegierung ist leistungsstark und Sie sollten sie immer dann in Betracht ziehen, wenn Sie in einer anderen Sprache eine abstrakte Klasse verwenden würden. So können Sie Verhalten über Komposition einfügen, anstatt viele Unterklassen zu benötigen, die jeweils auf unterschiedliche Weise spezialisiert sind.

Eine Datenklasse ähnelt in einigen anderen Sprachen einer struct – sie dient hauptsächlich zum Speichern von Daten –, aber ein Datenklassenobjekt ist trotzdem ein Objekt. Kotlin-Datenklassenobjekte bieten einige zusätzliche Vorteile, z. B. Dienstprogramme zum Drucken und Kopieren. In dieser Aufgabe erstellen Sie eine einfache Datenklasse und erfahren, wie Kotlin Datenklassen unterstützt.

Schritt 1: Datenklasse erstellen

  1. Fügen Sie unter dem Paket example.myapp ein neues Paket decor hinzu, um den neuen Code zu speichern. Klicken Sie im Bereich Project (Projekt) mit der rechten Maustaste auf example.myapp und wählen Sie File > New > Package (Datei > Neu > Paket) aus.
  2. Erstellen Sie im Paket eine neue Klasse mit dem Namen Decoration.
package example.myapp.decor

class Decoration {
}
  1. Wenn Sie Decoration zu einer Datenklasse machen möchten, stellen Sie der Klassendeklaration das Schlüsselwort data voran.
  2. Fügen Sie eine String-Property mit dem Namen rocks hinzu, um der Klasse einige Daten zu geben.
data class Decoration(val rocks: String) {
}
  1. Fügen Sie in der Datei außerhalb der Klasse eine makeDecorations()-Funktion hinzu, um eine Instanz von Decoration mit "granite" zu erstellen und auszugeben.
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. Fügen Sie eine main()-Funktion hinzu, um makeDecorations() aufzurufen, und führen Sie das Programm aus. Beachten Sie die sinnvolle Ausgabe, die erstellt wird, da es sich um eine Datenklasse handelt.
⇒ Decoration(rocks=granite)
  1. Instanziieren Sie in makeDecorations() zwei weitere Decoration-Objekte, die beide „slate“ sind, und geben Sie sie aus.
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)
}
  1. Fügen Sie in makeDecorations() eine Print-Anweisung hinzu, die das Ergebnis des Vergleichs von decoration1 mit decoration2 ausgibt, und eine zweite, die decoration3 mit decoration2 vergleicht. Verwenden Sie die von Datenklassen bereitgestellte Methode equals().
    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
  1. Führen Sie den Code aus.
⇒ Decoration(rocks=granite)
Decoration(rocks=slate)
Decoration(rocks=slate)
false
true

Schritt 2: Destrukturierung verwenden

Um auf die Eigenschaften eines Datenobjekts zuzugreifen und sie Variablen zuzuweisen, können Sie sie einzeln zuweisen, wie hier gezeigt.

val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver

Stattdessen können Sie für jede Property eine Variable erstellen und das Datenobjekt der Gruppe von Variablen zuweisen. In Kotlin wird der Attributwert in jeder Variablen gespeichert.

val (rock, wood, diver) = decoration

Das wird als Destrukturierung bezeichnet und ist eine nützliche Abkürzung. Die Anzahl der Variablen muss mit der Anzahl der Attribute übereinstimmen und die Variablen werden in der Reihenfolge zugewiesen, in der sie in der Klasse deklariert werden. Hier ist ein vollständiges Beispiel, das Sie in Decoration.kt ausprobieren können.

// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver)
crystal
wood
diver

Wenn Sie eine oder mehrere der Eigenschaften nicht benötigen, können Sie sie überspringen, indem Sie _ anstelle eines Variablennamens verwenden, wie im folgenden Code gezeigt.

    val (rock, _, diver) = d5

In dieser Aufgabe lernen Sie einige der speziellen Klassen in Kotlin kennen, darunter die folgenden:

  • Singleton-Klassen
  • Enums
  • Versiegelte Klassen

Schritt 1: Singleton-Klassen aufrufen

Erinnern Sie sich an das Beispiel von vorhin mit der Klasse GoldColor.

object GoldColor : FishColor {
   override val color = "gold"
}

Da jede Instanz von GoldColor dasselbe tut, wird sie als object anstelle von class deklariert, um sie zu einem Singleton zu machen. Es kann nur eine Instanz davon geben.

Schritt 2: Enum erstellen

Kotlin unterstützt auch Enums, mit denen Sie etwas aufzählen und anhand des Namens darauf verweisen können, ähnlich wie in anderen Sprachen. Deklarieren Sie ein Enum, indem Sie der Deklaration das Keyword enum voranstellen. Für eine einfache Enum-Deklaration ist nur eine Liste von Namen erforderlich. Sie können aber auch ein oder mehrere Felder definieren, die mit jedem Namen verknüpft sind.

  1. Sehen Sie sich in Decoration.kt ein Beispiel für ein Enum an.
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

Enums ähneln Singletons – es kann nur einen und nur einen Wert für jeden Wert in der Enumeration geben. Es kann beispielsweise nur ein Color.RED, ein Color.GREEN und ein Color.BLUE geben. In diesem Beispiel werden die RGB-Werte der Eigenschaft rgb zugewiesen, um die Farbkomponenten darzustellen. Sie können auch den Ordinalwert eines Enums mit der Property ordinal und seinen Namen mit der Property name abrufen.

  1. Sehen wir uns ein weiteres Beispiel für eine Enumeration an.
enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
⇒ EAST
2
90

Schritt 3: Versiegelte Klasse erstellen

Eine versiegelte Klasse ist eine Klasse, die abgeleitet werden kann, aber nur in der Datei, in der sie deklariert ist. Wenn Sie versuchen, die Klasse in einer anderen Datei unterzuordnen, erhalten Sie eine Fehlermeldung.

Da sich die Klassen und Unterklassen in derselben Datei befinden, kennt Kotlin alle Unterklassen statisch. Das heißt, zur Kompilierzeit sieht der Compiler alle Klassen und Unterklassen und weiß, dass dies alle sind. Daher kann er zusätzliche Prüfungen für Sie durchführen.

  1. Sehen Sie sich in AquariumFish.kt ein Beispiel für eine versiegelte Klasse an, die zum Thema Wasser passt.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()

fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

Die Klasse Seal kann nicht in einer anderen Datei abgeleitet werden. Wenn Sie weitere Seal-Typen hinzufügen möchten, müssen Sie sie in derselben Datei hinzufügen. Versiegelte Klassen sind daher eine sichere Möglichkeit, eine feste Anzahl von Typen darzustellen. Versiegelte Klassen eignen sich beispielsweise hervorragend, um Erfolg oder Fehler von einer Netzwerk-API zurückzugeben.

In dieser Lektion haben wir viele Themen behandelt. Vieles davon sollte Ihnen aus anderen objektorientierten Programmiersprachen bekannt sein. Kotlin bietet jedoch einige zusätzliche Funktionen, um Code prägnant und lesbar zu halten.

Klassen und Konstruktoren

  • Definieren Sie eine Klasse in Kotlin mit class.
  • In Kotlin werden automatisch Setter und Getter für Eigenschaften erstellt.
  • Definieren Sie den primären Konstruktor direkt in der Klassendefinition. Beispiel:
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • Wenn für einen primären Konstruktor zusätzlicher Code erforderlich ist, schreiben Sie ihn in einen oder mehrere init-Blöcke.
  • In einer Klasse können mit constructor ein oder mehrere sekundäre Konstruktoren definiert werden. Es ist jedoch üblich, stattdessen eine Fabrikfunktion zu verwenden.

Sichtbarkeitsmodifizierer und Unterklassen

  • Alle Klassen und Funktionen in Kotlin sind standardmäßig public, aber Sie können Modifizierer verwenden, um die Sichtbarkeit in internal, private oder protected zu ändern.
  • Damit eine abgeleitete Klasse erstellt werden kann, muss die übergeordnete Klasse mit open gekennzeichnet sein.
  • Wenn Sie Methoden und Attribute in einer Unterklasse überschreiben möchten, müssen die Methoden und Attribute in der übergeordneten Klasse mit open gekennzeichnet sein.
  • Eine versiegelte Klasse kann nur in derselben Datei untergeordnet werden, in der sie definiert ist. Sie können eine versiegelte Klasse erstellen, indem Sie der Deklaration sealed voranstellen.

Datenklassen, Singletons und Enums

  • Erstellen Sie eine Datenklasse, indem Sie der Deklaration data voranstellen.
  • Destrukturierung ist eine Kurzform für die Zuweisung der Eigenschaften eines data-Objekts zu separaten Variablen.
  • Erstellen Sie eine Singleton-Klasse, indem Sie object anstelle von class verwenden.
  • Definieren Sie eine Aufzählung mit enum class.

Abstrakte Klassen, Schnittstellen und Delegierung

  • Abstrakte Klassen und Schnittstellen sind zwei Möglichkeiten, gemeinsames Verhalten zwischen Klassen zu teilen.
  • Eine abstrakte Klasse definiert Eigenschaften und Verhalten, überlässt die Implementierung jedoch den abgeleiteten Klassen.
  • Ein Interface definiert das Verhalten und kann Standardimplementierungen für einen Teil oder das gesamte Verhalten bereitstellen.
  • Wenn Sie Schnittstellen zum Erstellen einer Klasse verwenden, wird die Funktionalität der Klasse durch die darin enthaltenen Klasseninstanzen erweitert.
  • Bei der Schnittstellendelegierung wird die Komposition verwendet, aber die Implementierung wird auch an die Schnittstellenklassen delegiert.
  • Die Komposition ist eine leistungsstarke Methode, um einer Klasse mithilfe der Schnittstellendelegation Funktionen hinzuzufügen. Im Allgemeinen wird die Komposition bevorzugt, aber die Vererbung von einer abstrakten Klasse ist für einige Probleme besser geeignet.

Kotlin-Dokumentation

Wenn Sie weitere Informationen zu einem Thema in diesem Kurs benötigen oder nicht weiterkommen, ist https://kotlinlang.org der beste Ausgangspunkt.

Kotlin-Tutorials

Die Website https://try.kotlinlang.org enthält umfangreiche Tutorials namens „Kotlin Koans“, einen webbasierten Interpreter und eine vollständige Referenzdokumentation mit Beispielen.

Udacity-Kurs

Den Udacity-Kurs zu diesem Thema finden Sie unter Kotlin Bootcamp for Programmers.

IntelliJ IDEA

Dokumentation für IntelliJ IDEA finden Sie auf der JetBrains-Website.

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:

  • Weisen Sie bei Bedarf Aufgaben zu.
  • Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.

Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.

Beantworten Sie diese Fragen

Frage 1

Klassen haben eine spezielle Methode, die als Vorlage für das Erstellen von Objekten dieser Klasse dient. Wie heißt die Methode?

▢ Ein Builder

▢ Ein Instanziator

▢ Ein Konstruktor

▢ Ein Blueprint

Frage 2

Welche der folgenden Aussagen zu Schnittstellen und abstrakten Klassen ist NICHT richtig?

▢ Abstrakte Klassen können Konstruktoren haben.

▢ Schnittstellen dürfen keine Konstruktoren haben.

▢ Schnittstellen und abstrakte Klassen können direkt instanziiert werden.

▢ Abstrakte Properties müssen von abgeleiteten Klassen der abstrakten Klasse implementiert werden.

Frage 3

Welche der folgenden Optionen ist KEIN Kotlin-Sichtbarkeitsmodifizierer für Attribute, Methoden usw.?

▢ internal

▢ nosubclass

▢ protected

▢ private

Frage 4

Betrachten Sie diese Datenklasse:
data class Fish(val name: String, val species:String, val colors:String)
Welcher der folgenden Codes ist NICHT gültig, um ein Fish-Objekt zu erstellen und zu dekonstruieren?

▢ val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")

▢ val (name2, _, colors2) = Fish("Bitey", "shark", "gray")

▢ val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")

▢ val (name4, species4, colors4) = Fish("Harry", "halibut")

Frage 5

Angenommen, Sie betreiben einen Zoo mit vielen Tieren, die alle versorgt werden müssen. Welche der folgenden Aufgaben gehört NICHT zur Umsetzung der Rolle als „Caretaker“?

▢ Eine interface für verschiedene Arten von Futter, die Tiere fressen.

▢ Eine abstract Caretaker-Klasse, aus der Sie verschiedene Arten von Betreuern erstellen können.

▢ Eine interface für das Geben von sauberem Wasser an ein Tier.

▢ Eine data-Klasse für einen Eintrag in einem Fütterungsplan.

Fahren Sie mit der nächsten Lektion fort: 5.1 Erweiterungen

Eine Übersicht über den Kurs mit Links zu anderen Codelabs finden Sie unter „Kotlin Bootcamp for Programmers: Welcome to the course“.