Classi astratte
Le classi astratte sono classi che lasciano alcuni o tutti i membri non implementati, in modo che le implementazioni possano essere fornite dalle classi derivate.
Sintassi
// 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
Osservazioni:
Nella programmazione orientata agli oggetti, una classe astratta viene usata come classe base di una gerarchia e rappresenta le funzionalità comuni di un set diversificato di tipi di oggetto. Come suggerisce il nome "abstract", le classi astratte spesso non corrispondono direttamente alle entità concrete nel dominio del problema. Tuttavia, rappresentano ciò che molte diverse entità concrete hanno in comune.
Le classi astratte devono avere l'attributo AbstractClass
. Possono avere membri implementati e non implementati. L'uso del termine abstract quando applicato a una classe è identico a quello di altri linguaggi .NET. Tuttavia, l'uso del termine abstract quando applicato ai metodi (e alle proprietà) è leggermente diverso in F# rispetto all'uso in altri linguaggi .NET. In F#, quando un metodo è contrassegnato con la abstract
parola chiave , indica che un membro ha una voce, nota come slot dispatch virtuale, nella tabella interna delle funzioni virtuali per tale tipo. In altre parole, il metodo è virtuale, anche se la virtual
parola chiave non viene usata in F#. La parola chiave abstract
viene usata nei metodi virtuali indipendentemente dal fatto che il metodo sia implementato. La dichiarazione di uno slot dispatch virtuale è separata dalla definizione di un metodo per lo slot dispatch. Di conseguenza, l'equivalente F# di una dichiarazione e una definizione di metodo virtuale in un altro linguaggio .NET è una combinazione di una dichiarazione di metodo astratta e di una definizione separata, con la default
parola chiave o la override
parola chiave . Per altre informazioni ed esempi, vedere Metodi.
Una classe viene considerata astratta solo se sono presenti metodi astratti dichiarati ma non definiti. Pertanto, le classi che dispongono di metodi astratti non sono necessariamente classi astratte. A meno che una classe non abbia metodi astratti non definiti, non usare l'attributo AbstractClass .
Nella sintassi precedente il modificatore di accessibilità può essere public
o private
internal
. Per altre informazioni, vedere Access Control.
Come per altri tipi, le classi astratte possono avere una classe di base e una o più interfacce di base. Ogni classe o interfaccia di base viene visualizzata in una riga separata insieme alla inherit
parola chiave .
La definizione di tipo di una classe astratta può contenere membri completamente definiti, ma può anche contenere membri astratti. La sintassi per i membri astratti viene visualizzata separatamente nella sintassi precedente. In questa sintassi, la firma del tipo di un membro è un elenco che contiene i tipi di parametro in ordine e i tipi restituiti, separati da ->
token e/o *
token in base ai parametri curried e tupled. La sintassi per le firme del tipo di membro astratto è identica a quella usata nei file di firma e visualizzata da IntelliSense nell'editor di Visual Studio Code.
Il codice seguente illustra una classe astratta Shape, che include due classi derivate non astratte, Square e Circle. Nell'esempio viene illustrato come usare classi astratte, metodi e proprietà. Nell'esempio, la classe astratta Shape rappresenta gli elementi comuni delle entità concrete cerchio e quadrato. Le caratteristiche comuni di tutte le forme (in un sistema di coordinate bidimensionale) vengono astratte nella classe Shape: la posizione sulla griglia, un angolo di rotazione e le proprietà dell'area e del perimetro. Questi elementi possono essere sottoposti a override, ad eccezione della posizione, del comportamento di cui le singole forme non possono cambiare.
È possibile eseguire l'override del metodo di rotazione, come nella classe Circle, ovvero rotazione invariante a causa della sua simmetria. Quindi, nella classe Circle, il metodo di rotazione viene sostituito da un metodo che non esegue alcuna operazione.
// 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
Output:
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