91
Design Patterns with Kotlin @ejf_io @yenerm #kotlinpatterns

Design patterns with Kotlin

Embed Size (px)

Citation preview

Page 1: Design patterns with Kotlin

Design Patterns with Kotlin@ejf_io

@yenerm

#kotlinpatterns

Page 2: Design patterns with Kotlin

@yenerm

Who are we?

@ejf_io

Page 3: Design patterns with Kotlin

Disclaimer

Page 4: Design patterns with Kotlin

Why this talk?Because Design Patterns,

- Makes you look cool & smart & geeky (or nerdy)

- Easy to win technical discussions

- DP books look great on your library

- Makes your life easier when architecting your software

Page 5: Design patterns with Kotlin

“Object-oriented programming is an

exceptionally bad idea which could only have

originated in California”

-Edsger W. Dijkstra

Page 6: Design patterns with Kotlin

Design patterns are "descriptions of communicating

objects and classes that are customized to solve a

general design problem in a particular context."

-Gang of Four

Page 7: Design patterns with Kotlin

Design Patterns are the collective wisdom and experience of many smart developers. They, which

unleash a huge great depth of experience that can be utilized to solve many common problems which that

occur in software development…

- from Professional Java EE Design Patterns

Page 8: Design patterns with Kotlin

Kotlin- Is a statically-typed programming language that runs on the JVM- Can be compiled to JavaScript source code- Is interoperable with Java code - Is reliant on Java code from the existing Java Class Library, such as the

collections framework- Adds new functionality by maintaining the previous two (such as mutable/non

mutable collections)- Designed and Developed by JetBrains- Named after an island like Java but Java is actually named after coffee..

https://en.wikipedia.org/wiki/Kotlin_(programming_language)

Page 9: Design patterns with Kotlin

Some tools...

Page 10: Design patterns with Kotlin

Java 2 KotlinIntelliJ > Code > Convert Java File to Kotlin File

Or simply paste Java code in a Kotlin file..

Page 11: Design patterns with Kotlin

Kotlin 2 JavaIntelliJ > Tools > Kotlin > Show Kotlin Bytecode ⇒ Decompile

Page 12: Design patterns with Kotlin

Before we start...

Written in Java

Written in Java,Converted to Kotlin

Written in Kotlin

Written in Kotlin,Decompiled to Java

Page 13: Design patterns with Kotlin

GoF Patterns with Kotlin

Page 14: Design patterns with Kotlin

Creational Patterns

Page 15: Design patterns with Kotlin

Singleton- “Ensure a class has only one instance, and provide

a global point of access to it.”

- Classic implementation: private constructor, double locking, static

initializer, enums…

- must be thread safe!! And ideally reflection safe..

- Some frameworks/platforms offer built-in solutions (Java EE, Android…)

Page 16: Design patterns with Kotlin

-Singleton - naive javapublic class Singleton{

private static final Singleton INSTANCE = new Singleton();

private Singleton(){}

public static Singleton getInstance(){

return INSTANCE;

}

private int count = 0;

public int count(){ return count++; }

}

Page 17: Design patterns with Kotlin

Singleton - naive java to kotlinobject Singleton {

val instance = Singleton()

private var count: Int = 0

fun count(): Int {

return count++

}

}

Page 18: Design patterns with Kotlin

Singleton - java with enumpublic enum Singleton {

INSTANCE;

private int count = 0;

public static int count(){

return count++;

}

}

Page 19: Design patterns with Kotlin

Singleton - to Kotlinenum class Singleton {

INSTANCE;

private var count = 0

fun count(): Int {

return count++

}

}

Page 20: Design patterns with Kotlin

Singleton - Native Kotlinobject Singleton {

var count: Int = 0

fun count() {

count++

}

}

Page 21: Design patterns with Kotlin

Singleton - Native Kotlinobject Singleton {

var count: Int = 0

fun count() {

count++

}

}

‘object’ makes it a singleton without any additional effort!

https://kotlinlang.org/docs/reference/object-declarations.html#object-declarations

Page 22: Design patterns with Kotlin

Singleton - Kotlin Decompiledobject Singleton {

var count: Int = 0

fun count() {

count++

}

}

public final class Singleton {

private static int count;

public static final Singleton INSTANCE;

public final int getCount() {return count;}

public final void setCount(int var1) {count = var1;}

public final int count() {

int var1 = count++;

return var1;

}

private Singleton() {

INSTANCE = (Singleton)this;

}

static { new Singleton(); }

}

