Upload
murat-yener
View
278
Download
0
Embed Size (px)
Citation preview
Design Patterns with Kotlin@ejf_io
@yenerm
#kotlinpatterns
@yenerm
Who are we?
@ejf_io
Disclaimer
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
“Object-oriented programming is an
exceptionally bad idea which could only have
originated in California”
-Edsger W. Dijkstra
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
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
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)
Some tools...
Java 2 KotlinIntelliJ > Code > Convert Java File to Kotlin File
Or simply paste Java code in a Kotlin file..
Kotlin 2 JavaIntelliJ > Tools > Kotlin > Show Kotlin Bytecode ⇒ Decompile
Before we start...
Written in Java
Written in Java,Converted to Kotlin
Written in Kotlin
Written in Kotlin,Decompiled to Java
GoF Patterns with Kotlin
Creational Patterns
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…)
-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++; }
}
Singleton - naive java to kotlinobject Singleton {
val instance = Singleton()
private var count: Int = 0
fun count(): Int {
return count++
}
}
Singleton - java with enumpublic enum Singleton {
INSTANCE;
private int count = 0;
public static int count(){
return count++;
}
}
Singleton - to Kotlinenum class Singleton {
INSTANCE;
private var count = 0
fun count(): Int {
return count++
}
}
Singleton - Native Kotlinobject Singleton {
var count: Int = 0
fun count() {
count++
}
}
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
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(); }
}
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..
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
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;
}
}
}
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
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
}
}
}
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
}
}
}
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.
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
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();
}
}
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()
}
}
}
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()
}
}
}
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
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()
}
}
}
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
-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.
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; } }}
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 } }}
Builder - Native Kotlinclass Person (val name: String?, val surname: String? = null)
The default value will be null if no argument is provided
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); }}
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
Structural
-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
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; }}
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 }}
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 }}
Facade - GBU
- The Good: probably easiest pattern to implement
- The Bad: overuse may introduce unnecessary layers
- and The Ugly: arguably a pattern...
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 :)
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;
}
}
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
}
}
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
}
}
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.
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
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);
}
}
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)
}
}
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
Behavioral
- “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
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) {}
}
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) {}
}
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) {}
}
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..
-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
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); }
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) }
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)}
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); }}
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: ...
-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)
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);
}
}
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)
}
}
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
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); }}}
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);
}
});
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)
}
}
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);
}
}
}));
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.
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...
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) {}
}
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?
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) {}
}
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
Dagger 101
- @Module + @Provides: mechanism for providing dependencies.
- @Inject: mechanism for requesting dependencies.
- @Component: bridge between modules and injections
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;
}
}
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
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
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
}
}
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.
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.
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