Klasy abstrakcyjne

Klasy abstrakcyjne to klasy , które pozostawiają niektóre lub wszystkie elementy członkowskie niezaimplementowane, dzięki czemu implementacje mogą być udostępniane przez klasy pochodne.

Składnia

// 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

Uwagi

W programowaniu obiektowym klasa abstrakcyjna jest używana jako klasa bazowa hierarchii i reprezentuje typowe funkcje zróżnicowanego zestawu typów obiektów. Jak wskazuje nazwa "abstrakcyjna", klasy abstrakcyjne często nie odpowiadają bezpośrednio konkretnym podmiotom w domenie problemu. Reprezentują one jednak wspólne elementy o różnych konkretnych jednostkach.

Klasy abstrakcyjne muszą mieć AbstractClass atrybut . Mogą mieć zaimplementowane i nieimplementowane elementy członkowskie. Użycie terminu abstrakcyjnego w przypadku zastosowania do klasy jest takie samo jak w innych językach platformy .NET, jednak użycie terminu abstrakcyjnego w przypadku zastosowania do metod (i właściwości) jest nieco inne w języku F# niż jego użycie w innych językach platformy .NET. W języku F#, gdy metoda jest oznaczona abstract słowem kluczowym, oznacza to, że element członkowski ma wpis znany jako gniazdo wysyłania wirtualnego w wewnętrznej tabeli funkcji wirtualnych dla tego typu. Innymi słowy, metoda jest wirtualna, chociaż virtual słowo kluczowe nie jest używane w języku F#. Słowo kluczowe abstract jest używane na metodach wirtualnych niezależnie od tego, czy metoda jest implementowana. Deklaracja wirtualnego miejsca wysyłki jest oddzielona od definicji metody dla tego miejsca wysyłki. W związku z tym odpowiednik języka F# deklaracji i definicji metody wirtualnej w innym języku .NET jest kombinacją zarówno deklaracji metody abstrakcyjnej, jak i oddzielnej definicji z default słowem kluczowym lub override słowem kluczowym. Aby uzyskać więcej informacji i przykładów, zobacz Metody.

Klasa jest uważana za abstrakcyjną tylko wtedy, gdy istnieją metody abstrakcyjne, które są zadeklarowane, ale nie zdefiniowano. W związku z tym klasy, które mają metody abstrakcyjne, nie muszą być abstrakcyjne klasy. Jeśli klasa nie ma niezdefiniowanych metod abstrakcyjnych, nie używaj atrybutu AbstractClass .

W poprzedniej składni modyfikator ułatwień dostępu może mieć publicwartość , private lub internal. Aby uzyskać więcej informacji, zobacz Kontrola dostępu.

Podobnie jak w przypadku innych typów, klasy abstrakcyjne mogą mieć klasę bazową i co najmniej jeden interfejs podstawowy. Każda klasa bazowa lub interfejs są wyświetlane w osobnym wierszu razem ze inherit słowem kluczowym.

Definicja typu klasy abstrakcyjnej może zawierać w pełni zdefiniowane składowe, ale może również zawierać abstrakcyjne elementy członkowskie. Składnia abstrakcyjnych elementów członkowskich jest wyświetlana oddzielnie w poprzedniej składni. W tej składni podpis typu elementu członkowskiego jest listą zawierającą typy parametrów w kolejności i typy zwracane, oddzielone -> tokenami i/lub * tokenami odpowiednio do parametrów curried i tupled. Składnia sygnatur typu abstrakcyjnego elementu członkowskiego jest taka sama jak używana w plikach podpisu i wyświetlana przez funkcję IntelliSense w Edytorze programu Visual Studio Code.

Poniższy kod ilustruje abstrakcyjną klasę Shape, która ma dwie nie abstrakcyjne klasy pochodne, Kwadrat i Okrąg. W przykładzie pokazano, jak używać klas abstrakcyjnych, metod i właściwości. W tym przykładzie klasa Shape abstrakcyjna reprezentuje wspólne elementy konkretnego okręgu i kwadratu jednostek. Typowe cechy wszystkich kształtów (w układzie współrzędnych dwuwymiarowych) są abstrakcje w klasie Kształt: położenie siatki, kąt obrotu oraz właściwości obszaru i obwodu. Można je zastąpić, z wyjątkiem pozycji, których zachowanie poszczególnych kształtów nie może ulec zmianie.

Metodę rotacji można zastąpić, tak jak w klasie Circle, która jest niezmienna ze względu na jego symetrię. W klasie Circle metoda rotacji jest zastępowana przez metodę, która nic nie robi.

// 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

Wyjście:

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

Zobacz też