Page 23: Design patterns with Kotlin

Singleton - GBU

- The Good: expensive objects, caching, global access…

- The Bad: overuse can cause problems, lazy loading can cause delays, not

lazy loading may cause memory problems...

- and The Ugly: sometimes considered as an anti-pattern..

Page 24: Design patterns with Kotlin

Factory Method- “Define an interface for creating an object, but let subclasses decide which

class to instantiate. Factory Method lets a class defer instantiation to

subclasses.”

- Creational pattern, to encapsulate creation of objects.

- The code responsible for object creation (and subject to change) is

encapsulated

Page 25: Design patterns with Kotlin

Factory Method - Javapublic interface Car {

enum Brand{ AUDI, MERCEDES }

}

public class Audi implements Car{}

public class Mercedes implements Car{}

public class CarFactory {

public Car rentACar(Car.Brand brand){

switch (brand){

case AUDI:

return new Audi();

case MERCEDES:

return new Mercedes();

default:

return null;

}

}

}

Page 26: Design patterns with Kotlin

Factory Method - Java to Kotlininterface Car {

enum class Brand{ AUDI, MERCEDES }

}

class Audi : Car

class Mercedes : Car

class CarFactory {

fun rentACar(brand: Car.Brand): Car {

when (brand) {

Car.Brand.AUDI -> return Audi()

Car.Brand.MERCEDES -> return Mercedes()

else -> return null

}

}

}Null can not be a value of a non-null type Car

Page 27: Design patterns with Kotlin

Factory Method - Java to Kotlininterface Car {

enum class Brand{ AUDI, MERCEDES }

}

class Audi : Car

class Mercedes : Car

class CarFactory {

fun rentACar(brand: Car.Brand): Car? {

when (brand) {

Car.Brand.AUDI -> return Audi()

Car.Brand.MERCEDES -> return Mercedes()

else -> return null

}

}

}

Page 28: Design patterns with Kotlin

Factory Method - Kotlininterface Car {

enum class Brand{ AUDI, MERCEDES }

}

class Audi : Car

class Mercedes : Car

class CarFactory {

fun rentACar(brand: Car.Brand): Car? {

when (brand) {

Car.Brand.AUDI -> return Audi()

Car.Brand.MERCEDES -> return Mercedes()

else -> return null

}

}

}

Page 29: Design patterns with Kotlin

Factory Method - GBU

- The Good: Very easy to implement and easily encapsulates object creation.

- The Bad: Although it might be enough most of the time, you may prefer

moving to Abstract Factory.

- The Ugly: Can create complicated messy code if not really needed.

Page 30: Design patterns with Kotlin

Abstract Factory- “Provide an interface for creating families of related or dependent objects

without specifying their concrete classes.”

- Creational pattern, to encapsulate creation

- Very similar to Factory Method

- Each subclass has its own factory

- Used commonly in JDK

Page 31: Design patterns with Kotlin

Abstract Factory - Javapublic interface Car {}

public class Audi implements Car{}

public class Mercedes implements Car{}

public class AudiFactory extends AbstractFactory{

@Override public Car rentACar() {

return new Audi();

}

}

public class MercedesFactory extends AbstractFactory{

@Override public Car rentACar() {

return new Mercedes();

}

}

public abstract class AbstractFactory {

public abstract Car rentACar();

public static Car createFactory(AbstractFactory

factory){

return factory.rentACar();

}

}

Page 32: Design patterns with Kotlin

Abstract Factory - Java to Kotlininterface Car

class Audi : Car

class Mercedes : Car

class AudiFactory : AbstractFactory() {

override fun rentACar(): Car {

return Audi()

}

}

class MercedesFactory : AbstractFactory() {

override fun rentACar(): Car {

return Mercedes()

}

}

abstract class AbstractFactory {

abstract fun rentACar(): Car

companion object {

fun createFactory(factory: AbstractFactory): Car {

return factory.rentACar()

}

}

}

Page 33: Design patterns with Kotlin

Abstract Factory - Kotlininterface Car

class Audi : Car

class Mercedes : Car

class AudiFactory : AbstractFactory() {

override fun rentACar(): Car = Audi()

}

class MercedesFactory : AbstractFactory() {

override fun rentACar(): Car = Mercedes()

}

