Abstrakte Klassen

Abstrakte Klassen sind Klassen, in denen einige oder alle Member nicht implementiert werden, damit Implementierungen von abgeleiteten Klassen bereitgestellt werden können.

Syntax

// Abstract class syntax.
[<AbstractClass>]
type [ accessibility-modifier ] abstract-class-name =
[ inherit base-class-or-interface-name ]
[ abstract-member-declarations-and-member-definitions ]

// Abstract member syntax.
abstract member member-name : type-signature

Bemerkungen

Beim objektorientierten Programmieren wird eine abstrakte Klasse als Basisklasse einer Hierarchie verwendet und stellt die allgemeine Funktionalität verschiedener Objekttypen dar. Wie der Name „abstrakt“ schon sagt, entsprechen abstrakte Klassen oft nicht direkt konkreten Entitäten in der Problemdomäne. Sie stellen jedoch das dar, was viele verschiedene konkrete Entitäten gemeinsam haben.

Abstrakte Klassen müssen über das AbstractClass-Attribut verfügen. Sie können implementierte und nicht implementierte Member haben. Die Verwendung des Begriffs abstract, wenn er auf eine Klasse angewendet wird, ist mit der Verwendung in anderen .NET-Sprachen identisch. Allerdings unterscheidet sich die Verwendung des Begriffs abstract in F# etwas von der Verwendung in anderen .NET-Sprachen, wenn er auf Methoden (und Eigenschaften) angewendet wird. Wenn in F# eine Methode mit dem abstract-Schlüsselwort gekennzeichnet ist, wird hierdurch angegeben, dass ein Member in der internen Tabelle der virtuellen Funktionen für diesen Typ über einen Eintrag verfügt, der als virtueller Dispatchslot bezeichnet wird. Das heißt, die Methode ist virtuell, obwohl das virtual-Schlüsselwort in F# nicht verwendet wird. Das abstract-Schlüsselwort wird für virtuelle Methoden verwendet, unabhängig davon, ob die Methode implementiert ist. Die Deklaration eines virtuellen Dispatchslots ist von der Definition einer Methode für diesen Dispatchslot getrennt. Daher ist das F#-Äquivalent einer virtuellen Methodendeklaration und -definition in einer anderen .NET-Sprache eine Kombination aus einer abstrakten Methodendeklaration und einer separaten Definition mit dem default- oder override-Schlüsselwort. Weitere Informationen und Beispiele finden Sie unter Methoden.

Eine Klasse gilt nur dann als abstrakt, wenn abstrakte Methoden deklariert, aber nicht definiert sind. Daher sind Klassen mit abstrakten Methoden nicht unbedingt abstrakte Klassen. Verwenden Sie das AbstractClass-Attribut nur, wenn eine Klasse über nicht definierte abstrakte Methoden verfügt.

In der vorherigen Syntax kann accessibility-modifierpublic, privateoder internal sein. Weitere Informationen finden Sie unter Zugriffssteuerung.

Wie bei anderen Typen können abstrakte Klassen über eine Basisklasse und mindestens eine Basisschnittstelle verfügen. Jede Basisklasse oder -schnittstelle wird zusammen mit dem inherit-Schlüsselwort in einer separaten Zeile angezeigt.

Die Typdefinition einer abstrakten Klasse kann vollständig definierte Member, aber auch abstrakte Member enthalten. Die Syntax für abstrakte Elemente wird in der vorherigen Syntax separat dargestellt. In dieser Syntax ist die Typsignatur eines Members eine Liste, in der die Parametertypen in der entsprechenden Reihenfolge und die Rückgabetypen getrennt durch ->-Token und/oder *-Token enthalten sind, je nachdem, welche Methode für Curry- oder Tupelparameter erforderlich ist. Die Syntax für abstrakte Membertypsignaturen ist mit der Signatur identisch, die in Signaturdateien verwendet und von IntelliSense im Visual Studio Code-Editor angezeigt wird.

