Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Объектно-ориентированный стиль программирования стремится к ограничению ограничивает области видимости функций. Функции не глобальны, вместо этого они являются частью классов, и могут быть вызваны только для экземпляра соответствующего класса (примечание Педанта:  некоторые классические процедурные языки имеют модульную систему и, значит, области видимости; процедурный язык != С). Конечно, мы всегда можем заменить функцию-члена класса глобальной функцией с дополнительным аргументом, имеющим тип вызываемого объекта, но с синтаксической точки зрения разница довольно значительна. Например, в этом случае методы сгруппированы в классе, к которому они обращаются, и поэтому более ясно видно какое поведение обеспечивают объекты данного типа. Конечно, наиболее важны здесь инкапсуляция, благодаря которой некоторые поля класса или его поведение могут быть приватными и доступными только членам этого класса (вы не можете обеспечить этого в чисто процедурном подходе), и полиморфизм, благодаря которому фактически используемый метод определяется не только на основе имени метода, но и на основе типа объекта, из которого он вызывается.  Диспетчеризация вызова метода в объектно-ориентированном подходе зависит от типа объекта, определяемого в рантайме, имени метода, и типа аргументов на этапе компиляции.

Функциональный подход не приносит чего-то принципиально нового в плане разрешения функций. Обычно функционально-ориентированные языки имеют лучшие правила для разграничения областей видимости (примечание Педанта: еще раз, C - это еще не все процедурные языки, есть такие, в которых области видимости хорошо разграничены), которые позволяют проводить более скрупулезный контроль видимости функций на основе системы модулей, но кроме этого, разрешение проводится во время компиляции основываясь на типе аргументов.

Что такое this?

В случае объектного подхода, при вызове метода у объекта, у нас есть его аргументы, но кроме того мы имеем явный (в случае Python) или неявный параметр, представляющий экземпляр вызываемого класса (здесь и далее все примеры написаны на Kotlin):

...

Code Block
interface B{
    fun doBSomething()
}

class A{
    fun doASomething(){
        val b = object: B{
            override fun doBSomething(){
                println("Этот метод вызывается на $this внутри ${this@A}")
            }
        }
        b.doBSomething()
    }
}

В данном случае , есть два неявных this для функции doBSomething - один соответствует экземпляру класса B, а другой возникает от замыкания экземпляра A. То же самое происходит в намного более часто встречающемся случае лямбда-замыкания. Важно отметить, что this в данном случае работает не только как неявный параметр, но и как область или контекст для всех функций и объектов, вызываемых в лексической области определения. Так что метод doBSomething на самом деле имеет доступ к любым, открытым или закрытым, членам класса A, так же как и к членам самого B.

...

В этом примере обе функции можно было вызвать без дополнительного "a." в начале, потому что функция with помещает весь код последующего блока внутрь контекста a. Это значит, что все функции в этом блоке вызываются так, как если бы они вызывались на (явно переданном) объекте a.

...

Code Block
interface NumberOperations{
    operator fun Number.plus(other: Number) : Number
    operator fun Number.minus(other: Number) : Number
    operator fun Number.times(other: Number) : Number
    operator fun Number.div(other: Number) : Number
}

object DoubleOperations: NumberOperations{
    override fun Number.plus(other: Number) = this.toDouble() + other.toDouble()
    override fun Number.minus(other: Number) = this.toDouble() - other.toDouble()
    override fun Number.times(other: Number) = this.toDouble() * other.toDouble()
    override fun Number.div(other: Number) = this.toDouble() / other.toDouble()
}

fun main(){
    val n1: Number = 1.0
    val n2: Number = 2
    val res = with(DoubleOperations){
        (n1 + n2)/2
    }
    
    println(res)
}

В этом примере , расчет res делается внутри контекста, который определяет дополнительные операции. Контекст не обязательно определять локально, вместо этого он может быть передан неявно как получатель функции. Например, можно сделать так:

...

  • Он содержит CoroutineContext, который нужен для запуска корутин и наследуется когда запускается новая сопрограмма.
  • Он содержит состояние родительской корутины, позволяющее отменить ее в случае, если порожденная сопрограмма выкидывает ошибку.

Также, структурированная конкурентность (structured concurrency) предоставляет отличный пример контекстно-ориентированной архитектуры:

...

DSL-построители в большинстве случаев контекстно ориентированы. Например, если вы хотите создать HTML-элемент, надо в первую очередь проверить, можно ли добавлять этот конкретный элемент в данное место. Библиотека kotlinx.html достигает этого предоставлением основанных на контексте расширений классов, представляющих определенный тег. По сути, вся библиотека состоит из  из контекстных расширений для существующих элементов DOM.

...

На данный момент разработка в контекстном подходе ограничена тем фактом, что нужно определять расширения, чтобы получить какое-то ограниченное контекстом поведение класса. Это нормально, когда речь идет о пользовательском классе, но что если мы хотим то же самое для класса из библиотеки? Или если мы хотим создать расширение для уже ограниченного в области поведения (например, добавить какое-то расширение внутрь CoroutineScope)? На данный момент Kotlin не позволяет функциям-расширениям иметь более одного получателя. Но множественные получатели можно было бы добавить в язык, не нарушая обратной совместимости. Возможность использования множественных получателей сейчас обсуждается (KT-10468) и будет оформлена в виде KEEP-запроса (UPD: уже оформлена). Проблема (или, может быть, фишка) вложенных контекстов - в том, что они позволяют покрыть большинство, если не все, варианты использования классов типов (type-classes), другой очень желанной из возможный предложенных фич. Довольно маловероятно, что обе эти фичи будут реализованы в языке одновременно.

...