Kotlin factory что это

Паттерны проектирования в Kotlin

Паттерны проектирования в Kotlin

Говорят, что «паттерны проектирования — это обходные пути недостатков определенного языка программирования». Интересное суждение, если бы только оно не было сказано апологетами Lisp и Schema.

Но, похоже, разработчики языка Kotlin восприняли это высказывание по-настоящему близко к сердцу.

Одиночка (Singleton)

Конечно, первый паттерн, который приходит на ум, — Одиночка. И он встроен прямо в язык в виде ключевого слова object:

Теперь поле JustSingleton.value будет доступно из любого места в пакете.

И нет, это не статическая инициализация, как может показаться. Давайте попробуем инициализировать это поле с некоторой задержкой внутри:

object SlowSingleton < val value : String init < var uuid = "" val total = measureTimeMillis < println("Computing") for (i in 1..10_000_000) < uuid = UUID.randomUUID().toString() >> value = uuid println("Done computing in $ms") > >

Происходит ленивая инициализация при первом вызове:

@org.testng.annotations.Test fun testSingleton() < println("Test started") for (i in 1..3) < val total = measureTimeMillis < println(SlowSingleton.value) >println("Took $total ms") > >
Test started Computing Done computing in 5376ms "45f7d567-9b3e-4099-98e6-569ebc26ecdf" Took 5377 ms "45f7d567-9b3e-4099-98e6-569ebc26ecdf" Took 0 ms "45f7d567-9b3e-4099-98e6-569ebc26ecdf" Took 0 ms

Обратите внимание, если вы не используете этот объект, операция проходит за 0 мс, хотя объект всё ещё определён в вашем коде.

val total = measureTimeMillis < //println(SlowSingleton.value) >
Test started Took 0 ms Took 0 ms Took 0 ms

Декоратор

Затем идет Декоратор. Это паттерн, который позволяет добавить немного функциональности поверх какого-то другого класса. Да, IntelliJ может создать его за вас. Но Kotlin пошёл ещё дальше.

Как насчёт того, чтобы каждый раз при добавлении нового ключа в HashMap, мы получали сообщение об этом?

В конструкторе вы определяете экземпляр, которому делегируете все методы, используя ключевое слово by.

/** * Using `by` keyword you can delegate all but overridden methods */ class HappyMap(val map : MutableMap = mutableMapOf()) : MutableMap by map < override fun put(key: K, value: V): V? < return map.put(key, value).apply < if (this == null) < println("Yay! $key") >> > >

Заметьте, что мы можем получать доступ к элементам нашей мапы через квадратные скобки и использовать все остальные методы так же, как и в обычной HashMap.

@org.testng.annotations.Test fun testDecorator() < val map = HappyMap() val result = captureOutput < map["A"] = "B" map["B"] = "C" map["A"] = "C" map.remove("A") map["A"] = "C" >assertEquals(mapOf("A" to "C", "B" to "C"), map.map) assertEquals(listOf("Yay! A", "Yay! B", "Yay! A"), (result)) >

Фабричный метод

Companion object позволяет легко реализовать Фабричный метод. Это тот паттерн, при помощи которого объект контролирует процесс своей инициализации для того, чтобы скрывать какие-то секреты внутри себя.

class SecretiveGirl private constructor(val age: Int, val name: String = "A girl has no name", val desires: String = "A girl has no desires") < companion object < fun newGirl(vararg desires : String) : SecretiveGirl < return SecretiveGirl(17, desires = desires.joinToString(", ")) >fun newGirl(name : String) : SecretiveGirl < return SecretiveGirl(17, name = name) >> >

Теперь никто не может изменить возраст SecretiveGirl:

@org.testng.annotations.Test fun FactoryMethodTest() < // Cannot do this, constructor is private // val arya = SecretiveGirl(); val arya1 = SecretiveGirl.newGirl("Arry") assertEquals(17, arya1.age) assertEquals("Arry", arya1.name) assertEquals("A girl has no desires", arya1.desires) val arya2 = SecretiveGirl.newGirl("Cersei Lannister", "Joffrey", "Ilyn Payne") assertEquals(17, arya2.age) assertEquals("A girl has no name", arya2.name) assertEquals("Cersei Lannister, Joffrey, Ilyn Payne", arya2.desires) >

Стратегия

Последний на сегодня — Стратегия. Поскольку в Kotlin есть функции высокого порядка, реализовать этот паттерн тоже очень просто:

И динамически менять поведение:

@org.testng.annotations.Test fun testStrategy() < val someAnimal = UncertainAnimal() val output = captureOutput < someAnimal.makeSound() someAnimal.makeSound = fun () < println("Woof!") >someAnimal.makeSound() > assertEquals(listOf("Meow!", "Woof!"), output) >

