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

Design Patterns and the happy dev friends

Design Patterns and the happy dev friends

This deck is a journey through the Design Patterns, used in a talk kept with Sebastiano Gottardo at Droidcon IT 2017

Roberto Orgiu

April 06, 2017
Tweet

More Decks by Roberto Orgiu

Other Decks in Technology

Transcript

  1. DESIGN PATTERNS AND THE HAPPY DEV FRIENDS WHY SHOULD WE

    USE THEM? ▸ Almost ready-to-use solutions ▸ Generic ▸ Increases decoupling ▸ Favors testing ▸ Android already uses a bunch of them
  2. ANDROIDNE.WS Hey, I have news! Oh-hi Android news! MOAR news!

    Such Androids! I’m no longer interested!
  3. OBSERVER/OBSERVABLE OBSERVER interface Observer<T> { fun onDataChanged(data : T) }

    class SimpleObserver : Observer<Int> { override fun onDataChanged(data : Int) { … } }
  4. OBSERVER/OBSERVABLE OBSERVABLE interface Observable<T> { fun addObserver(observer : Observer<T>) }

    class SimpleObservable : Observable<T> { override fun addObserver(observer : Observer<T>) { observers.add(observer) } fun doThings() { val d = 3 observers.forEach { it.onDataChanged(d) } } }
  5. OBSERVER/OBSERVABLE HOW TO USE IT val observer1 = SimpleObserver() val

    observer2 = SimpleObserver() val observable = SimpleObservable() observable.addObserver(observer1) observable.addObserver(observer2) … observable.doThings()
  6. STATIC FACTORY METHOD FACTORY class Robot private constructor(private val serialNumber:

    Int) { companion object { var serialNumber: Int = 0 fun createRobot(): Robot = Robot(serialNumber++) } } // How to use it val someRobot = Robot.createRobot()
  7. STATIC FACTORY METHOD FACTORY class Robot private constructor(private val serialNumber:

    Int) { companion object { var serialNumber: Int = 0 fun createRobot(): Robot = Robot(serialNumber++) } } // How to use it val someRobot = Robot.createRobot()
  8. STATIC FACTORY METHOD FACTORY class Robot private constructor(private val serialNumber:

    Int) { companion object { var serialNumber: Int = 0 fun createRobot(): Robot = Robot(serialNumber++) } } // How to use it val someRobot = Robot.createRobot()
  9. STATIC FACTORY METHOD FACTORY class Robot private constructor(private val serialNumber:

    Int) { companion object { var serialNumber: Int = 0 fun createRobot(): Robot = Robot(serialNumber++) } } // How to use it val someRobot = Robot.createRobot()
  10. STATIC FACTORY METHOD FACTORY class Robot private constructor(private val serialNumber:

    Int) { companion object { var serialNumber: Int = 0 fun createRobot(): Robot = Robot(serialNumber++) } } // How to use it val someRobot = Robot.createRobot()
  11. STATIC FACTORY METHOD FACTORY class Robot private constructor(private val serialNumber:

    Int) { companion object { var serialNumber: Int = 0 fun createRobot(): Robot = Robot(serialNumber++) } } // How to use it val someRobot = Robot.createRobot()
  12. STATIC FACTORY ‣ Not really a pattern ‣ Controls the

    creation of objects ‣ Widely spread across Java/Android Calendar.getInstance() String.format() SomeFragment.newInstance() ಠ_ಠ
  13. FACTORY PATTERN FACTORY interface Robot class Nestor4_Factory : RobotFactory {

    override fun createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } interface RobotFactory { fun createRobot(): Robot }
  14. FACTORY PATTERN FACTORY interface Robot class Nestor4_Factory : RobotFactory {

    override fun createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } interface RobotFactory { fun createRobot(): Robot }
  15. FACTORY PATTERN FACTORY interface Robot class Nestor4_Factory : RobotFactory {

    override fun createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } interface RobotFactory { fun createRobot(): Robot }
  16. FACTORY PATTERN FACTORY interface Robot class Nestor4_Factory : RobotFactory {

    override fun createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } interface RobotFactory { fun createRobot(): Robot }
  17. FACTORY PATTERN FACTORY interface Robot class Nestor4_Factory : RobotFactory {

    override fun createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } interface RobotFactory { fun createRobot(): Robot }
  18. FACTORY PATTERN HOW TO USE IT // U.S. Robots and

    Mechanical Men class USR { private val factory : RobotFactory = Nestor4_Factory() fun produceRobots(quantity: Int): List<Robot> = List(quantity) { factory.createRobot() } } fun main(args: Array<String>) { val usr = USR() // will produce a thousand robots usr.produceRobots(1000) }
  19. FACTORY PATTERN HOW TO USE IT // U.S. Robots and

    Mechanical Men class USR { private val factory : RobotFactory = Nestor4_Factory() fun produceRobots(quantity: Int): List<Robot> = List(quantity) { factory.createRobot() } } fun main(args: Array<String>) { val usr = USR() // will produce a thousand robots usr.produceRobots(1000) }
  20. FACTORY PATTERN HOW TO USE IT // U.S. Robots and

    Mechanical Men class USR { private val factory : RobotFactory = Nestor4_Factory() fun produceRobots(quantity: Int): List<Robot> = List(quantity) { factory.createRobot() } } fun main(args: Array<String>) { val usr = USR() // will produce a thousand robots usr.produceRobots(1000) }
  21. FACTORY PATTERN HOW TO USE IT // U.S. Robots and

    Mechanical Men class USR { private val factory : RobotFactory = Nestor4_Factory() fun produceRobots(quantity: Int): List<Robot> = List(quantity) { factory.createRobot() } } fun main(args: Array<String>) { val usr = USR() // will produce a thousand robots usr.produceRobots(1000) }
  22. FACTORY PATTERN HOW TO USE IT // U.S. Robots and

    Mechanical Men class USR { private val factory : RobotFactory = Nestor4_Factory() fun produceRobots(quantity: Int): List<Robot> = List(quantity) { factory.createRobot() } } fun main(args: Array<String>) { val usr = USR() // will produce a thousand robots usr.produceRobots(1000) }
  23. FACTORY PATTERN FACTORY interface Robot class Nestor4_Factory : RobotFactory {

    fun createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } interface RobotFactory { fun createRobot(): Robot }
  24. FACTORY PATTERN FACTORY class Nestor4_Factory : RobotFactory { fun createRobot():

    Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } class Nestor5_Factory : RobotFactory { fun createRobot(): Robot = Nestor5_Robot() private class Nestor5_Robot: Robot }
  25. FACTORY PATTERN FACTORY class Nestor4_Factory : RobotFactory { @Suppress(“unused”) fun

    createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } class Nestor5_Factory : RobotFactory { fun createRobot(): Robot = Nestor5_Robot() private class Nestor5_Robot: Robot }
  26. FACTORY PATTERN HOW TO USE IT // U.S. Robots and

    Mechanical Men class USR { private val factory : RobotFactory = Nestor4_Factory() fun produceRobots(quantity: Int): List<Robot> = List(quantity) { factory.createRobot() } } fun main(args: Array<String>) { val usr = USR() // will produce a thousand robots usr.produceRobots(1000) }
  27. FACTORY PATTERN HOW TO USE IT // U.S. Robots and

    Mechanical Men class USR { private val factory : RobotFactory = Nestor5_Factory() fun produceRobots(quantity: Int): List<Robot> = List(quantity) { factory.createRobot() } } fun main(args: Array<String>) { val usr = USR() // will still produce a thousand robots usr.produceRobots(1000) }
  28. FACTORY ‣ Creational ‣ Decoupling ‣ Inheritance Define an interface

    for creating an object, but let the classes which implement the interface decide which class to instantiate.
  29. FACTORY ‣ Editable.Factory ‣ Context.getSystemService() ‣ LayoutInflater.Factory Define an interface

    for creating an object, but let the classes which implement the interface decide which class to instantiate.
  30. BUILDER PATTERN INTERFACES // just a car interface Car //

    boost interface Boost object NOS: Boost // color of the exterior interface BodyColor object Orange: BodyColor object Blue: BodyColor // engine interface Engine object StockEngine: Engine object SuperPumpedEngine: Engine
  31. BUILDER PATTERN TARGET class QuarterMileCar private constructor( val boost: Boost?

    = null, val color: BodyColor = Orange, val engine: Engine = StockEngine ) : Car { // Builder goes here }
  32. BUILDER PATTERN TARGET class QuarterMileCar private constructor( val boost: Boost?

    = null, val color: BodyColor = Orange, val engine: Engine = StockEngine ) : Car { // Builder goes here }
  33. BUILDER PATTERN BUILDER class Builder { private var boost: Boost?

    = null private var color: BodyColor = Orange private var engine: Engine = StockEngine fun withBoost(boost: Boost): Builder { this.boost = boost return this } fun withColor(color: BodyColor): Builder { this.color = color return this } fun withEngine(engine: Engine): Builder { this.engine = engine return this } fun build(): QuarterMileCar = QuarterMileCar(boost, color, engine) }
  34. BUILDER PATTERN BUILDER class Builder { private var boost: Boost?

    = null private var color: BodyColor = Orange private var engine: Engine = StockEngine fun withBoost(boost: Boost): Builder { this.boost = boost return this } fun withColor(color: BodyColor): Builder { this.color = color return this } fun withEngine(engine: Engine): Builder { this.engine = engine return this } fun build(): QuarterMileCar = QuarterMileCar(boost, color, engine) }
  35. FACTORY PATTERN HOW TO USE IT fun main(args: Array<String>) {

    val supra = QuarterMileCar.Builder() .withBoost(NOS) .withColor(Orange) .withEngine(SuperPumpedEngine) .build() }
  36. FACTORY PATTERN HOW TO USE IT fun main(args: Array<String>) {

    val supra = QuarterMileCar.Builder() .withBoost(NOS) .withColor(Blue) .withEngine(SuperPumpedEngine) .build() }
  37. FACTORY PATTERN HOW TO USE IT fun main(args: Array<String>) {

    val s2000 = QuarterMileCar.Builder() .withEngine(SuperPumpedEngine) .build() }
  38. BUILDER PATTERN BUILDER class Builder { private var boost: Boost?

    = null private var color: BodyColor = Orange private var engine: Engine = StockEngine fun withBoost(boost: Boost): Builder { this.boost = boost return this } fun withColor(color: BodyColor): Builder { this.color = color return this } fun withEngine(engine: Engine): Builder { this.engine = engine return this } fun build(): QuarterMileCar = QuarterMileCar(boost, color, engine) }
  39. BUILDER PATTERN BUILDER <3 KOTLIN class QuarterMileCar private constructor( //

    constructor params ) : Car { class Builder { private var boost: Boost? = null private var color: BodyColor = Orange private var engine: Engine = StockEngine fun withBoost(boost: Boost) = apply { this.boost = boost } fun withColor(color: BodyColor) = apply { this.color = color } fun withEngine(engine: Engine) = apply { this.engine = engine } fun build() = QuarterMileCar(boost, color, engine) } }
  40. BUILDER ‣ Decoupling/Encapsulation ‣ Step-by-step control ‣ Immutability! The intent

    of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.
  41. BUILDER ‣ [insert_library_from_Square_here] ‣ AlertDialog.Builder ‣ … The intent of

    the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.
  42. CHAIN OF RESPONSIBILITY when(x) {
 1 -> doThis()
 2 ->

    doThat()
 else -> crashPainfully()
 }
  43. CHAIN OF RESPONSIBILITY DRAWBACKS ▸ Execute more than one branch

    ▸ Testing ▸ Complex conditions ▸ Possible violation of SRP
  44. CHAIN OF RESPONSIBILITY BASICS abstract class Base<T> {
 lateinit var

    next : Base<T>
 
 fun onEvent(data : T) {
 if (canManage(data)) {
 doManage(data)
 } else {
 next.onEvent(data)
 }
 }
 
 abstract fun canManage(data: T): Boolean
 abstract fun doManage(data: T)
 }
  45. CHAIN OF RESPONSIBILITY BASICS abstract class Base<T> {
 lateinit var

    next : Base<T>
 
 fun onEvent(data : T) {
 if (canManage(data)) {
 doManage(data)
 } else {
 next.onEvent(data)
 }
 }
 
 abstract fun canManage(data: T): Boolean
 abstract fun doManage(data: T)
 }
  46. CHAIN OF RESPONSIBILITY BASICS abstract class Base<T> {
 lateinit var

    next : Base<T>
 
 fun onEvent(data : T) {
 if (canManage(data)) {
 doManage(data)
 } else {
 next.onEvent(data)
 }
 }
 
 abstract fun canManage(data: T): Boolean
 abstract fun doManage(data: T)
 }
  47. CHAIN OF RESPONSIBILITY BASICS abstract class Base<T> {
 lateinit var

    next : Base<T>
 
 fun onEvent(data : T) {
 if (canManage(data)) {
 doManage(data)
 } else {
 next.onEvent(data)
 }
 }
 
 abstract fun canManage(data: T): Boolean
 abstract fun doManage(data: T)
 }
  48. CHAIN OF RESPONSIBILITY BASICS abstract class Base<T> {
 lateinit var

    next : Base<T>
 
 fun onEvent(data : T) {
 if (canManage(data)) {
 doManage(data)
 } else {
 next.onEvent(data)
 }
 }
 
 abstract fun canManage(data: T): Boolean
 abstract fun doManage(data: T)
 }
  49. CHAIN OF RESPONSIBILITY MAKING IT WORK class One : Base<Int>()

    { override fun canManage(data: Int) = data == 1 override fun doManage(data: Int) { // do amazing stuff here } }
  50. CHAIN OF RESPONSIBILITY MAKING IT WORK class One : Base<Int>()

    { override fun canManage(data: Int) = data == 1 override fun doManage(data: Int) { // do amazing stuff here } }
  51. CHAIN OF RESPONSIBILITY MAKING IT WORK class One : Base<Int>()

    { override fun canManage(data: Int) = data == 1 override fun doManage(data: Int) { // do amazing stuff here } }
  52. CHAIN OF RESPONSIBILITY MAKING IT WORK val chain = One()

    chain.next = Two() chain.onEvent(evt)
  53. CHAIN OF RESPONSIBILITY PROS ▸ Decouples senders and receivers ▸

    Simplifies the object ▸ Dynamically add & remove responsibilities
  54. USE A DESIGN EXPECTING ONE INTERFACE WITH A CLASS THAT

    IMPLEMENTS A DIFFERENT ONE ADAPTER
  55. ADAPTER LETS CLASSES WORK TOGETHER THAT COULDN’T OTHERWISE BECAUSE OF

    INCOMPATIBLE INTERFACES Someone who understood the previous slide ADAPTER
  56. ADAPTER interface LegacySystem { fun accept(legacyData : LegacyData) } interface

    LegacyData { fun byteCount() : Long fun data() : String }
  57. ADAPTER interface LegacySystem { fun accept(legacyData : LegacyData) } interface

    LegacyData { fun byteCount() : Long fun data() : String } interface NewData { fun theData() : String }
  58. ADAPTER interface LegacySystem { fun accept(legacyData : LegacyData) } interface

    LegacyData { fun byteCount() : Long fun data() : String } interface NewData { fun theData() : String }
  59. ADAPTER class Adapter (val wrapped : NewData) : LegacyData {

    override fun byteCount() = wrapped.theData.bytes.length override fun data() = wrapped.theData }
  60. ADAPTER class Adapter (val wrapped : NewData) : LegacyData{ override

    fun byteCount() = wrapped.theData.bytes.length override fun data() = wrapped.theData }
  61. ADAPTER class Adapter (val wrapped : NewData) : LegacyData{ override

    fun byteCount() = wrapped.theData.bytes.length override fun data() = wrapped.theData }
  62. FLYWEIGHT SOMETHING ABOUT IT ▸ One instance to control them

    all ▸ Used when more instances can be controlled identically ▸ Single instances cannot be controlled independently
  63. DECORATOR BASE class Tony implements Hero { final int badassLevel;

    Tony(int badassLevel) { this.badassLevel = badassLevel; } @Override int badassLevel() { return badassLevel; } }
  64. DECORATOR BASE class Tony implements Hero { final int badassLevel;

    Tony(int badassLevel) { this.badassLevel = badassLevel; } @Override int badassLevel() { return badassLevel; } }
  65. DECORATOR DECORATOR class IronMan implements Hero { final Hero hero;

    IronMan(Hero hero) { this.hero = hero; } @Override int badassLevel() { return 2 * hero.badassLevel(); } }
  66. DECORATOR HOW TO USE IT Hero tony = new Tony(100);

    Hero ironMan = new IronMan(tony);
  67. DECORATOR DECORATOR class HulkBuster implements Hero { final Hero hero;

    HulkSmasher(Hero hero) { this.hero = hero; } @Override int badassLevel() { return 3 * hero.badassLevel() + 1; } }
  68. DECORATOR HOW TO USE IT Hero tony = new Tony(100);

    Hero ironMan = new IronMan(tony); Hero hulkBuster = new HulkBuster(ironMan);
  69. DECORATOR HOW TO USE IT Hero tony = new Tony(100);

    Hero hulkBuster = new HulkBuster(tony);
  70. DECORATOR HOW TO USE IT Hero tony = new Tony(100);

    Hero hulkBuster = new HulkBuster(tony); Hero ironMan = new IronMan(hulkBuster);
  71. DECORATOR HOW TO USE IT Hero tony = new Tony(100);

    new IronMan(new HulkBuster(new IronMan(tony)));
  72. REPOSITORY PATTERN REPOSITORY interface Repository<T> { fun add(item: T) fun

    add(items: List<T>) fun delete(item: T) fun update(item: T) fun query(specification: QuerySpec<T>): List<T> } interface QuerySpec<T> { fun getResults(items: List<T>): List<T> }
  73. REPOSITORY PATTERN REPOSITORY interface Repository<T> { fun add(item: T) fun

    add(items: List<T>) fun delete(item: T) fun update(item: T) fun query(specification: QuerySpec<T>): List<T> } interface QuerySpec<T> { fun getResults(items: List<T>): List<T> }
  74. REPOSITORY PATTERN REPOSITORY interface Repository<T> { fun add(item: T) fun

    add(items: List<T>) fun delete(item: T) fun update(item: T) fun query(specification: QuerySpec<T>): List<T> } interface QuerySpec<T> { fun getResults(items: List<T>): List<T> }
  75. REPOSITORY PATTERN REPOSITORY interface Repository<T> { fun add(item: T) fun

    add(items: List<T>) fun delete(item: T) fun update(item: T) fun query(specification: QuerySpec<T>): List<T> } interface QuerySpec<T> { fun getResults(items: List<T>): List<T> }
  76. REPOSITORY PATTERN MODEL class DroidconItSpeakers : Repository<Speaker> { private val

    list: MutableList<Speaker> = mutableListOf() override fun add(item: Speaker) { if (!list.contains(item)) { list.add(item) } } override fun add(items: List<Speaker>) { ... } override fun delete(item: Speaker) { list.remove(item) } override fun update(item: Speaker) { ... } override fun query(specification: QuerySpec<Speaker>): List<Speaker> = specification.getResults(list) }
  77. REPOSITORY PATTERN MODEL class DroidconItSpeakers : Repository<Speaker> { private val

    list: MutableList<Speaker> = mutableListOf() override fun add(item: Speaker) { if (!list.contains(item)) { list.add(item) } } override fun add(items: List<Speaker>) { ... } override fun delete(item: Speaker) { list.remove(item) } override fun update(item: Speaker) { ... } override fun query(specification: QuerySpec<Speaker>): List<Speaker> = specification.getResults(list) }
  78. REPOSITORY PATTERN MODEL class DroidconItSpeakers : Repository<Speaker> { private val

    list: MutableList<Speaker> = mutableListOf() override fun add(item: Speaker) { if (!list.contains(item)) { list.add(item) } } override fun add(items: List<Speaker>) { ... } override fun delete(item: Speaker) { list.remove(item) } override fun update(item: Speaker) { ... } override fun query(specification: QuerySpec<Speaker>): List<Speaker> = specification.getResults(list) }
  79. REPOSITORY PATTERN MODEL class DroidconItSpeakers : Repository<Speaker> { private val

    list: MutableList<Speaker> = mutableListOf() override fun add(item: Speaker) { if (!list.contains(item)) { list.add(item) } } override fun add(items: List<Speaker>) { ... } override fun delete(item: Speaker) { list.remove(item) } override fun update(item: Speaker) { ... } override fun query(specification: QuerySpec<Speaker>): List<Speaker> = specification.getResults(list) }
  80. REPOSITORY PATTERN QUERY object GetAllTheSebastianos : QuerySpec<Speaker> { override fun

    getResults(items: List<Speaker>): List<Speaker> = items.filter { it.name.startsWith("Sebastiano") } }
  81. REPOSITORY PATTERN QUERY object GetAllTheSebastianos : QuerySpec<Speaker> { override fun

    getResults(items: List<Speaker>): List<Speaker> = items.filter { it.name.startsWith("Sebastiano") } }
  82. REPOSITORY PATTERN QUERY object GetAllTheSebastianos : QuerySpec<Speaker> { override fun

    getResults(items: List<Speaker>): List<Speaker> = items.filter { it.name.startsWith("Sebastiano") } }
  83. REPOSITORY PATTERN MODEL class DroidconItSpeakers : Repository<Speaker> { private val

    list: MutableList<Speaker> = mutableListOf() override fun add(item: Speaker) { ... } override fun add(items: List<Speaker>) { ... } override fun delete(item: Speaker) { ... } override fun update(item: Speaker) { ... } override fun query(specification: QuerySpec<Speaker>) { ... } }
  84. REPOSITORY PATTERN MODEL class DroidconItSpeakers : Repository<Speaker> { private val

    sharedPrefs: SharedPreference override fun add(item: Speaker) { ... } override fun add(items: List<Speaker>) { ... } override fun delete(item: Speaker) { ... } override fun update(item: Speaker) { ... } override fun query(specification: QuerySpec<Speaker>) { ... } }
  85. REPOSITORY PATTERN MODEL class DroidconItSpeakers : Repository<Speaker> { private val

    database: SQLiteDatabase override fun add(item: Speaker) { ... } override fun add(items: List<Speaker>) { ... } override fun delete(item: Speaker) { ... } override fun update(item: Speaker) { ... } override fun query(specification: QuerySpec<Speaker>) { ... } }
  86. REPOSITORY ‣ Isolation ‣ Strong typing for reliability ‣ Testability

    The repository is a central place where data is stored and maintained.
  87. SIMPLIFIES AN INTERFACE BY HIDING ALL THE COMPLEXITY BEHIND A

    CLEAN FACADE Someone who loves facades FACADE PATTERN
  88. FACADE VS ADAPTER both may wrap different classes simplify vs

    convert PLUS easier and unified access to the subsystem