Share
Embed
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
Transcript
141 slides
-
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 -
ANDROIDNE.WS Sure, I’ll let you know when I have some!
I’m interested in Android news -
ANDROIDNE.WS Hey, I have news! Oh-hi Android news! MOAR news!
Such Androids! I’m no longer interested! -
OBSERVER/OBSERVABLE OBSERVER interface Observer<T> { fun onDataChanged(data : T) }
class SimpleObserver : Observer<Int> { override fun onDataChanged(data : Int) { … } } -
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) } } } -
OBSERVER/OBSERVABLE HOW TO USE IT val observer1 = SimpleObserver() val
observer2 = SimpleObserver() val observable = SimpleObservable() observable.addObserver(observer1) observable.addObserver(observer2) … observable.doThings() -
OBSERVER/OBSERVABLE WE ALREADY MET THIS PATTERN ▸ View.OnClickListener (& friends)
▸ Event bus ▸ RxJava -
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() -
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() -
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() -
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() -
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() -
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() -
STATIC FACTORY ‣ Not really a pattern ‣ Controls the
creation of objects ‣ Widely spread across Java/Android Calendar.getInstance() String.format() SomeFragment.newInstance() ಠ_ಠ -
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 } -
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 } -
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 } -
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 } -
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 } -
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) } -
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) } -
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) } -
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) } -
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) } -
FACTORY PATTERN FACTORY interface Robot class Nestor4_Factory : RobotFactory {
fun createRobot(): Robot = Nestor4_Robot() private class Nestor4_Robot: Robot } interface RobotFactory { fun createRobot(): Robot } -
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 } -
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 } -
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) } -
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) } -
FACTORY ‣ Creational ‣ Decoupling ‣ Inheritance Define an interface
for creating an object, but let the classes which implement the interface decide which class to instantiate. -
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. -
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 -
BUILDER PATTERN TARGET class QuarterMileCar private constructor( val boost: Boost?
= null, val color: BodyColor = Orange, val engine: Engine = StockEngine ) : Car { // Builder goes here } -
BUILDER PATTERN TARGET class QuarterMileCar private constructor( val boost: Boost?
= null, val color: BodyColor = Orange, val engine: Engine = StockEngine ) : Car { // Builder goes here } -
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) } -
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) } -
FACTORY PATTERN HOW TO USE IT fun main(args: Array<String>) {
val supra = QuarterMileCar.Builder() .withBoost(NOS) .withColor(Orange) .withEngine(SuperPumpedEngine) .build() } -
FACTORY PATTERN HOW TO USE IT fun main(args: Array<String>) {
val supra = QuarterMileCar.Builder() .withBoost(NOS) .withColor(Blue) .withEngine(SuperPumpedEngine) .build() } -
FACTORY PATTERN HOW TO USE IT fun main(args: Array<String>) {
val s2000 = QuarterMileCar.Builder() .withEngine(SuperPumpedEngine) .build() } -
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) } -
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) } } -
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. -
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. -
CHAIN OF RESPONSIBILITY when(x) { 1 -> doThis() 2 ->
doThat() else -> crashPainfully() } -
CHAIN OF RESPONSIBILITY DRAWBACKS ▸ Execute more than one branch
▸ Testing ▸ Complex conditions ▸ Possible violation of SRP -
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) } -
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) } -
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) } -
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) } -
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) } -
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 } } -
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 } } -
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 } } -
CHAIN OF RESPONSIBILITY MAKING IT WORK val chain = One()
chain.next = Two() -
CHAIN OF RESPONSIBILITY MAKING IT WORK val chain = One()
chain.next = Two() chain.onEvent(evt) -
CHAIN OF RESPONSIBILITY PROS ▸ Decouples senders and receivers ▸
Simplifies the object ▸ Dynamically add & remove responsibilities -
USE A DESIGN EXPECTING ONE INTERFACE WITH A CLASS THAT
IMPLEMENTS A DIFFERENT ONE ADAPTER -
ADAPTER LETS CLASSES WORK TOGETHER THAT COULDN’T OTHERWISE BECAUSE OF
INCOMPATIBLE INTERFACES Someone who understood the previous slide ADAPTER -
ADAPTER interface LegacySystem { fun accept(legacyData : LegacyData) } interface
LegacyData { fun byteCount() : Long fun data() : String } -
ADAPTER interface LegacySystem { fun accept(legacyData : LegacyData) } interface
LegacyData { fun byteCount() : Long fun data() : String } interface NewData { fun theData() : String } -
ADAPTER interface LegacySystem { fun accept(legacyData : LegacyData) } interface
LegacyData { fun byteCount() : Long fun data() : String } interface NewData { fun theData() : String } -
ADAPTER class Adapter (val wrapped : NewData) : LegacyData {
override fun byteCount() = wrapped.theData.bytes.length override fun data() = wrapped.theData } -
ADAPTER class Adapter (val wrapped : NewData) : LegacyData{ override
fun byteCount() = wrapped.theData.bytes.length override fun data() = wrapped.theData } -
ADAPTER class Adapter (val wrapped : NewData) : LegacyData{ override
fun byteCount() = wrapped.theData.bytes.length override fun data() = wrapped.theData } -
FLYWEIGHT SOMETHING ABOUT IT ▸ One instance to control them
all ▸ Used when more instances can be controlled identically ▸ Single instances cannot be controlled independently -
DECORATOR BASE class Tony implements Hero { final int badassLevel;
Tony(int badassLevel) { this.badassLevel = badassLevel; } @Override int badassLevel() { return badassLevel; } } -
DECORATOR BASE class Tony implements Hero { final int badassLevel;
Tony(int badassLevel) { this.badassLevel = badassLevel; } @Override int badassLevel() { return badassLevel; } } -
DECORATOR DECORATOR class IronMan implements Hero { final Hero hero;
IronMan(Hero hero) { this.hero = hero; } @Override int badassLevel() { return 2 * hero.badassLevel(); } } -
DECORATOR HOW TO USE IT Hero tony = new Tony(100);
Hero ironMan = new IronMan(tony); -
DECORATOR DECORATOR class HulkBuster implements Hero { final Hero hero;
HulkSmasher(Hero hero) { this.hero = hero; } @Override int badassLevel() { return 3 * hero.badassLevel() + 1; } } -
DECORATOR HOW TO USE IT Hero tony = new Tony(100);
Hero ironMan = new IronMan(tony); Hero hulkBuster = new HulkBuster(ironMan); -
DECORATOR HOW TO USE IT Hero tony = new Tony(100);
Hero hulkBuster = new HulkBuster(tony); -
DECORATOR HOW TO USE IT Hero tony = new Tony(100);
Hero hulkBuster = new HulkBuster(tony); Hero ironMan = new IronMan(hulkBuster); -
DECORATOR HOW TO USE IT Hero tony = new Tony(100);
new IronMan(new HulkBuster(new IronMan(tony))); -
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> } -
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> } -
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> } -
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> } -
REPOSITORY PATTERN MODEL data class Speaker( val id: Long, val
name: String, val age: Int ) -
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) } -
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) } -
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) } -
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) } -
REPOSITORY PATTERN QUERY object GetAllTheSebastianos : QuerySpec<Speaker> { override fun
getResults(items: List<Speaker>): List<Speaker> = items.filter { it.name.startsWith("Sebastiano") } } -
REPOSITORY PATTERN QUERY object GetAllTheSebastianos : QuerySpec<Speaker> { override fun
getResults(items: List<Speaker>): List<Speaker> = items.filter { it.name.startsWith("Sebastiano") } } -
REPOSITORY PATTERN QUERY object GetAllTheSebastianos : QuerySpec<Speaker> { override fun
getResults(items: List<Speaker>): List<Speaker> = items.filter { it.name.startsWith("Sebastiano") } } -
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>) { ... } } -
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>) { ... } } -
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>) { ... } } -
REPOSITORY ‣ Isolation ‣ Strong typing for reliability ‣ Testability
The repository is a central place where data is stored and maintained. -
SIMPLIFIES AN INTERFACE BY HIDING ALL THE COMPLEXITY BEHIND A
CLEAN FACADE Someone who loves facades FACADE PATTERN -
FACADE I J A B C D E F G
H FACADE -
FACADE HELPS DECOUPLING THE CLIENT FROM A SUBSYSTEM OF OTHER
COMPONENTS FACADE -
FACADE VS ADAPTER both may wrap different classes simplify vs
convert PLUS easier and unified access to the subsystem