abstract class AbstractFactory {

abstract fun rentACar(): Car

companion object {

inline fun <reified T : Car> createFactory():

AbstractFactory = when (T::class) {

Audi::class -> AudiFactory()

Mercedes::class -> MercedesFactory()

else -> throw IllegalArgumentException()

}

}

}

Page 34: Design patterns with Kotlin

Abstract Factory - Kotlininterface Car

class Audi : Car

class Mercedes : Car

class AudiFactory : AbstractFactory() {

override fun rentACar(): Car = Audi()

}

class MercedesFactory : AbstractFactory() {

override fun rentACar(): Car = Mercedes()

}

abstract class AbstractFactory {

abstract fun rentACar(): Car

companion object {

inline fun <reified T : Car> createFactory():

AbstractFactory = when (T::class) {

Audi::class -> AudiFactory()

Mercedes::class -> MercedesFactory()

else -> throw IllegalArgumentException()

}

}

}

https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters

Page 35: Design patterns with Kotlin

Abstract Factory - Kotlinprivate final AbstractFactory createFactory() { Intrinsics.reifiedOperationMarker(4, "T"); KClass var2 = Reflection.getOrCreateKotlinClass(Car.class); AbstractFactory var10000; if(Intrinsics.areEqual(var2, Reflection.getOrCreateKotlinClass(Audi.class))) { var10000 = (AbstractFactory)(new AudiFactory()); } else { if(!Intrinsics.areEqual(var2, Reflection.getOrCreateKotlinClass(Mercedes.class))) { throw (Throwable)(new IllegalArgumentException()); }

var10000 = (AbstractFactory)(new MercedesFactory()); }

return var10000; }

abstract class AbstractFactory {

abstract fun rentACar(): Car

companion object {

inline fun <reified T : Car> createFactory():

AbstractFactory = when (T::class) {

Audi::class -> AudiFactory()

Mercedes::class -> MercedesFactory()

else -> throw IllegalArgumentException()

}

}

}

Page 36: Design patterns with Kotlin

Abstract Factory - GBU

- The Good: Encapsulates object creation in a relatively organized and type

safe way

- The Bad: Introduces more classes

- The Ugly: Overuse will introduce many class files

Page 37: Design patterns with Kotlin

-Builder- “Separate the construction of a complex object from its representation so that the same

construction process can create different representations.”

- Unlike Factory Method and Abstract Method doesn’t target polymorphism but tries to

minimize constructor overload.

- Uses another object to build the target object in steps.

- Very useful in creating new modified object from immutable objects.

Page 38: Design patterns with Kotlin

Builder - Javapublic class Person {

private String name;

private String surname;

private Person() {}

public String getName() {

return name;

}

public String getSurname() {

return surname;

}

public static class Builder { private Person personToBuild; Builder() { personToBuild = new Person(); } Person build() { Person builtPerson = personToBuild; personToBuild = new Person(); return builtPerson; } public Builder setName(String name) { this.personToBuild.name = name; return this; } public Builder setSurname(String surname) { this.personToBuild.surname = surname; return this; } }}

Page 39: Design patterns with Kotlin

Builder - Java to Kotlinclass Person

private constructor() {

var name: String? = null

private set

var surname: String? = null

private set

class Builder internal constructor() { private var personToBuild: Person? = null

init { personToBuild = Person() } internal fun build(): Person? { val builtPerson = personToBuild personToBuild = Person() return builtPerson } fun setName(name: String): Builder { this.personToBuild!!.name = name return this } fun setSurname(surname: String): Builder { this.personToBuild!!.surname = surname return this } }}

Page 40: Design patterns with Kotlin

Builder - Native Kotlinclass Person (val name: String?, val surname: String? = null)

The default value will be null if no argument is provided

Page 41: Design patterns with Kotlin

Builder - Kotlin Decompiledpublic final class Person { @Nullable private final String name; @Nullable private final String surname; @Nullable public final String getName() { return this.name; } @Nullable public final String getSurname() { return this.surname; } public Person(@Nullable String name, @Nullable String surname){ this.name = name; this.surname = surname; } public Person(String var1, String var2, int var3, DefaultConstructorMarker var4) { if((var3 & 1) != 0) { var1 = (String)null; } if((var3 & 2) != 0) { var2 = (String)null; } this(var1, var2); } public Person() { this((String)null, (String)null, 3,(DefaultConstructorMarker)null); }}

Page 42: Design patterns with Kotlin

Builder - GBU

