Интерфейсы

Последнее обновление: 12.12.2023

Интерфейсы представляют набор связанных компонентов, которые должен реализовать класс. Определение интерфейсов имеет следующий синтаксис:

type имя_интерфейса =
    [ interface ]
    abstract компонент1 : тип_компонента1
    abstract компонент2 : тип_компонента2
    abstract компонент3 : тип_компонента3
[ end ]

Объявления интерфейса напоминают объявления классов - интерфейс определяется с помощью оператора type, после которого идет имя интерфейса. Затем идет тело интерфейса между операторами interface и end. Внутри тела интерфейса, как и в классе, можно определять свойства и методы. Но в отличие от классов свойства и методы интерфейсов не имеют реализации. Вместо этого они являются абстрактными, на что указывает ключевое слово abstract.

Если интерфейс не пустой - имеет свойства и методы, то ключевые слова interface и end можно не использовать, а компилятор автоматически выведет, что тип представляет интерфейс.

Например, определим пустой интерфейс:

type IPrintable = interface end

Этот интерфейс называется IPrintable (обычно названия интерфейсов в .NET начинаются с буквы I, однако это не более чем необязательная условность). Этот интерфейс пустой, он не определяет никаких свойств и методов.

Определим в интерфейсе метод:

type IPrintable = 
    abstract member Print: unit -> unit

Здесь определен метод Print, который имеет тип unit -> unit, то не принимает никаких параметров и ничего не возвращает.

Реализуем интерфейс в классе:

type IPrintable = 
    abstract member Print: unit -> unit

type Person(name: string, age: int) =
    interface IPrintable with
        member this.Print() = printfn "Name: %s  Age: %d" name age

let tom: IPrintable = Person("Tom", 39)
tom.Print() // Name: Tom  Age: 39

Для реализации интерфейса внутри класса применяем выражение

interface имя_интерфейса with
    member реализация_метода

В данном случае в реализации метода Print выводим на консоль имя и возраст пользователя.

Далее мы определяем переменную типа IPrintable, то есть типа интерфейса, а для создания объекта используется конструктор класса Person.

let tom: IPrintable = Person("Tom", 39)
tom.Print()

Главное ограничение интерфейсов в F# заключается в том, что методы интерфейса можно вызывать только через объект этого интерфейса. Поэтому в примере выше значение tom представлял объект интерфейса IPrintable, а не просто Person, несмотря на то, что класс Person реализует этот интерфейс. То есть мы НЕ можем написать следующим образом:

let tom = Person("Tom", 39)
tom.Print()

В этом случае компилятор нам сгенерирует ошибку, потому что в классе Person нет метода Print.

В качестве альтернативы мы можем выполнить преобразование к типу интерфейса с помощью оператора :> или оператора восходящего преобразования:

type IPrintable = 
    abstract member Print: unit -> unit

type Person(name: string, age: int) =
    interface IPrintable with
        member this.Print() = printfn "Name: %s  Age: %d" name age

// значение tom имеет тип Person
let tom = Person("Tom", 39)
// преобразуем значение tom к типу IPrintable
(tom :> IPrintable).Print()

В качестве альтернативы можно объявить в классе одноименный метод и вызывать в нем реализацию интерфейса:

type IPrintable = 
    abstract member Print: unit -> unit

type Person(name: string, age: int) =
    // метод Print вызывает реализацию метода интерфейса
    member this.Print() = (this :> IPrintable).Print()
    interface IPrintable with
        member this.Print() = printfn "Name: %s  Age: %d" name age


// значение tom имеет тип Person
let tom = Person("Tom", 39)
// преобразование к типу IPrintable не нужно
tom.Print()

Аналогичным образом можно применять несколько интерфейсов:

type IPrintable = 
    abstract member Print: unit -> unit

type ISendable = 
    abstract member Send: unit -> unit

