Привет Хабр!

Объектно-ориентированное программирование — это подход к разработке, основанный на объектах, экземплярах классов, сочетающих как данные, так и поведение. F#, выросший на функциональных концепциях, представляет ООП как дополнение к существующей функциональности. F# позволяет использовать классы, наследование и интерфейсы таким образом, чтобы элементы ООП легко вписывались в функциональный контекст, не создавая ощущения странности.

Основы ООП в F#

Классы и домены

Урок F# — это структуры, позволяющие комбинировать поля и методы, определяющие состояние и поведение объекта соответственно. Создание класса в F# начинается с ключевого слова typeза которым следует имя класса и его конструктор:

type MyClass(param1 : int, param2 : string) =
    // поля
    let mutable field1 = param1
    let field2 = param2

    // свойство
    member this.Property1 = field1
    member this.Property2 with get() = field2 and set(value) = field1 <- value

    // метод
    member this.Method1() =
        printfn "Method1 called with field1: %d and field2: %s" field1 field2

MyClass с двумя полями: field1 И field2, они инициализируются через параметры конструктора. Также есть два объекта Property1 И Property2Или Property1 доступен только для чтения и Property2 — чтение-запись, демонстрирующее использование свойств в F# для управления доступом к данным класса. Метод Method1 печатает значения полей.

let Привязки в классе используются для объявления полей или функций, доступных только в классе.

do привязки выполняют код инициализации при создании экземпляра класса.

type MyClass(x: int, y: int) =
    let sum = x + y
    do printfn "Сумма x и y равна %d" sum
    member this.Sum = sum

Конструктор инициализирует поля и выполняет дополнительное действие

Вы можете использовать самоидентификаторы для ссылки на текущий экземпляр класса:

type MyClass(x: int, y: int) as self =
    member self.Sum = x + y
    member self.Multiply = x * y

as self позволяет получить доступ к текущему экземпляру класса в его методах.

Интерфейсы

Интерфейсы определяются с помощью ключевого слова type указывая ключевое слово interface и перечислять методы и свойства без их реализации. Каждый метод или свойство интерфейса является абстрактным и определяет форму без конкретной реализации:

type IExampleInterface =
    abstract member ExampleMethod : string -> string
    abstract member ExampleProperty : int with get

IExampleInterface определяет интерфейс с методом ExampleMethodкоторый принимает строку и возвращает строку вместе со свойством ExampleProperty тип только для чтения int.

Реализация интерфейса указывается в теле класса с помощью ключевого слова interface за которым следует ключевое слово withмониторинг реализации методов и свойств интерфейса:

type ExampleClass() =
    interface IExampleInterface with
        member this.ExampleMethod(input) = "Processed: " + input
        member this.ExampleProperty with get() = 42

ExampleClass запустить проект IExampleInterfaceпутем предоставления конкретных реализаций для ExampleMethod И ExampleProperty. Это позволяет объектам ExampleClass использоваться по назначению IExampleInterface.

Наследование и абстрактные классы

Наследование позволяет создать новый класс на основе существующего класса, унаследовав его свойства и методы:

try
    let result = 10 / 0
with
| :? System.DivideByZeroException -> printfn "Деление на ноль."
| ex -> printfn "Произошло исключение: %s" (ex.Message)

DerivedClass унаследованный BaseClassдобавив новый параметр param2 и сохраните настройку param1 базового класса.

ЧИТАТЬ   Бизнес «объединился», чтобы бороться с правительственными реформами в международных отношениях, говорит Росс Гринвуд

Абстрактный класс в F# — это класс, экземпляр которого не может быть создан сам по себе и который служит основой для других классов. Абстрактные классы могут содержать абстрактные методы (без реализации) и методы с реализацией:

[<AbstractClass>]
type Shape(x0: float, y0: float) =
    let mutable x, y = x0, y0
    abstract member Area: float
    abstract member Perimeter: float
    member this.Move(dx: float, dy: float) =
        x <- x + dx
        y <- y + dy

Shape определяет общие характеристики фигур, такие как площадь и периметр, но не обеспечивает их конкретную реализацию, что делает класс абстрактным.

Пример наследования от абстрактного класса:

type Circle(x: float, y: float, radius: float) =
    inherit Shape(x, y)
    override this.Area =
        Math.PI * radius * radius
    override this.Perimeter =
        2.0 * Math.PI * radius