- The Good: thanks to optionals, builder pattern is not needed

- The Bad: optionals are nice and much easier to code but may not be as self

explanatory as Builder Pattern (Kotlin’s Elvis operator)

- and The Ugly: you may still need to implement a builder in order to make

some libraries work

Page 43: Design patterns with Kotlin

Structural

Page 44: Design patterns with Kotlin

-Facade- “Provide a unified interface to a set of interfaces in a subsystem. Facade defines a

higher-level interface that makes the subsystem easier to use.”

- Hides the complex logic and provides an interface for the clients

- Typically used in APIs

- Good to hide unnecessary details from the caller

- Easy to change internal flow

- Some may argue really qualifies as a Pattern

Page 45: Design patterns with Kotlin

Facade - Javapublic class Facade { private String trim(String s){ return s.trim(); } private String changeCase(String s){ return s.toLowerCase(); } //Don’t do this at home! We are professionals :) private String doSomeAdditionalWork(String s){ return s.substring(0, s.length()-1).concat(s); } public String specialStringOps(String s){ s = doSomeAdditionalWork(s); s = changeCase(s); s = trim(s); s = doSomeAdditionalWork(s); return s; }}

Page 46: Design patterns with Kotlin

Facade - Java to Kotlinclass Facade { private fun trim(s: String): String { return s.trim { it <= ' ' } } private fun changeCase(s: String): String { return s.toLowerCase() } private fun doSomeAdditionalWork(s: String): String { return s.substring(0, s.length - 1) + s } fun specialStringOps(s: String): String { var s = s s = doSomeAdditionalWork(s) s = changeCase(s) s = trim(s) s = doSomeAdditionalWork(s) return s }}

Page 47: Design patterns with Kotlin

Facade - Kotlinclass Facade { private fun trim(s: String): String { return s.trim { it <= ' ' } } private fun changeCase(s: String): String { return s.toLowerCase() } private fun doSomeAdditionalWork(s: String): String { return s.substring(0, s.length - 1) + s } fun specialStringOps(s: String): String { var s = s s = doSomeAdditionalWork(s) s = changeCase(s) s = trim(s) s = doSomeAdditionalWork(s) return s }}

Page 48: Design patterns with Kotlin

Facade - GBU

- The Good: probably easiest pattern to implement

- The Bad: overuse may introduce unnecessary layers

- and The Ugly: arguably a pattern...

Page 49: Design patterns with Kotlin

Decorator- “Attach additional responsibilities to an object dynamically. Decorators provide a

flexible alternative to subclassing for extending functionality”

- Adds behavior to objects in runtime.

- More flexible and extensible than inheritance.

- Allows functionality to be divided between classes with unique areas of concern.

- My Favourite :)

Page 50: Design patterns with Kotlin

Decorator - Javapublic interface Pizza {

String makePizza();

}

public class BasePizza implements Pizza{

private String type;

@Override

public String makePizza() {

return type;

}

public BasePizza(String type){

this.type=type;

}

}

public class MeatTopping implements Pizza{

private String topping;

private Pizza pizza;

@Override public String makePizza() {

return pizza.makePizza() + " " +topping;

}

public MeatTopping(Pizza pizza, String topping){

this.pizza=pizza; this.topping=topping;

}

}

public class VeggieTopping implements Pizza{

private String topping;

private Pizza pizza;

@Override public String makePizza() {

return pizza.makePizza() + " " +topping;

}

public VeggieTopping(Pizza pizza, String topping){

this.pizza=pizza; this.topping=topping;

}

}

Page 51: Design patterns with Kotlin

Decorator - Java to Kotlininterface Pizza {

fun makePizza(): String

}

class BasePizza(private val type: String) : Pizza {

override fun makePizza(): String {

return type

}

}

class MeatTopping(private val pizza: Pizza,

private val topping: String) : Pizza {

override fun makePizza(): String {

return pizza.makePizza() + " " + topping

}

}

class VeggieTopping(private val pizza: Pizza,

private val topping: String) : Pizza {

override fun makePizza(): String {

return pizza.makePizza() + " " + topping

}

}

Page 52: Design patterns with Kotlin

Decorator - Kotlininterface Pizza {

fun makePizza(): String

}

class BasePizza(private val type: String) : Pizza {

override fun makePizza(): String {

return type

}

}

class MeatTopping(private val pizza: Pizza,

private val topping: String) : Pizza {

override fun makePizza(): String {

return pizza.makePizza() + " " + topping

}

}

