Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Gang of Four Patterns in Kotlin

Gang of Four Patterns in Kotlin

How would some of the most famous design patterns in computer science look like in Kotlin?
Instead of converting Java Code, I will try to find a simpler, easier or more idiomatic way to solve the same problem each pattern is solving.
by Lovis Möller
presented on December 12, 2017 @yelp

Kotlin User Group Hamburg

December 12, 2017
Tweet

More Decks by Kotlin User Group Hamburg

Other Decks in Programming

Transcript

  1. Patterns Structural Creational Behavioral Adapter Bridge Decorator Façade Flyweight Composite

    Proxy Factory method Abstract factory Singleton Builder Prototype Interpreter Template method Observer Visitor Iterator Command Memento Strategy Mediator State Chain of responsibility
  2. Patterns Structural Creational Behavioral Adapter Bridge Decorator Façade Flyweight Composite

    Proxy Factory method Abstract factory Singleton Builder Prototype Interpreter Template method Observer Visitor Iterator Command Memento Strategy Mediator State Chain of responsibility
  3. interface Discount { double discountedPrice(double raw); } public static class

    Customer { final String name; final double fee; final Discount discount; public Customer(String name, double fee, Discount discount) { this.name = name; this.fee = fee; this.discount = discount; } public double pricePerMonth() { return discount.discountedPrice(fee); } }
  4. interface Discount { double discountedPrice(double raw); } public static class

    Customer { final String name; final double fee; final Discount discount; public Customer(String name, double fee, Discount discount) { this.name = name; this.fee = fee; this.discount = discount; } public double pricePerMonth() { return discount.discountedPrice(fee); } }
  5. interface Discount { double discountedPrice(double raw); } public static class

    Customer { final String name; final double fee; final Discount discount; public Customer(String name, double fee, Discount discount) { this.name = name; this.fee = fee; this.discount = discount; } public double pricePerMonth() { return discount.discountedPrice(fee); } }
  6. interface Discount { double discountedPrice(double raw); } public static class

    NoDiscount implements Discount { @Override public double discountedPrice(double raw) { return raw; } }
  7. interface Discount { double discountedPrice(double raw); } public static class

    NoDiscount implements Discount { @Override public double discountedPrice(double raw) { return raw; } } public static class StudentDiscount implements Discount { @Override public double discountedPrice(double raw) { return 0.5 * raw; } }
  8. Customer student = new Customer("Ned", 10, new StudentDiscount()); Customer regular

    = new Customer("John", 10, new NoDiscount()); System.out.println(String.format("%s pays %.2f per month", student.name, student.pricePerMonth())); System.out.println(String.format("%s pays %.2f per month", regular.name, regular.pricePerMonth()));
  9. Customer student = new Customer("Ned", 10, new StudentDiscount()); Customer regular

    = new Customer("John", 10, new NoDiscount()); System.out.println(String.format("%s pays %.2f per month", student.name, student.pricePerMonth())); System.out.println(String.format("%s pays %.2f per month", regular.name, regular.pricePerMonth()));
  10. Customer student = new Customer("Ned", 10, new StudentDiscount()); Customer regular

    = new Customer("John", 10, new NoDiscount()); System.out.println(String.format("%s pays %.2f per month", student.name, student.pricePerMonth())); System.out.println(String.format("%s pays %.2f per month", regular.name, regular.pricePerMonth()));
  11. typealias Discount = (Double)  Double class Customer(val name: String,

    val fee: Double, val discount: Discount) { fun pricePerMonth() = discount(fee) }
  12. typealias Discount = (Double)  Double class Customer(val name: String,

    val fee: Double, val discount: Discount) { fun pricePerMonth() = discount(fee) } val studentDiscount = { raw: Double  raw/2 } val noDiscount = { raw: Double  raw }
  13. typealias Discount = (Double)  Double class Customer(val name: String,

    val fee: Double, val discount: Discount) { fun pricePerMonth() = discount(fee) } val studentDiscount = { raw: Double  raw/2 } val noDiscount = { raw: Double  raw } val student = Customer("Ned", 10.0, studentDiscount) val regular = Customer("John", 10.0, noDiscount) println("${student.name} pays %.2f per month".format(student.pricePerMonth())) println("${regular.name} pays %.2f per month".format(regular.pricePerMonth()))
  14. typealias Discount = (Double)  Double class Customer(val name: String,

    val fee: Double, val discount: Discount) { fun pricePerMonth() = discount(fee) } val studentDiscount = { raw: Double  raw/2 } val noDiscount = { raw: Double  raw } val student = Customer("Ned", 10.0, studentDiscount) val regular = Customer("John", 10.0, noDiscount) println("${student.name} pays %.2f per month".format(student.pricePerMonth())) println("${regular.name} pays %.2f per month".format(regular.pricePerMonth()))
  15. typealias Discount = (Double)  Double class Customer(val name: String,

    val fee: Double, val discount: Discount) { fun pricePerMonth() = discount(fee) } val studentDiscount = { raw: Double  raw/2 } val noDiscount = { raw: Double  raw } val student = Customer("Ned", 10.0, studentDiscount) val regular = Customer("John", 10.0, noDiscount) println("${student.name} pays %.2f per month".format(student.pricePerMonth())) println("${regular.name} pays %.2f per month".format(regular.pricePerMonth()))
  16. Builder Separate the construction of a complex object from its

    representation so that the same construction process can create different representations
  17. public interface CarBuilder { void setDoors(int doors); void setColor(String color);

    Car build(); } public static CarBuilder newBuilder(){ … }
  18. public interface CarBuilder { void setDoors(int doors); void setColor(String color);

    Car build(); } public final class Car { private int doors; private String color; public Car(int doors, String color) { this.doors = doors; this.color = color; } } public static CarBuilder newBuilder(){ … }
  19. class Car(var color: String = “red”, var doors = 3,

    ) val car = Car( color = “green”, doors = 5,  )
  20. val car = Car().apply { color = "yellow" doors =

    5 } class Car() { var color: String = "red" var doors = 3 }
  21. class Car() { var color: String = "red" var doors

    = 3 } val config: Car.()  Unit = { color = "yellow" doors = 5 }
  22. class Car() { var color: String = "red" var doors

    = 3 } val config: Car.()  Unit = { color = "yellow" doors = 5 } val car = Car().apply(config)
  23. class Car() { var color: String = "red" var doors

    = 3 } val config: Car.()  Unit = { color = "yellow" doors = 5 } val car = Car() .apply(COBALT_BLUE) .apply(LEATHER_PACKAGE) .apply(TIRES_18_INCH)
  24. public class Dictionary { private static final Dictionary INSTANCE =

    new Dictionary(); private Dictionary() { … } public static Dictionary get() { return INSTANCE; } public void addWord(String word, String definition){ … } public String getDefinition(String word) { … } }
  25. public class Dictionary { private static final Dictionary INSTANCE =

    new Dictionary(); private Dictionary() { … } public static Dictionary get() { return INSTANCE; } public void addWord(String word, String definition){ … } public String getDefinition(String word) { … } }
  26. public class Dictionary { private static final Dictionary INSTANCE =

    new Dictionary(); private Dictionary() { … } public static Dictionary get() { return INSTANCE; } public void addWord(String word, String definition){ … } public String getDefinition(String word) { … } }
  27. public class Dictionary { private static final Dictionary INSTANCE =

    new Dictionary(); private Dictionary() { … } public static Dictionary get() { return INSTANCE; } public void addWord(String word, String definition){ … } public String getDefinition(String word) { … } }
  28. object Dictionary { fun addWord(word: String, definition: String) { …

    } fun getDefinition(word: String): String { … } }
  29. Prototype Specify the kinds of objects to create using a

    prototypical instance, and create new objects by copying this prototype
  30. /** * A class implements the <code>Cloneable</code> interface to *

    indicate to the {@link java.lang.Object#clone()} method that it * is legal for that method to make a * field-for-field copy of instances of that class. * <p> * Invoking Object's clone method on an instance that does not implement the * <code>Cloneable</code> interface results in the exception * <code>CloneNotSupportedException</code> being thrown. * <p> * By convention, classes that implement this interface should override * <tt>Object.clone</tt> (which is protected) with a public method. * See {@link java.lang.Object#clone()} for details on overriding this * method. * <p> * Note that this interface does <i>not</i> contain the <tt>clone</tt> method. * Therefore, it is not possible to clone an object merely by virtue of the * fact that it implements this interface. Even if the clone method is invoked * reflectively, there is no guarantee that it will succeed. * */ public interface Cloneable { }
  31. /** * A class implements the <code>Cloneable</code> interface to *

    indicate to the {@link java.lang.Object#clone()} method that it * is legal for that method to make a * field-for-field copy of instances of that class. * <p> * Invoking Object's clone method on an instance that does not implement the * <code>Cloneable</code> interface results in the exception * <code>CloneNotSupportedException</code> being thrown. * <p> * By convention, classes that implement this interface should override * <tt>Object.clone</tt> (which is protected) with a public method. * See {@link java.lang.Object#clone()} for details on overriding this * method. * <p> * Note that this interface does <i>not</i> contain the <tt>clone</tt> method. * Therefore, it is not possible to clone an object merely by virtue of the * fact that it implements this interface. Even if the clone method is invoked * reflectively, there is no guarantee that it will succeed. * */ public interface Cloneable { } Clone is broken
 http://www.artima.com/intv/bloch13.html
  32. data class EMail( var recipient: String, var subject: String?, var

    message: String? ) val mail = EMail("[email protected]", "Hello", "Don't know what to write.")
  33. data class EMail( var recipient: String, var subject: String?, var

    message: String? ) val mail = EMail("[email protected]", "Hello", "Don't know what to write.") val copy1 = mail.copy(recipient = "[email protected]")
  34. data class EMail( var recipient: String, var subject: String?, var

    message: String? ) val mail = EMail("[email protected]", "Hello", "Don't know what to write.") val copy1 = mail.copy(recipient = "[email protected]") val copy2 = mail.copy(recipient = "[email protected]", subject = "Re: Hello")
  35. data class EMail( var recipient: String, var subject: String?, var

    message: String? ) val mail = EMail("[email protected]", "Hello", "Don't know what to write.") val copy1 = mail.copy(recipient = "[email protected]") val copy2 = mail.copy(recipient = "[email protected]", subject = "Re: Hello") val copy3 = mail.copy()
  36. Iterator Provide a way to access the elements of an

    aggregate object sequentially without exposing its underlying representation.
  37. public class Sentence { private final List<String> words; public Sentence(List<String>

    words) { this.words = words; } public List<String> getWords() { return words; } }
  38. public class Sentence { private final List<String> words; public Sentence(List<String>

    words) { this.words = words; } public List<String> getWords() { return words; } } Sentence sentence = new Sentence(words); for (String word : sentence.getWords()) { // }
  39. public class Sentence { private final List<String> words; public Sentence(List<String>

    words) { this.words = words; } public List<String> getWords() { return words; } } Sentence sentence = new Sentence(words); for (String word : sentence.getWords()) { //not so cool }
  40. public static class Sentence { … } public class IterableSentence

    extends Sentence implements Iterable<String> { public IterableSentence(List<String> words) { super(words); } @Override public Iterator<String> iterator() { return getWords().iterator(); } }
  41. public static class Sentence { … } public class IterableSentence

    extends Sentence implements Iterable<String> { public IterableSentence(List<String> words) { super(words); } @Override public Iterator<String> iterator() { return getWords().iterator(); } } Sentence sentence = new IterableSentence(words);
 for (String word : sentence) { //better }
  42. class OtherSentence( private val words: List<String> ): Iterable<String> by words

    val otherSentence = OtherSentence(words) for(word in otherSentence){ … }
  43. class OtherSentence( private val words: List<String> ): Iterable<String> by words

    val otherSentence = OtherSentence(words) for(word in otherSentence){ … } otherSentence.forEach { word  … } if(otherSentence.contains(“X-Mas”)){ … }
  44. Visitor Represent an operation to be performed on the elements

    of an object structure. A Visitor lets you define a new operation without changing the classes of the elements on which it operates
  45. interface Shape { <T> T accept(ShapeVisitor<T> visitor); } public static

    class Square implements Shape { final double side; public Square(double side) { this.side = side; } @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visit(this); } } public static class Circle implements Shape { final double radius; public Circle(double radius) { this.radius = radius; } @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visit(this); } } public static class Rectangle implements Shape { final double width; final double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visit(this); } } interface ShapeVisitor<T> { T visit(Square element); T visit(Circle element); T visit(Rectangle element); } public static class AreaVisitor implements ShapeVisitor<Double> { public Double visit(Square element) { return element.side * element.side; } public Double visit(Circle element) { return Math.PI * element.radius * element.radius; } public Double visit(Rectangle element) { return element.height * element.width; } } public static class PerimeterVisitor implements ShapeVisitor<Double> { public Double visit(Square element) { return 4 * element.side; } public Double visit(Circle element) { return 2 * Math.PI * element.radius; } public Double visit(Rectangle element) { return (2 * element.height + 2 * element.width); } }
  46. interface Shape { <T> T accept(ShapeVisitor<T> visitor); } public static

    class Square implements Shape { final double side; public Square(double side) { this.side = side; } @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visit(this); } } public static class Circle implements Shape { final double radius; public Circle(double radius) { this.radius = radius; } @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visit(this); } } public static class Rectangle implements Shape { final double width; final double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visit(this); } } interface ShapeVisitor<T> { T visit(Square element); T visit(Circle element); T visit(Rectangle element); } public static class AreaVisitor implements ShapeVisitor<Double> { public Double visit(Square element) { return element.side * element.side; } public Double visit(Circle element) { return Math.PI * element.radius * element.radius; } public Double visit(Rectangle element) { return element.height * element.width; } } public static class PerimeterVisitor implements ShapeVisitor<Double> { public Double visit(Square element) { return 4 * element.side; } public Double visit(Circle element) { return 2 * Math.PI * element.radius; } public Double visit(Rectangle element) { return (2 * element.height + 2 * element.width); } } “Elements” “Visitors”
  47. sealed class Shape() class Square(val side: Double) : Shape() class

    Circle(val radius: Double) : Shape() class Rectangle(val width: Double, val height: Double) : Shape()
  48. sealed class Shape() class Square(val side: Double) : Shape() class

    Circle(val radius: Double) : Shape() class Rectangle(val width: Double, val height: Double) : Shape() val perimeterVisitor = { shape: Shape  when (shape) { is Rectangle  2 * shape.height + 2 * shape.width is Circle  2 * Math.PI * shape.radius is Square  4 * shape.side } }
  49. sealed class Shape() class Square(val side: Double) : Shape() class

    Circle(val radius: Double) : Shape() class Rectangle(val width: Double, val height: Double) : Shape() val perimeterVisitor = { shape: Shape  when (shape) { is Rectangle  2 * shape.height + 2 * shape.width is Circle  2 * Math.PI * shape.radius is Square  4 * shape.side } } val figures = listOf(Circle(4.0), Square(5.0), Rectangle(6.0, 7.0)) val totalArea = figures.sumByDouble { perimeterVisitor(it) }
  50. sealed class Shape() class Square(val side: Double) : Shape() class

    Circle(val radius: Double) : Shape() class Rectangle(val width: Double, val height: Double) : Shape() val perimeterVisitor = { shape: Shape  when (shape) { is Rectangle  2 * shape.height + 2 * shape.width is Circle  2 * Math.PI * shape.radius else  } } val figures = listOf(Circle(4.0), Square(5.0), Rectangle(6.0, 7.0)) val totalArea = figures.sumByDouble { perimeterVisitor(it) }
  51. sealed class Shape() class Square(val side: Double) : Shape() class

    Circle(val radius: Double) : Shape() class Rectangle(val width: Double, val height: Double) : Shape() val perimeterVisitor = { shape: Shape  when (shape) { is Rectangle  2 * shape.height + 2 * shape.width is Circle  2 * Math.PI * shape.radius } } val figures = listOf(Circle(4.0), Square(5.0), Rectangle(6.0, 7.0)) val totalArea = figures.sumByDouble { perimeterVisitor(it) } ‘when’ expression must be exhaustive
  52. sealed class Shape() class Square(val side: Double) : Shape() class

    Circle(val radius: Double) : Shape() class Rectangle(val width: Double, val height: Double) : Shape() val perimeterVisitor = { shape: Shape  when (shape) { is Rectangle  2 * shape.height + 2 * shape.width is Circle  2 * Math.PI * shape.radius is Square  4 * shape.side } } val figures = listOf(Circle(4.0), Square(5.0), Rectangle(6.0, 7.0)) val totalArea = figures.sumByDouble { perimeterVisitor(it) }
  53. public interface Text { void draw(); } public class PrintedText

    implements Text { … } public abstract class TextEffect implements Text { protected Text decorated; … }
  54. public interface Text { void draw(); } public class PrintedText

    implements Text { … } public abstract class TextEffect implements Text { protected Text decorated; … } public class Background extends TextEffect { public Background(Text decorated) { super(decorated); } @Override public void draw() { … decorated.draw(); } … }
  55. public interface Text { void draw(); } public class PrintedText

    implements Text { … } public abstract class TextEffect implements Text { protected Text decorated; … } public class Background extends TextEffect { public Background(Text decorated) { super(decorated); } @Override public void draw() { … decorated.draw(); } … }
  56. new Background(
 new PrintedText("With Background")
 ).draw(); new Background( new Underline(

    new PrintedText(“Underlined with Background”) ) ).draw(); new Background( new Underline( new Blinking( new PrintedText(“Underlined with Background, that blinks!”) ) ) ).draw();
  57. interface Text { fun draw() } class DefaultText(val text: String)

    : Text { override fun draw() { … } } fun Text.underline(decorated: Text.()  Unit) { … this.decorated() … }
  58. interface Text { fun draw() } class DefaultText(val text: String)

    : Text { override fun draw() { … } } fun Text.underline(decorated: Text.()  Unit) { … this.decorated() … }
  59. interface Text { fun draw() } class DefaultText(val text: String)

    : Text { override fun draw() { … } } fun Text.underline(decorated: Text.()  Unit) { … this.decorated() … }
  60. interface Text { fun draw() } class DefaultText(val text: String)

    : Text { override fun draw() { … } } fun Text.underline(decorated: Text.()  Unit) { //decorate this.decorated() //decorate }
  61. interface Text { fun draw() } class DefaultText(val text: String)

    : Text { override fun draw() { … } } fun Text.underline(decorated: Text.()  Unit) { //decorate this.decorated() //decorate } fun Text.background(decorated: Text.()  Unit) { //decorate this.decorated() //decorate }
  62. Decorator…? The Decorator pattern as demonstrated in GoF is just

    a very convoluted way to achieve functions composition. 
 ~ Mario Fusco
  63. Function composition f(x) g(x) h(x) = (f ∘ g)(x)
 …made

    simple! (But probably not really correct. Sorry, not sorry, mathematicians )
  64. Function composition f(x) g(x) h(x) = (f ∘ g)(x)
 =

    g(f(x)) …made simple! (But probably not really correct. Sorry, not sorry, mathematicians )
  65. Function composition f(x) = x + 1 g(x) = x2

    h(x) = (f ∘ g)(x)
 = g(f(x)) = (x + 1)2 …made simple! (But probably not really correct. Sorry, not sorry, mathematicians )
  66. Function composition f(x) = x + 1 g(x) = x2

    h(x) = (f ∘ g)(x)
 = g(f(x)) = (x + 1)2 …made simple! (But probably not really correct. Sorry, not sorry, mathematicians ) fun h(x: Int) = g(f(x))

  67. fun underline(text: String) = … fun background(text: String) = …

    val text = underline(background(“Functional?”))
  68. fun underline(text: String): String = … fun background(text: String): String

    = … val text = underline(background(“Functional?”))
  69. fun underline(text: String): String = … fun background(text: String): String

    = … val text: String = underline(background(“Functional?”))
  70. fun underline(text: String): String = … fun background(text: String): String

    = … val text: String = underline(background(“Functional?”))
  71. fun underline(text: String): String = … fun background(text: String): String

    = … fun combine(f: (String)  String, g: (String)  String):  { return  }
  72. fun underline(text: String): String = … fun background(text: String): String

    = … fun combine(f: (String)  String, g: (String)  String):  { return { x  g(f(x)) } }
  73. fun underline(text: String): String = … fun background(text: String): String

    = … fun combine(f: (String)  String, g: (String)  String): (String)  String { return { x  g(f(x)) } }
  74. fun underline(text: String): String = … fun background(text: String): String

    = … fun combine(f: (String)  String, g: (String)  String): (String)  String { return { x  g(f(x)) } } val h = combine( underline, background) val text = h(“Functional?”)
  75. fun underline(text: String): String = … fun background(text: String): String

    = … fun combine(f: (String)  String, g: (String)  String): (String)  String { return { x  g(f(x)) } }
  76. fun underline(text: String): String = … fun background(text: String): String

    = … fun ((String)  String).combine(g: (String)  String): (String)  String { return { x  g(this(x)) } }
  77. fun underline(text: String): String = … fun background(text: String): String

    = … fun ((String)  String).combine(g: (String)  String): (String)  String { return { x  g(this(x)) } } val h = underline.combine( background)
  78. fun underline(text: String): String = … fun background(text: String): String

    = … infix fun ((String)  String).then(g: (String)  String): (String)  String { return { x  g(this(x)) } } val h = underline.combine( background)
  79. fun underline(text: String): String = … fun background(text: String): String

    = … infix fun ((String)  String).then(g: (String)  String): (String)  String { return { x  g(this(x)) } } val h = underline combine background
  80. fun underline(text: String): String = … fun background(text: String): String

    = … infix fun ((String)  String).then(g: (String)  String): (String)  String { return { x  g(this(x)) } } val h = underline combine background val decoratedText = h(“Functional!?”)
  81. infix fun <P1, IP, R> ((P1)  IP).combine(f: (IP) 

    R): (P1)  R { return { p1: P1  f(this(p1)) } }
  82. infix fun <P1, IP, R> ((P1)  IP).combine(f: (IP) 

    R): (P1)  R { return { p1: P1  f(this(p1)) } }