type Message(text:string) = 
    interface IPrintable with
        member this.Print() = printfn "Text: %s" text
    interface ISendable with
        member this.Send() = printfn "Sending message: %s" text

let mes = Message("Hello World")

(mes :> IPrintable).Print()
(mes :> ISendable).Send()

В данном случае класс Message реализует два интерфейса - IPrintable и ISendable.

Интерфейс как контракт

Суть интерфейса заключается в том, что он определяет контракт, что определенный тип реализует определенные методы. И когда мы используем объект интерфейса, мы уверены, что у этого объекта есть определенный набор методов, который мы можем использовать. Например:

type IPrintable = 
    abstract member Print: unit -> unit

type Person(name: string, age: int) =
    interface IPrintable with
        member this.Print() = printfn "Name: %s  Age: %d" name age

type Message(text:string) = 
    interface IPrintable with
        member this.Print() = printfn "Text: %s" text

let print (obj:IPrintable) = obj.Print()

let tom = Person("Tom", 39)
let mes = Message("Hello World")

print tom 
print mes

Здесь функция print в качестве параметра принимает объект IPrintable. Мы не знаем, что это будет за объект, какой именно тип он будет представлять (в примере выше это может быть объект Person или объект Message). Но мы знаем, что он реализует интерфейс IPrintable, и поэтому у него есть метод Print, который можно вызвать.

Выражения объектов

Выражения объектов (object expression) предоставляют краткий способ реализации некоторого функционала, например, интерфейса. Выражения объектов применяются, когда надо избежать дополнительного кода, необходимого для создания нового именованного типа. Выражения объектов позволяют минимизировать код и количество типов.

Выражение объектов на основе интерфейса имеет следующий синтаксис:

{ new имя_интерфейса with
    компоненты_объекта
}

Все выражение объекта заключается в фигурные скобки и начинается со слова new. Затем идет имя интерфейса, на основе которого создается объект. После оператора with указываются реализации методов интерфейса.

Объекты на основе интерфейсов могут быть полезны, когда нет смысла создавать отдельный класс, который реализует интерфейс, а просто нужен объект, который имеет методы интерфейса без какой-то дополнительной функциональности. Другой вариант - если у нас есть класс, который не реализует интерфейс, и мы хотим, чтобы для этих объектов выполнить методы интефейса. Например:

type IPrintable = 
    abstract member Print: unit -> unit

// класс, который не реализует интерфейс IPrintable
type Person(name: string, age: int) =
    member this.Name = name
    member this.Age = age

// выражение объекта
let makePrintable (person: Person) =
    { new IPrintable with
        member this.Print() = printfn "Name: %s  Age: %d" person.Name person.Age }

// создаем объекты с помощью выражения объектов
let tom = makePrintable (Person("Tom", 39))
let bob = makePrintable (Person("Bob", 43))

tom.Print()
bob.Print()

Здесь класс Person не реализует интерфейс IPrintable, однако мы хотим, чтобы для объектов Person был реализован метод Print. Возможно, в каких-то ситуациях лучше реализовать интерфейс в классе. Но, допуcтим, класс используется довольно широко в проекте, а возможность вывода обекта Person нам нужна в каком-то единичном месте. Не говоря уже о том, что мы можем использовать внешний класс, код которого нам не доступен. В этом случае можно было бы использовать выражения объектов.

В данном случае определяется функция makePrintable, которая использует выражение объектов и возвращает объект на основе интерфейса IPrintable:

let makePrintable (person: Person) =
    { new IPrintable with
        member this.Print() = printfn "Name: %s  Age: %d" person.Name person.Age }

Здесь определяется метод Print, который выводит значения свойств Name и Age из параметра person.

Далее мы можем вызвать функцию makePrintable и создать несколько объектов, которые будут иметь метод Print:

let tom = makePrintable (Person("Tom", 39))
let bob = makePrintable (Person("Bob", 43))

Консольный вывод:

Name: Tom  Age: 39
Name: Bob  Age: 43
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850