class VeggieTopping(private val pizza: Pizza,

private val topping: String) : Pizza {

override fun makePizza(): String {

return pizza.makePizza() + " " + topping

}

}

Page 53: Design patterns with Kotlin

Decorator - GBU- The Good: unlike inheritance very easy to change behavior without breaking

legacy code.

- The Bad: ...

- and The Ugly: overuse will introduce an execution flow which is hard to follow.

Page 54: Design patterns with Kotlin

Adapter- “Convert the interface of a class into another interface clients expect. Adapter

lets classes work together that couldn't otherwise because of incompatible

interfaces.”

- Helps to reuse an existing piece of code in a new client or new piece of code

in a legacy client.

- Easy to implement/refactor

- Based on Delegation and Wrapper

Page 55: Design patterns with Kotlin

Adapter - Javapublic interface EUPlug {

void get220Volts(int electricity);

}

public class EUPlugImpl implements EUPlug{

public void get220Volts(int electricity){

System.out.println(electricity);

}

}

public interface USPlug implements USPlug{

void get110Volts(int electricity);

}

public class USPlugImpl {

public void get110Volts(int electricity){

System.out.println(electricity);

}

}

public class EUOutlet {

public EUOutlet(EUPlug plug){

plug.get220Volts(220);

}

}

public class VoltageAdapter implements EUPlug{

private USPlug plug;

public VoltageAdapter(USPlug plug) {

this.plug = plug;

}

@Override

public void get220Volts(int electricity) {

plug.get110Volts(electricity/2);

}

}

Page 56: Design patterns with Kotlin

Adapter - Java to Kotlininterface EUPlug {

fun get220Volts(electricity: Int)

}

class EUPlugImpl : EUPlug {

fun get220Volts(electricity: Int) {

println(electricity)

}

}

interface USPlug {

fun get110Volts(electricity: Int)

}

class USPlugImpl : USPlug {

fun get110Volts(electricity: Int) {

println(electricity)

}

}

class EUOutlet(plug: EUPlug) {

init {

plug.get220Volts(220)

}

}

class VoltageAdapter(private val plug: USPlug) : EUPlug {

override fun get220Volts(electricity: Int) {

plug.get110Volts(electricity / 2)

}

}

Page 57: Design patterns with Kotlin

Adapter - GBU

- The Good: Easy to implement and refactor (most of the time). Very readable

and doesn’t change the flow miraculously

- The Bad: Refactoring may require introduction of many new interfaces

depending on the design.

- and The Ugly: May help you to keep the legacy piece forever

Page 58: Design patterns with Kotlin

Behavioral

Page 59: Design patterns with Kotlin

- “Define a family of algorithms, encapsulate each one, and make them

interchangeable. Strategy lets the algorithm vary independently from clients

that use it.”

- Inject/use the right behavior for the job.

- Easy to change and add new behaviors.

- Works great with Dependency Injection

Strategy

Page 60: Design patterns with Kotlin

Strategy - Java

public interface Logger {

void logWarning(String log);

void logError(String log);

}

public class FileLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

public class ConsoleLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

Page 61: Design patterns with Kotlin

Strategy - Java

public interface Logger {

void logWarning(String log);

void logError(String log);

}

public class SomeClass{

Logger logger;

public SomeClass(Logger logger){

this.logger=logger;

logger.logWarning("This is a warning!");

}

}

public class FileLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

public class ConsoleLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

Page 62: Design patterns with Kotlin

Strategy - Java to Kotlin

interface Logger {

fun logWarning(log: String)

fun logError(log: String)

}

class ConsoleLogger : Logger {

override fun logWarning(log: String) {}

override fun logError(log: String) {}

}

class FileLogger : Logger {

override fun logWarning(log: String) {}

override fun logError(log: String) {}

}

Page 63: Design patterns with Kotlin

Strategy - GBU- The Good: Simple, easy to implement, open to add new behaviors without

breaking existing code.

- The Bad: Once the contract is set, it is impossible to change it without

breaking the code.

- The Ugly: Nothing really..

Page 64: Design patterns with Kotlin

-Command- “Encapsulate a request as an object, thereby letting you parameterize clients

with different requests, queue or log requests, and support undoable

operations.”

- Good for when you have an object attached to some event that’s being

passed, and you want specific behavior for each type of object, but don’t want