Circle наследует от абстрактного класса Shapeпутем предоставления конкретных реализаций для Area И Perimeter.

Обработка исключений и ошибок

Блокировать try...with используется для перехвата и обработки исключений. Код внутри блока try выполняется, и если во время его выполнения возникает исключение, выполнение передается блоку withгде исключение может быть обработано:

try
    let result = 10 / 0
with
| :? System.DivideByZeroException -> printfn "Деление на ноль."
| ex -> printfn "Произошло исключение: %s" (ex.Message)

Исключение перехватывается в блоке withи программа вместо сбоя выводит соответствующее сообщение

Блокировать try...finally используется для обеспечения выполнения определенного кода после блока tryпроизошло ли исключение:

try
    let result = 10 / 2
finally
    // код в этом блоке выполнится в любом случае
    printfn "Этот код выполнится независимо от исключений."

Вы можете создать пользовательские типы исключений для обработки конкретных ошибочных ситуаций. Это делается путем наследования от класса System.Exception или любое другое онлайн-исключение:

type MyCustomException(message: string) =
    inherit System.Exception(message)

MyCustomException — это пользовательское исключение, которое принимает сообщение об ошибке в качестве параметра и передает его конструктору базового класса. System.Exception:

let riskyOperation x =
    if x < 0 then
        raise (MyCustomException("Число не должно быть отрицательным"))
    else
        printfn "%d - это положительное число" x

riskyOperation генерирует MyCustomExceptionесли ему передается отрицательное число, что позволяет точно указать тип ошибки и облегчить ее управление.

ЧИТАТЬ   Да, я действительно плачу за Discovery Plus

Вы также можете войти в систему чтобы залогиниться Ошибки с использованием Серилог. Добавьте зависимость Serilog в проект через NuGet:

open Serilog

Log.Logger <- LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .WriteTo.File("logs/myapp.txt", rollingInterval = RollingInterval.Day)
    .CreateLogger()

Log.Information("Запуск приложения")

try
    let result = 10 / 0
with
| ex ->
    Log.Error(ex, "Произошло исключение при выполнении операции")

В блоке try...with Исключение обнаруживается и регистрируется с помощью Serilog, предоставляя подробную информацию об ошибке.

Оптимизация

Инкапсуляция — это принцип ООП, который заключается в ограничении прямого доступа к определенным компонентам объекта и контроле доступа к этим данным с помощью методов:

type BankAccount() =
    let mutable balance = 0.0

    member this.Deposit(amount: float) =
        if amount > 0.0 then
            balance <- balance + amount
        else
            raise (ArgumentException("Сумма должна быть больше нуля"))

    member this.Withdraw(amount: float) =
        if amount <= balance && amount > 0.0 then
            balance <- balance - amount
        else
            raise (InvalidOperationException("Недостаточно средств или сумма меньше нуля"))

    member this.GetBalance() = balance

Здесь мы используем инкапсуляцию для контроля изменений баланса банковского счета с помощью методов Deposit И Withdrawпредотвращение прямого доступа к земле balance

Наследие позволяет создать новый класс на основе существующего класса, повторно используя его код и расширяя его функциональность:

type Vehicle() =
    abstract member Move: unit -> string

type Car() =
    inherit Vehicle()
    override this.Move() = "Едет на четырех колесах"

type Bicycle() =
    inherit Vehicle()
    override this.Move() = "Едет на двух колесах"

Car И Bicycle наследовать от Vehicle и реализовать абстрактный метод Move

Полиморфизм используется для создания интерфейсов и абстрактных классов, которые могут быть реализованы множеством разных классов:

type IDrawable =
    abstract member Draw: unit -> unit

type Circle() =
    interface IDrawable with
        member this.Draw() = printfn "Рисуется круг"

type Square() =
    interface IDrawable with
        member this.Draw() = printfn "Рисуется квадрат"

let drawShapes shapes =
    shapes |> List.iter (fun shape -> shape.Draw())

Circle И Square реализовать интерфейс IDrawableи поэтому вы можете обрабатывать их также в функции drawShapes.


ООП позволяет создавать структурированные и простые в обслуживании системы. А узнать больше об ООП и PL в целом можно в рамках экспертные уроки программирования от моих друзей из OTUS.

Source

От admin