Der folgende Code veranschaulicht eine abstrakte Klasse „Shape“, die über zwei nicht abstrakte abgeleitete Klassen verfügt: „Square“ und „Circle“. Das Beispiel zeigt, wie abstrakte Klassen, Methoden und Eigenschaften verwendet werden. Im Beispiel stellt die abstrakte Klasse „Shape“ die allgemeinen Elemente der konkreten Entitäten „Circle“ und „Square“ dar. Die allgemeinen Merkmale aller Formen (in einem zweidimensionalen Koordinatensystem) werden in die Klasse „Shape“ abstrahiert: die Position auf dem Raster, ein Rotationswinkel sowie die Bereichs und Umkreiseigenschaften. Diese können überschrieben werden. Eine Ausnahme bildet die Position, deren Verhalten einzelne Formen nicht ändern können.

Die Rotationsmethode kann überschrieben werden, wie in der Klasse „Circle“, die aufgrund ihrer Symmetrie rotationsinvariant ist. Daher wird in der Klasse „Circle“ die Rotationsmethode durch eine Methode ersetzt, die keine Aktion ausführt.

// An abstract class that has some methods and properties defined
// and some left abstract.
[<AbstractClass>]
type Shape2D(x0: float, y0: float) =
    let mutable x, y = x0, y0
    let mutable rotAngle = 0.0

    // These properties are not declared abstract. They
    // cannot be overriden.
    member this.CenterX
        with get () = x
        and set xval = x <- xval

    member this.CenterY
        with get () = y
        and set yval = y <- yval

    // These properties are abstract, and no default implementation
    // is provided. Non-abstract derived classes must implement these.
    abstract Area: float with get
    abstract Perimeter: float with get
    abstract Name: string with get

    // This method is not declared abstract. It cannot be
    // overridden.
    member this.Move dx dy =
        x <- x + dx
        y <- y + dy

    // An abstract method that is given a default implementation
    // is equivalent to a virtual method in other .NET languages.
    // Rotate changes the internal angle of rotation of the square.
    // Angle is assumed to be in degrees.
    abstract member Rotate: float -> unit
    default this.Rotate(angle) = rotAngle <- rotAngle + angle

type Square(x, y, sideLengthIn) =
    inherit Shape2D(x, y)
    member this.SideLength = sideLengthIn
    override this.Area = this.SideLength * this.SideLength
    override this.Perimeter = this.SideLength * 4.
    override this.Name = "Square"

type Circle(x, y, radius) =
    inherit Shape2D(x, y)
    let PI = 3.141592654
    member this.Radius = radius
    override this.Area = PI * this.Radius * this.Radius
    override this.Perimeter = 2. * PI * this.Radius
    // Rotating a circle does nothing, so use the wildcard
    // character to discard the unused argument and
    // evaluate to unit.
    override this.Rotate(_) = ()
    override this.Name = "Circle"

let square1 = new Square(0.0, 0.0, 10.0)
let circle1 = new Circle(0.0, 0.0, 5.0)
circle1.CenterX <- 1.0
circle1.CenterY <- -2.0
square1.Move -1.0 2.0
square1.Rotate 45.0
circle1.Rotate 45.0
printfn "Perimeter of square with side length %f is %f, %f" (square1.SideLength) (square1.Area) (square1.Perimeter)
printfn "Circumference of circle with radius %f is %f, %f" (circle1.Radius) (circle1.Area) (circle1.Perimeter)

let shapeList: list<Shape2D> = [ (square1 :> Shape2D); (circle1 :> Shape2D) ]
List.iter (fun (elem: Shape2D) -> printfn "Area of %s: %f" (elem.Name) (elem.Area)) shapeList

Ausgabe:

Perimeter of square with side length 10.000000 is 40.000000
Circumference of circle with radius 5.000000 is 31.415927
Area of Square: 100.000000
Area of Circle: 78.539816

Siehe auch