a lot of ugly logic for determining which class, and then calling a specific

method on that class.

- Typical use case for Interfaces

Page 65: Design patterns with Kotlin

Command - Classic Javapublic class CommandInJava { interface Commando { void execute(); }

public static class CommandWrapper { Object member;

CommandWrapper(Object member){ this.member = member; } }

static void orders(CommandWrapper c){ ((Commando)c.member).execute(); }

}

public static class Grunt implements Commando { @Override public void execute() { System.out.println("Grunt reporting in"); } }

public static void main(String[] args) { Commando grunt = new Grunt(); CommandWrapper g = new CommandWrapper(grunt); orders(g); }

Page 66: Design patterns with Kotlin

Command - Classic Java to Kotlinobject CommandFromJava { internal interface Commando { fun execute() }

class CommandWrapper internal constructor(internal var member: Any)

internal fun orders(c: CommandWrapper) { (c.member as Commando).execute() }

}

class Grunt : Commando { override fun execute() { println("Grunt reporting in") } }

@JvmStatic fun main(args: Array<String>) { val grunt = Grunt() val g = CommandWrapper(grunt) orders(g) }

Page 67: Design patterns with Kotlin

Command - Kotlinobject CommandInKotlin { fun orders(c: CommandWrapperKt) = (c.member as CommandoKt).execute()}

class CommandWrapperKt constructor(var member: Any)

interface CommandoKt { fun execute()}

class Grunt : CommandoKt { override fun execute() = println("Grunt reporting in")}

fun main(args: Array<String>) { val grunt = Grunt() val g = CommandWrapper(grunt) orders(g)}

Page 68: Design patterns with Kotlin

Command - Kotlin decompiledpublic final class CommandBytecode { public static final CommandBytecode INSTANCE = new CommandBytecode();

public interface CommandoBytecode { void execute(); }

public static final class CommandWrapperBytecode { private Object member;

public final Object getMember() { return this.member; }

public final void setMember(@NotNull Object var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.member = var1; }

public CommandWrapperBytecode(@NotNull Object member) { Intrinsics.checkParameterIsNotNull(member, "member"); this.member = member; } }// continued to the left ...

public static final void orders(@NotNull CommandWrapperBytecode c) { Intrinsics.checkParameterIsNotNull(c, "c"); Object var10000 = c.getMember(); if (var10000 == null) { throw new TypeCastException("null cannot be cast to non-null type CommandoBytecode"); } else { ((CommandoBytecode)var10000).execute(); }}

public static final class Grunt implements CommandoBytecode { public void execute() { String var1 = "Grunt reporting in"; System.out.println(var1); }}

Page 69: Design patterns with Kotlin

Command - GBU

- The Good: simplifies execution flow (fewer if’s), allows for more reuse of

code, still using static typing, implementation is separate

- The Bad: increases the number of classes, don’t get to use switch statements

as much?

- The Ugly: ...

Page 70: Design patterns with Kotlin

-Observer- “Define a one-to-many dependency between objects so that when one object

changes state, all its dependents are notified and updated automatically.”- Don’t call us, we call you!- Publisher-Subscriber- Java offers out of box solutions: Observable Interface, Listeners via inner

classes- Java EE offers another out of box solution: @Observable- 3rd party libraries: EventBus(es), RxJava (RxAndroid)

Page 71: Design patterns with Kotlin

Observer - Classic Javapublic class RadioChannel implements Observer {

@Override

public void update(Observable agency,

Object newsItem) {

if (agency instanceof Publisher) {

System.out.println((String)newsItem);

}

}

}

public interface Publisher {}

public class NewsAgency extends Observable

implements Publisher {

private List<Observer> channels =

new ArrayList<>();

public void addNews(String newsItem) {

notifyObservers(newsItem);

}

public void notifyObservers(String newsItem) {

for (Observer outlet : this.channels) {

outlet.update(this, newsItem);

}

}

public void register(Observer outlet) {

channels.add(outlet);

}

}

Page 72: Design patterns with Kotlin

Observer - Classic Java to Kotlinclass RadioChannel : Observer {

override fun update(agency: Observable,

newsItem: Any) {

if (agency is Publisher) {

println(newsItem as String)

}

}

}

interface Publisher

class NewsAgency : Observable(), Publisher {

private val channels = ArrayList<Observer>()

fun addNews(newsItem: String) {

notifyObservers(newsItem)

}

fun notifyObservers(newsItem: String) {

for (outlet in this.channels) {

outlet.update(this, newsItem)

}

}

fun register(outlet: Observer) {

channels.add(outlet)

}

}

Page 73: Design patterns with Kotlin

Observer - Kotlininterface Publisher{

fun onNews(news: String)

}

class RadioChannel : Publisher {

override fun onNews(newText: String) = println("News: $newText")

}

class NewsAgency {

var listener: Publisher? = null

var text: String by Delegates.observable("") { prop, old, new ->

listener?.onNews(new)

}

}

Returns a property delegate for a read/write property that calls a specified callback function when changed.

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/observable.html

Page 74: Design patterns with Kotlin

Observer - Kotlin decompiledpublic final class NewsAgency { static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(NewsAgency.class), "text", "getText()Ljava/lang/String;"))}; @Nullable private Publisher listener; @NotNull private final ReadWriteProperty text$delegate; @Nullable public final Publisher getListener() { return this.listener; } public final void setListener(@Nullable Publisher var1) { this.listener = var1; } @NotNull public final String getText() { return (String)this.text$delegate.getValue(this, $$delegatedProperties[0]); } public final void setText(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.text$delegate.setValue(this, $$delegatedProperties[0], var1); } public NewsAgency() { Delegates var1 = Delegates.INSTANCE; Object initialValue$iv = ""; ReadWriteProperty var5 = (ReadWriteProperty)(new NewsAgency$$special$$inlined$observable$1(initialValue$iv, initialValue$iv, this)); this.text$delegate = var5;}}public final class NewsAgency$$special$$inlined$observable$1 extends ObservableProperty { final Object $initialValue; final NewsAgency this$0;

public NewsAgency$$special$$inlined$observable$1(Object $captured_local_variable$1, Object $super_call_param$2, NewsAgency var3) { super($super_call_param$2); this.$initialValue = $captured_local_variable$1; this.this$0 = var3; } protected void afterChange(@NotNull KProperty property, Object oldValue, Object newValue) { Intrinsics.checkParameterIsNotNull(property, "property"); String var4 = (String)newValue; String var10001 = (String)oldValue; Publisher var10000 = this.this$0.getListener(); if(var10000 != null) { var10000.onNews(var4); }}}

