Статьи

Котлин месяц пост 2: наследование и значения по умолчанию

вступление

На этой неделе, продолжая месяц Kotlin , я буду обсуждать темы в Kotlin, связанные с наследованием и значениями по умолчанию.

Композиция по наследству

Kotlin добавил функцию и пару настроек по умолчанию, которые помогают разработчикам держать принцип Composition Over Inheritance в центре внимания. Первая из них — это функция, которая позволяет составлять почти так же легко, как наследование, когда речь идет об определении классов-оболочек.

Делегация первого класса

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

  1. Новый класс наследует от интерфейса
  2. Новый класс предоставляет основной конструктор, который определяет свойство, которое наследуется от того же интерфейса
  3. После объявления наследования интерфейса добавьте «by <name of property>»

Например:

1
2
3
4
5
interface A {
   fun doSomething(): Unit
}
 
class B (val a: A) : A by a

В этом примере у нас есть интерфейс A , в котором есть метод doSomething() для реализации. Класс B — это наш класс делегатов. Он наследует от A , включает в себя первичный конструктор со свойством, называемым a который наследует от A ( (val a: A) ), и говорит, что должен делегировать a с A by a .

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

1
2
3
4
5
class B (val a: A) A {
   override fun doSomething() {
      a.doSomething()
   }
}

Делегирование может быть выполнено одновременно для нескольких интерфейсов.

Я создал декоратор классов в Python, который сделал нечто подобное некоторое время назад, так как мне очень понравилась эта идея.

Kotlin практически нужно что-то подобное в языке, чтобы согласиться с определенным набором значений по умолчанию …

Окончательный по умолчанию

Классы и публичные методы являются final по умолчанию в Kotlin, то есть они не могут быть унаследованы или переопределены, соответственно. Чтобы сделать эти опции доступными, классы и методы должны быть помечены как open .

Это соответствует пункту 17 «Эффективной Java», а именно «Разработать и документировать документ для наследования или запретить его», точно так же, как весь этот элемент соответствует пункту 16 в «Эффективной Java» «Композиция Favor Over Inheritance».

Эти две части прекрасно работают вместе и, будем надеяться, будут означать, что код, написанный на Kotlin, будет лучше из-за этого.

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

Модификатор видимости по умолчанию

Какое слово наиболее часто вводится во всей Java? Я бы рискнул предположить, что это public , с return и import на втором месте. По большей части это public ключевое слово является беспорядком; большинство вещей общедоступны, и это должно быть по умолчанию. Котлин решил сделать это, сделав код чище.

Теперь, это идет вразрез с общим советом по инкапсуляции для того, чтобы сделать члены класса как можно более закрытыми, но даже когда этот совет очень хорошо выполняется, public по-прежнему является наиболее распространенным модификатором видимости. Так что, к счастью, в Kotlin по умолчанию используется public .

Запечатанные Классы

Запечатанные Классы — более интересная, чем полезная особенность, по моему мнению. Они обеспечивают способ блокировки иерархии, чтобы единственными подклассами запечатанного класса были те, которые определены в его границах. Это действительно удобно для монад с разделенным типом (таких как Either и Maybe / Optional) и, судя по примеру, используемому в документах, для деревьев выражений.

Опять же, у Kotlin есть дополнительная функция, которая хорошо работает с этой функцией, и это выражение « when , которое является переосмысленным switch . Выражение when уведомляется компилятором, чтобы узнать, представлены ли все подклассы, чтобы оно не заставляло вас без необходимости предоставлять предложение else умолчанию.

Опять же, я нахожу эту функцию более интересной, чем полезной, тем более что я разработал (немного более болезненный) способ в значительной степени воссоздать то же самое в Java (с дополнительной идеей или двумя, проходящими через мою голову). Если вам интересно, как я это делаю, оставьте комментарий, дающий мне знать, и я сделаю пост об этом.

Методы расширения

Методы расширения — это методы, которые вы можете «добавить в класс» после факта. Я использую кавычки, потому что они на самом деле не добавляются в класс (по крайней мере, в Kotlin); скорее это статические методы, которые можно использовать так, как если бы они были методами в экземпляре, что позволяет вам импортировать эти методы для использования только в определенных случаях и обнаруживать их с помощью автозаполнения. Но в основном это синтаксис, который делает статические методы менее глупыми.

Почему это не так уж много в мире программирования? Все эти служебные классы String и Date было бы намного проще использовать, если бы их можно было использовать как расширения для реального класса. Кроме C #, я не знаю ни одного другого языка, который делает это (понятно, что динамическим языкам на самом деле не нужно ничего особенного для этой функции, поскольку вы можете буквально добавить любой метод в класс в любое время). Если вы знаете что-либо, я был бы признателен, если бы вы перечислили их в комментариях.

Еще одна удивительная вещь о методах расширения — это то, как Kotlin комбинирует их с лямбдами, чтобы сделать лямбды-расширения, которые делают некоторые вещи, такие как их безопасные для типов сборщики, намного чище.

Outro

Это конец этой статьи. Настройтесь на следующую неделю для третьего поста месяца Kotlin! В этом разделе будут рассмотрены функции безопасности, которые есть у Kotlin, такие как нулевая безопасность и улучшенные генерики.