Обратите внимание, что это действительно паттерн Стратегия, и измененить сигнатуру метода нельзя (привет, JS!)

// Won't compile! someAnimal.makeSound = fun (message : String)

И если вам интересно узнать больше о Kotlin и встроенных в него паттернах проектирования, есть отличная книга «Kotlin in Action». Вам она понравится, даже если вы не планируете использовать этот язык в ближайшем будущем (хотя нет причин этого не делать).

  • совершенный код
  • code complete
  • паттерны проектирования
  • design patterns
  • kotlin
  • котлин
  • перевод с английского
  • программирование
  • разработка
  • devcolibri
  • никто не читает теги

Источник

Introduction 🔥

We all know design patterns or at least heard of them once in our lives as developers, they are supposed to ensure the maintenance integrity of our codebase and reduce headaches, but do they? The truth is, there are cases where you do not need this pattern and those cases may vary from not using an OOP language, writing a project where you don’t really care about its scalability, or it’s not complex enough to use a design pattern. In this article, we will get our hands dirty with Kotlin coding as well as UML & some hot tips!

Prerequisite ☕️

Before proceeding, you need to be familiar with UML, some basic Kotlin knowledge and have an idea of how Java handles inheritance.

Quick note before reading 📖

Since it’s 2020 and we’re all quarantined, I used an example that is relevant to 2020’s pandemic 🦠. We are going to make a virus factory, that creates virus Java objects! I know this is not 100% practical, but. it’s fun. This article can also be found on my blog archive which is located here https://jimfilippou.github.io/blog/using-the-factory-design-pattern-with-kotlin.

Taking a look at the UML we will be implementing. 👀

Alt Text

The UML diagram will look as follows

Planning 📝

  1. Create the virus interface
  2. Create concrete virus classes that implement the Virus interface
  3. Create an enumeration which will hold virus types (constants)
  4. Create the VirusFactory which will eventually generate a proper Virus given the virus type from the enumeration
  5. Use what we made

implementing ⚒

1. Creating the virus interface

The following code will do the trick to make this Interface 👇

interface Virus  public fun mutate() public fun spread()  println("Spreading the virus. ") > > 

💡 TIP: Notice that in regular java interfaces, typically you only do declarations, not implementations. The function body is 100% optional because in the future it is supposed to be overridden.

The following statement comes from the documentation:

Interfaces in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. They can have properties but these need to be abstract or to provide accessor implementations.

2. Creating concrete virus classes

Now we need to create the different classes that implement the Virus Interface.

💡TIP: You can create as many classes as you wish, as long they implement the Virus interface. Typically, this pattern is more useful when you have lots of classes because when you only have a few, you can work around this issue and maybe avoid using this pattern.

class CoronaVirus: Virus  override fun mutate()  println("Mutating the corona virus. ") > > class InfluenzaVirus: Virus  override fun mutate()  println("Mutating the flu virus. ") > > class HIVVirus: Virus  override fun mutate()  println("Mutating the HIV virus. ") > > 

3. Creating the enumeration

In kotlin, it’s fairly easy to make an enum by using the following code 👇

enum class VirusType  CORONA_VIRUS, INFLUENZA, HIV > 

But why do we use constants this way? couldn’t we just use strings for virus types?

The answer is yes however, a good practice is not to use strings because strings are more error-prone because as humans, we tend to make typos very frequently.

If you are coming from the front-end world, the same reason applies to Redux action types ⚛

4. Creating the factory 🏭

Now let’s implement the VirusFactory class 🚀

class VirusFactory  fun makeVirus(type: VirusType): Virus?  return when(type)  VirusType.CORONA_VIRUS -> CoronaVirus() VirusType.INFLUENZA -> InfluenzaVirus() VirusType.HIV -> HIVVirus() else -> null > > > 

💡TIP: The return type is Virus? which means it may return null . We did this because we want to make sure that the factory accepts only the specified virus types inside the when keyword. The when keyword is just like the switch keyword, but much sexier 🍑.

5. Using the factory 🏭

Now that everything is completed, let’s use the factory we just created!

fun main()  val factory = VirusFactory() val virus = factory.makeVirus(VirusType.CORONA_VIRUS) virus?.spread() virus?.mutate() > 

💡TIP: Notice that we called the functions mutate and spread with a question mark before accessing the object’s properties and/or methods. This question mark means that the functions will only be called when the virus object is not NULL.

If you are familiar with JavaScript, this behaves just like optional chaining

Thank you for reading! 🎉

You reached the end! I hope you learned something. In case you need the entire codebase, I have embedded it below so you can copy and paste it. Let me know in the comments on what you think about this post or this design pattern in general.

Источник

Читайте также:  Php fopen can open file
Оцените статью