Page 75: Design patterns with Kotlin

Observer - RxJavaObservable<News> newsObservable = Observable.create(emitter -> {

try {

List<News> newsList = getNews();

for (News news : newsList) {

emitter.onNext(news);

}

emitter.onComplete();

} catch (Exception e) {

emitter.onError(e);

}

});

Page 76: Design patterns with Kotlin

Observer - RxJava to Kotlin internal var newsObservable = Observable.create<News> { emitter ->

try {

val newsList = news

for (news in newsList) {

emitter.onNext(news)

}

emitter.onComplete()

} catch (e: Exception) {

emitter.onError(e)

}

}

Page 77: Design patterns with Kotlin

Observer - Kotlin Decompiled private Observable newsObservable = Observable.create((ObservableOnSubscribe)(new ObservableOnSubscribe() {

public final void subscribe(@NotNull ObservableEmitter emitter) {

Intrinsics.checkParameterIsNotNull(emitter, "emitter");

try {

List newsList = this.getNews();

Iterator var4 = newsList.iterator();

while(var4.hasNext()) {

News news = (News)var4.next();

emitter.onNext(news);

}

emitter.onComplete();

} catch (Exception var5) {

emitter.onError((Throwable)var5);

}

}

}));

Page 78: Design patterns with Kotlin

Observer - GBU

- The Good: Push (subscription) is much efficient than polling. Most

frameworks, libraries even programming models are based on this idea.

- The Bad: May introduce difficulty to follow execution flow & debug.

- and The Ugly: RxJava may not be very easy to learn.

Page 79: Design patterns with Kotlin

Dependency Injection

- Not a GoF pattern but probably the most famous one.- Inversion of Control or Dependency Injection- Inject the right resource instead of using the ‘new’ keyword in the client.- Very flexible in changing implementation or even behavior (inject mock for

unit testing).- Works great with Strategy, some even say DI is Strategy Pattern.- Many frameworks are available: CDI (any EE folks?), Spring, Guice, Play

framework, Salta, Glassfish HK2, Dagger...

Page 80: Design patterns with Kotlin

DI - Naive Java

public interface Logger {

void logWarning(String log);

void logError(String log);

}

