추상 클래스(F#)
추상 클래스는 파생 클래스를 통해 멤버를 구현할 수 있도록 멤버 중 일부 또는 멤버 전체를 구현하지 않은 채로 두는 클래스입니다.
// 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
설명
개체 지향 프로그래밍에서 추상 클래스는 계층 구조의 기본 클래스로 사용되며 다양한 개체 형식 집합의 공통 기능을 나타냅니다. "추상"이라는 명칭이 암시하듯이 추상 클래스는 관련 도메인의 구체적인 엔터티에 직접 대응하지 않는 경우가 많습니다. 그러나 여러 가지 서로 다른 구체적 엔터티가 공통으로 지니는 요소를 추상 클래스도 갖고 있습니다.
추상 클래스에는 AbstractClass 특성이 있어야 합니다. 추상 클래스는 구현된 멤버와 구현되지 않은 멤버를 가질 수 있습니다. 클래스에 추상이라는 용어를 적용할 때는 그 용법이 다른 .NET 언어의 경우와 같습니다. 그러나 추상이라는 용어를 메서드(및 속성)에 적용하는 경우에는 그 용법이 F#과 기타 .NET 언어 사이에 조금 다릅니다. F#에서 메서드를 abstract 키워드로 표시하는 경우 이는 멤버가 가상 디스패치 슬롯이라고 하는 항목을 해당 형식에 대한 가상 함수의 내부 테이블에 갖는다는 의미입니다. 즉, F# 언어에서는 virtual 키워드를 사용하지 않더라도 메서드가 가상 메서드가 됩니다. abstract 키워드는 메서드의 구현 여부와 상관없이 가상 메서드에 사용됩니다. 가상 디스패치 슬롯을 선언하는 것과 해당 디스패치 슬롯에 대한 메서드를 정의하는 것은 별개입니다. 따라서 다른 .NET 언어의 가상 메서드 선언 및 정의에 상응하는 F#의 방식은 default 키워드나 override 키워드를 사용하여 추상 메서드 선언과 개별 정의를 모두 결합하는 것입니다. 자세한 내용과 예제를 보려면 메서드(F#)을 참조하십시오.
클래스는 선언만 되고 정의되지는 않은 추상 메서드가 있을 때만 추상 클래스로 간주됩니다. 따라서 추상 메서드를 갖는 클래스라고 해서 반드시 추상 클래스라는 법은 없습니다. 클래스에 정의되지 않은 추상 메서드가 있는 경우가 아니면 AbstractClass 특성을 사용하지 말아야 합니다.
위 구문에서 accessibility-modifier는 public, private 또는 internal이 될 수 있습니다. 자세한 내용은 액세스 제어(F#)를 참조하십시오.
다른 형식과 마찬가지로 추상 클래스는 기본 클래스와 하나 이상의 기본 인터페이스를 가질 수 있습니다. 각각의 기본 클래스 또는 인터페이스는 inherit 키워드와 함께 별도의 줄에 표시됩니다.
추상 클래스의 형식 정의는 완전하게 정의된 멤버를 포함할 수도 있고 추상 멤버를 포함할 수도 있습니다. 추상 멤버의 구문은 위 구문에 별도로 표시되어 있습니다. 이 구문에서 멤버의 type signature는 매개 변수 형식을 순서대로 포함하고 반환 형식을 포함하는 목록입니다. 목록의 각 형식은 변환 및 튜플 매개 변수에 맞게 -> 토큰 및/또는 * 토큰을 사용하여 구분됩니다. 추상 멤버 형식 시그니처의 구문은 시그니처 파일에 사용되는 구문이면서 Visual Studio 코드 편집기에 IntelliSense를 통해 표시되는 구문과 같습니다.
예제
다음 코드에는 Shape라는 추상 클래스가 사용되고 있습니다. 이 추상 클래스에는 추상이 아닌 Square와 Circle이라는 두 개의 파생 클래스가 있습니다. 이 예제에서는 추상 클래스, 메서드 및 속성을 사용하는 방법을 보여 줍니다. 이 예제에서 추상 클래스 Shape는 구체적 엔터티인 원과 사각형의 공통 요소를 나타냅니다. 2차원 좌표계에 그릴 수 있는 모든 도형의 공통적인 특징인 눈금 위치, 회전 각도, 면적과 둘레 속성이 Shape 클래스로 추상화됩니다. 개별 도형에서 해당 동작을 변경할 수 없는 속성인 위치를 제외하고는 이들 속성을 재정의할 수 있습니다.
대칭성이 있으므로 어떻게 회전하더라도 아무런 차이가 없는 Circle 클래스에서 볼 수 있듯이 회전 메서드를 재정의할 수 있습니다. Circle 클래스에서 회전 메서드를 아무 작업도 수행하지 않는 메서드로 대체했습니다.
// 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
// overriden.
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