public class SomeClass{

Logger logger;

public SomeClass(Logger logger){

this.logger=logger;

logger.logWarning("This is a warning!");

}

}

public class FileLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

public class ConsoleLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

Page 81: Design patterns with Kotlin

DI - Naive Java

public interface Logger {

void logWarning(String log);

void logError(String log);

}

public class SomeClass{

Logger logger;

public SomeClass(Logger logger){

this.logger=logger;

logger.logWarning("This is a warning!");

}

}

public class FileLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

public class ConsoleLogger implements Logger{

@Override

public void logWarning(String log) {}

@Override

public void logError(String log) {}

}

Who and how is this done?

Page 82: Design patterns with Kotlin

DI - Naive Java to Kotlin

interface Logger {

fun logWarning(log: String)

fun logError(log: String)

}

class ConsoleLogger : Logger {

override fun logWarning(log: String) {}

override fun logError(log: String) {}

}

class FileLogger : Logger {

override fun logWarning(log: String) {}

override fun logError(log: String) {}

}

Page 83: Design patterns with Kotlin

Dagger for DI- Fast dependency injection

- Standard javax.inject (JSR 330)

- Make your code easy to test

- Compile time code generation (No reflection)

- Complicated

- Hard to do for ongoing project

Page 84: Design patterns with Kotlin

Dagger 101

- @Module + @Provides: mechanism for providing dependencies.

- @Inject: mechanism for requesting dependencies.

- @Component: bridge between modules and injections

Page 85: Design patterns with Kotlin

DI - Simple Daggerclass CoffeeMaker {

@Inject Heater heater;

@Inject Pump pump;

...

}

@Component(modules = DripCoffeeModule.class)

interface CoffeeShop {

CoffeeMaker maker();

}

@Module

class DripCoffeeModule {

@Provides Heater provideHeater() {

return new ElectricHeater();

}

@Provides Pump providePump(Thermosiphon pump) {

return pump;

}

}

Page 86: Design patterns with Kotlin

DI - Simple Daggerclass CoffeeMaker {

@Inject Heater heater;

@Inject Pump pump;

...

}

@Component(modules = DripCoffeeModule.class)

interface CoffeeShop {

CoffeeMaker maker();

}

@Module

class DripCoffeeModule {

@Provides Heater provideHeater() {

return new ElectricHeater();

}

@Provides Pump providePump(Thermosiphon pump) {

return pump;

}

}

Module acts as a factory

Page 87: Design patterns with Kotlin

DI - Simple Daggerclass CoffeeMaker {

@Inject Heater heater;

@Inject Pump pump;

...

}

@Component(modules = DripCoffeeModule.class)

interface CoffeeShop {

CoffeeMaker maker();

}

@Module

class DripCoffeeModule {

@Provides Heater provideHeater() {

return new ElectricHeater();

}

@Provides Pump providePump(Thermosiphon pump) {

return pump;

}

}

Module acts as a factory

Component binds module to injection

Page 88: Design patterns with Kotlin

class CoffeeMaker {

@Inject

var heater: Heater? = null

@Inject

var pump: Pump? = null

//...

}

@Component(modules = DripCoffeeModule.class)

interface CoffeeShop {

CoffeeMaker maker();

}

DI - Simple Dagger to Kotlin@Module

class DripCoffeeModule {

@Provides

internal fun provideHeater(): Heater {

return ElectricHeater()

}

@Provides

internal fun providePump(pump: Thermosiphon): Pump {

return pump

}

}

Page 89: Design patterns with Kotlin

DI - GBU

- The Good: Let’s you to inject right resource for the right purpose magically.

Helps Unit Testing or using different resources for different environments.

- The Bad: If you are new to it, it might be too much magic.

- The Ugly: Getting understand the DI Frameworks may become even harder

as you dig in.

Page 90: Design patterns with Kotlin

TL;DR

- Patterns are collective wisdom of many smart developers.

- Existing solutions to repeating problems

- Overuse can be really harmful.

- With Kotlin, mostly it is easier and less lines of code

- Java to Kotlin tool works near perfect.

Page 91: Design patterns with Kotlin

Q & A@yenerm || @ejf_io

https://www.manning.com/books/kotlin-in-actionhttps://speakerdeck.com/jakewharton/generating-kotlin-code-kotlinconf-2017https://www.amazon.com/Professional-Java-EE-Design-Patterns/dp/111884341X