├── .gitignore ├── README.md ├── abstract-types ├── pom.xml └── src │ └── main │ └── scala │ └── com │ └── ivan │ └── nikolov │ ├── abstract_types │ ├── AbstractTypesExamples.scala │ ├── GenericsExamples.scala │ ├── Person.scala │ └── Printer.scala │ ├── polymorphism │ ├── AdhocPolymorphismExample.scala │ └── SubtypePolymorphismExample.scala │ └── self_types │ ├── DB.scala │ └── PersisterExample.scala ├── aop ├── pom.xml └── src │ └── main │ ├── resources │ └── users.json │ └── scala │ └── com │ └── ivan │ └── nikolov │ ├── aop │ ├── DataReader.scala │ ├── LoggingDataReader.scala │ └── model │ │ └── Person.scala │ └── components │ ├── CookingComponent.scala │ ├── RecipeComponent.scala │ ├── Robot.scala │ ├── RobotRegistry.scala │ ├── TimeComponent.scala │ ├── base │ ├── Cooker.scala │ ├── RecipeFinder.scala │ └── Time.scala │ └── model │ └── Food.scala ├── behavioral-design-patterns ├── pom.xml └── src │ └── main │ ├── resources │ ├── com │ │ └── ivan │ │ │ └── nikolov │ │ │ └── behavioral │ │ │ ├── strategy │ │ │ ├── people.csv │ │ │ └── people.json │ │ │ └── template │ │ │ ├── people.csv │ │ │ └── people.json │ └── log4j.properties │ └── scala │ └── com │ └── ivan │ └── nikolov │ └── behavioral │ ├── chain_of_responsibility │ ├── ATM.scala │ ├── Dispenser.scala │ └── Money.scala │ ├── command │ ├── Robot.scala │ └── RobotCommand.scala │ ├── interpreter │ ├── Expression.scala │ └── RPNParser.scala │ ├── iterator │ └── Student.scala │ ├── mediator │ └── Student.scala │ ├── memento │ ├── Memento.scala │ └── TextEditor.scala │ ├── null_object │ ├── DataGenerator.scala │ └── Message.scala │ ├── observer │ ├── Observer.scala │ └── Post.scala │ ├── state │ ├── State.scala │ └── model │ │ └── MediaPlayer.scala │ ├── strategy │ ├── Parser.scala │ ├── ParsingStrategy.scala │ └── model │ │ └── Person.scala │ ├── template │ ├── DataFinder.scala │ └── model │ │ └── Person.scala │ ├── value_object │ └── Date.scala │ └── visitor │ ├── Element.scala │ ├── Visitor.scala │ └── better │ ├── Element.scala │ └── Visitor.scala ├── creational-design-patterns ├── pom.xml └── src │ └── main │ ├── resources │ └── com │ │ └── ivan │ │ └── nikolov │ │ └── creational │ │ └── lazy_init │ │ └── pi.properties │ └── scala │ └── com │ └── ivan │ └── nikolov │ └── creational │ ├── builder │ ├── case_classes │ │ └── Person.scala │ ├── java_way │ │ └── Person.scala │ └── type_safe │ │ ├── Person.scala │ │ └── case_require │ │ └── Person.scala │ ├── factories │ ├── SimpleConnection.scala │ ├── abstract_factory │ │ ├── DatabaseClient.scala │ │ ├── DatabaseConnectorFactory.scala │ │ └── Example.scala │ ├── factory_method │ │ ├── BadDatabaseClient.scala │ │ ├── BadExample.scala │ │ ├── DatabaseClient.scala │ │ ├── Example.scala │ │ └── SimpleConnectionPrinter.scala │ └── simple │ │ └── Animal.scala │ ├── lazy_init │ └── CircleUtils.scala │ ├── prototype │ └── Cell.scala │ └── singleton │ ├── AppRegistry.scala │ └── StringUtils.scala ├── deep-theory ├── input.txt ├── output.txt ├── pom.xml └── src │ └── main │ └── scala │ └── com │ └── ivan │ └── nikolov │ ├── functors │ ├── Functor.scala │ ├── FunctorsExample.scala │ └── package.scala │ ├── monads │ ├── ListWrapper.scala │ ├── Monad.scala │ ├── Option.scala │ └── io │ │ ├── State.scala │ │ └── package.scala │ └── monoids │ ├── Monoid.scala │ ├── MonoidFolding.scala │ └── package.scala ├── functional-design-patterns ├── pom.xml └── src │ └── main │ └── scala │ └── com │ └── ivan │ └── nikolov │ ├── cake │ ├── Application.scala │ ├── ApplicationComponentRegistry.scala │ ├── DaoComponent.scala │ ├── DatabaseComponent.scala │ ├── MigrationComponent.scala │ ├── UserComponent.scala │ └── model │ │ ├── Class.scala │ │ └── Person.scala │ ├── duck │ ├── DuckTypingExample.scala │ ├── SentenceParserSplit.scala │ └── SentenceParserTokenize.scala │ ├── implicits │ ├── ImplicitExamples.scala │ ├── di │ │ ├── DatabaseService.scala │ │ ├── ImplicitDIExample.scala │ │ ├── UserService.scala │ │ ├── model │ │ │ └── Person.scala │ │ └── package.scala │ └── package.scala │ ├── laziness │ └── Person.scala │ ├── lens │ ├── User.scala │ └── bad │ │ └── User.scala │ ├── memo │ ├── Hasher.scala │ ├── MemoizationExample.scala │ └── Memoizer.scala │ ├── partial_functions │ ├── PartiallyAppliedFunctions.scala │ └── PartiallyDefinedFunctions.scala │ ├── pimp │ ├── PimpExample.scala │ ├── model │ │ └── Person.scala │ └── package.scala │ ├── stackable │ ├── IntQueue.scala │ └── StringWriter.scala │ └── type_classes │ ├── Number.scala │ ├── Stats.scala │ └── StatsExample.scala ├── job-scheduler ├── pom.xml └── src │ ├── main │ ├── resources │ │ ├── application.conf │ │ └── log4j.properties │ └── scala │ │ └── com │ │ └── ivan │ │ └── nikolov │ │ └── scheduler │ │ ├── Scheduler.scala │ │ ├── actors │ │ ├── ActorFactoryComponent.scala │ │ ├── Master.scala │ │ ├── Worker.scala │ │ └── messages │ │ │ └── SchedulerMessage.scala │ │ ├── config │ │ ├── app │ │ │ └── AppConfigComponent.scala │ │ └── job │ │ │ ├── JobConfig.scala │ │ │ ├── JobFrequency.scala │ │ │ ├── JobType.scala │ │ │ └── TimeOptions.scala │ │ ├── dao │ │ ├── DaoServiceComponent.scala │ │ ├── DatabaseServiceComponent.scala │ │ └── MigrationComponent.scala │ │ ├── io │ │ └── IOServiceComponent.scala │ │ ├── registry │ │ └── ComponentRegistry.scala │ │ └── services │ │ └── JobConfigReaderServiceComponent.scala │ └── test │ ├── resources │ ├── application.conf │ ├── command.json │ └── log4j.properties │ └── scala │ └── com │ └── ivan │ └── nikolov │ └── scheduler │ ├── TestEnvironment.scala │ ├── config │ └── job │ │ └── TimeOptionsTest.scala │ ├── dao │ └── DaoServiceTest.scala │ └── services │ └── JobConfigReaderServiceTest.scala ├── pom.xml ├── real-life-applications ├── pom.xml └── src │ ├── main │ └── scala │ │ └── com │ │ └── ivan │ │ └── nikolov │ │ ├── monads │ │ ├── IOMonadExample.scala │ │ ├── model │ │ │ └── Person.scala │ │ └── package.scala │ │ └── monoids │ │ ├── MonoidsExample.scala │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── ivan │ └── nikolov │ └── monoids │ └── MonoidsTest.scala ├── structural-design-patterns ├── pom.xml └── src │ └── main │ ├── resources │ ├── com │ │ └── ivan │ │ │ └── nikolov │ │ │ └── structural │ │ │ ├── decorator │ │ │ └── data.txt │ │ │ └── proxy │ │ │ ├── file1.txt │ │ │ ├── file2.txt │ │ │ └── file3.txt │ └── log4j.properties │ └── scala │ └── com │ └── ivan │ └── nikolov │ └── structural │ ├── adapter │ ├── AppLogger.scala │ └── package.scala │ ├── bridge │ ├── Hasher.scala │ ├── PasswordConverter.scala │ ├── common │ │ └── Hasher.scala │ └── scala │ │ ├── HasherImpl.scala │ │ └── PasswordConverterBase.scala │ ├── composite │ └── Node.scala │ ├── decorator │ ├── CapitalizedInputReaderTrait.scala │ ├── InputReaderDecorator.scala │ └── common │ │ └── InputReader.scala │ ├── facade │ ├── DataDecoder.scala │ ├── DataDeserializer.scala │ ├── DataDownloader.scala │ ├── DataReader.scala │ └── model │ │ └── Person.scala │ ├── flyweight │ ├── Circle.scala │ └── Color.scala │ └── proxy │ └── FileReader.scala ├── traits ├── pom.xml └── src │ ├── main │ └── scala │ │ └── com │ │ └── ivan │ │ └── nikolov │ │ ├── basic │ │ ├── Beeper.scala │ │ ├── NotifierImpl.scala │ │ └── Ping.scala │ │ ├── common │ │ ├── Alarm.scala │ │ ├── Connector.scala │ │ └── Notifier.scala │ │ ├── composition │ │ ├── Clashing.scala │ │ ├── Greeter.scala │ │ ├── Watch.scala │ │ └── self_types │ │ │ └── AlarmNotifier.scala │ │ ├── diamond │ │ └── Diamond.scala │ │ └── linearisation │ │ ├── Animal.scala │ │ └── MultiplierIdentity.scala │ └── test │ └── scala │ └── com │ └── ivan │ └── nikolov │ ├── composition │ ├── TraitACaseScopeTest.scala │ └── TraitATest.scala │ └── linearisation │ └── DoubledMultiplierIdentityTest.scala └── unification ├── pom.xml └── src └── main └── scala └── com └── ivan └── nikolov └── unification ├── adts ├── Month.scala ├── RGB.scala └── Shape.scala ├── functions ├── FunctionLiterals.scala └── FunctionObjects.scala └── modules └── Alarm.scala /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | 19 | # IntelliJ specific 20 | *.iml 21 | 22 | .idea/ 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Scala Design Patterns 2 | ===================== 3 | 4 | This repository contains the code that goes with the **Scala Design Patterns** book. 5 | 6 | ### Project Structure 7 | 8 | The source code for the book is presented as amulti-module Maven project and it contains different modules for the following chapters: 9 | 10 | 1. Chapter 1 - The Design Patterns Out There and Setting Up Your Environment. 11 | 12 | There is no code for this chapter. It provided the users with some skeletons. 13 | 14 | 2. Chapter 2 - Traits and Mixin Compositions. 15 | 16 | a. Module name: **traits**. 17 | 18 | 3. Chapter 3 - Unification. 19 | 20 | a. Module name: **unification**. 21 | 22 | 4. Chapter 4 - Abstract and Self Types. 23 | 24 | a. Module name: **abstract-types**. 25 | 26 | 5. Chapter 5 - Aspect Oriented Programming and Components. 27 | 28 | a. Module name: **aop**. 29 | 30 | 6. Chapter 6 - Creational Design Patterns. 31 | 32 | a. Module name: **creational-design-patterns**. 33 | 34 | 7. Chapter 7 - Structural Design Patterns. 35 | 36 | a. Module name: **structural-design-patterns**. 37 | 38 | 8. Chapter 8 - Behavioral Design Patterns - Part 1. 39 | 40 | a. Module name: **behavioral-design-patterns**. 41 | 42 | 9. Chapter 9 - Behavioral Design Patterns - Part 2. 43 | 44 | a. Module name: **behavioral-design-patterns**. 45 | 46 | 10. Chapter 10 - Functional Design Patterns – The Deep Theory. 47 | 48 | a. Module name: **deep-theory**. 49 | 50 | 11. Chapter 11 - Functional Design Patterns – Applying What We Learned. 51 | 52 | a. Module name: **functional-design-patterns**. 53 | 54 | 12. Chapter 12 - Real Life Applications. 55 | 56 | a. Module name: **real-life-applications**. 57 | 58 | b. Module name: **job-scheduler**. 59 | 60 | ### Running the Code 61 | 62 | Running the code is pretty straightforward from here and anyone with some minor Maven experience should manage. 63 | 64 | #### Compiling the Projects 65 | 66 | `mvn clean compile` 67 | 68 | #### Running the Unit Tests 69 | 70 | `mvn clean test` 71 | 72 | The test command will also run the compile one. 73 | 74 | #### Creating the Jars 75 | 76 | `mvn clean package` 77 | 78 | The above command will package everything. You can find the jars in the **target** subfolder of the respective module folder. -------------------------------------------------------------------------------- /abstract-types/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | abstract-types 13 | 14 | 15 | -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/abstract_types/AbstractTypesExamples.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.abstract_types 2 | 3 | trait ContainerAT { 4 | type T 5 | val data: T 6 | 7 | def compare(other: T) = data.equals(other) 8 | } 9 | 10 | class StringContainer(val data: String) extends ContainerAT { 11 | override type T = String 12 | } 13 | 14 | object AbstractTypesExamples { 15 | def main(args: Array[String]): Unit = { 16 | val stringContainer = new StringContainer("some text") 17 | System.out.println(s"Comparing with string: ${stringContainer.compare("some text")}") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/abstract_types/GenericsExamples.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.abstract_types 2 | 3 | trait Adder { 4 | def sum[T](a: T, b: T)(implicit numeric: Numeric[T]): T = numeric.plus(a, b) 5 | } 6 | 7 | class Container[T](data: T) { 8 | def compare(other: T) = data.equals(other) 9 | } 10 | 11 | object GenericsExamples extends Adder { 12 | def main(args: Array[String]): Unit = { 13 | System.out.println(s"1 + 3 = ${sum(1, 3)}") 14 | System.out.println(s"1.2 + 6.7 = ${sum(1.2, 6.7)}") 15 | // System.out.println(s"abc + cde = ${sum("abc", "cde")}") // compilation fails 16 | 17 | val intContainer = new Container(10) 18 | System.out.println(s"Comparing with int: ${intContainer.compare(11)}") 19 | 20 | val stringContainer = new Container("some text") 21 | System.out.println(s"Comparing with string: ${stringContainer.compare("some text")}") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/abstract_types/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.abstract_types 2 | 3 | 4 | case class Person(name: String) 5 | -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/abstract_types/Printer.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.abstract_types 2 | 3 | abstract class PrintData 4 | abstract class PrintMaterial 5 | abstract class PrintMedia 6 | 7 | case class Paper() extends PrintMedia 8 | case class Air() extends PrintMedia 9 | 10 | case class Text() extends PrintData 11 | case class Model() extends PrintData 12 | 13 | case class Toner() extends PrintMaterial 14 | case class Plastic() extends PrintMaterial 15 | 16 | trait Printer { 17 | type Data <: PrintData 18 | type Material <: PrintMaterial 19 | type Media <: PrintMedia 20 | 21 | def print(data: Data, material: Material, media: Media) = 22 | s"Printing $data with $material material on $media media." 23 | } 24 | 25 | trait GenericPrinter[Data <: PrintData, Material <: PrintMaterial, Media <: PrintMedia] { 26 | def print(data: Data, material: Material, media: Media) = 27 | s"Printing $data with $material material on $media media." 28 | } 29 | 30 | class LaserPrinter extends Printer { 31 | type Media = Paper 32 | type Data = Text 33 | type Material = Toner 34 | } 35 | 36 | class ThreeDPrinter extends Printer { 37 | type Media = Air 38 | type Data = Model 39 | type Material = Plastic 40 | } 41 | 42 | class GenericLaserPrinter[Data <: Text, Material <: Toner, Media <: Paper] extends GenericPrinter[Data, Material, Media] 43 | class GenericThreeDPrinter[Data <: Model, Material <: Plastic, Media <: Air] extends GenericPrinter[Data, Material, Media] 44 | 45 | class GenericPrinterImpl[Data <: PrintData, Material <: PrintMaterial, Media <: PrintMedia] extends GenericPrinter[Data, Material, Media] 46 | 47 | object PrinterExample { 48 | def main(args: Array[String]): Unit = { 49 | val laser = new LaserPrinter 50 | val threeD = new ThreeDPrinter 51 | 52 | val genericLaser = new GenericLaserPrinter[Text, Toner, Paper] 53 | val genericThreeD = new GenericThreeDPrinter[Model, Plastic, Air] 54 | 55 | val wrongPrinter = new GenericPrinterImpl[Model, Toner, Air] 56 | 57 | System.out.println(laser.print(Text(), Toner(), Paper())) 58 | System.out.println(threeD.print(Model(), Plastic(), Air())) 59 | 60 | System.out.println(genericLaser.print(Text(), Toner(), Paper())) 61 | System.out.println(genericThreeD.print(Model(), Plastic(), Air())) 62 | 63 | System.out.println(wrongPrinter.print(Model(), Toner(), Air())) 64 | } 65 | } -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/polymorphism/AdhocPolymorphismExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.polymorphism 2 | 3 | trait Adder[T] { 4 | def sum(a: T, b: T): T 5 | } 6 | 7 | object Adder { 8 | def sum[T: Adder](a: T, b: T): T = 9 | implicitly[Adder[T]].sum(a, b) 10 | 11 | // implicit val int2Adder: Adder[Int] = new Adder[Int] { 12 | // override def sum(a: Int, b: Int): Int = a + b 13 | // } 14 | 15 | implicit def numeric2Adder[T : Numeric]: Adder[T] = new Adder[T] { 16 | override def sum(a: T, b: T): T = implicitly[Numeric[T]].plus(a, b) 17 | } 18 | 19 | implicit val string2Adder: Adder[String] = new Adder[String] { 20 | override def sum(a: String, b: String): String = s"$a concatenated with $b" 21 | } 22 | } 23 | 24 | object AdhocPolymorphismExample { 25 | import Adder._ 26 | def main(args: Array[String]): Unit = { 27 | System.out.println(s"The sum of 1 + 2 is ${sum(1, 2)}") 28 | System.out.println(s"The sum of 1.2 + 6.5 is ${sum(1.2, 6.5)}") 29 | System.out.println(s"The sum of abc + def is ${sum("abc", "def")}") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/polymorphism/SubtypePolymorphismExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.polymorphism 2 | 3 | abstract class Item { 4 | def pack: String 5 | } 6 | 7 | class Fruit extends Item { 8 | override def pack: String = "I'm a fruit and I'm packed in a bag." 9 | } 10 | 11 | class Drink extends Item { 12 | override def pack: String = "I'm a drink and I'm packed in a bottle." 13 | } 14 | 15 | object SubtypePolymorphismExample { 16 | def main(args: Array[String]): Unit = { 17 | val shoppingBasket: List[Item] = List( 18 | new Fruit, 19 | new Drink 20 | ) 21 | shoppingBasket.foreach(i => System.out.println(i.pack)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/self_types/DB.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.self_types 2 | 3 | trait DB { 4 | def connect(): Unit = { 5 | System.out.println("Connected.") 6 | } 7 | 8 | def dropDatabase(): Unit = { 9 | System.out.println("Dropping!") 10 | } 11 | 12 | def close(): Unit = { 13 | System.out.println("Closed.") 14 | } 15 | } 16 | 17 | trait UserDB { 18 | this: DB => 19 | 20 | def createUser(username: String): Unit = { 21 | connect() 22 | try { 23 | System.out.println(s"Creating a user: $username") 24 | } finally { 25 | close() 26 | } 27 | } 28 | 29 | def getUser(username: String): Unit = { 30 | connect() 31 | try { 32 | System.out.println(s"Getting a user: $username") 33 | } finally { 34 | close() 35 | } 36 | } 37 | } 38 | 39 | trait UserService { 40 | this: UserDB => 41 | 42 | // does not compile 43 | // def bad(): Unit = { 44 | // dropDatabase() 45 | // } 46 | } 47 | 48 | //trait UserDB extends DB { 49 | // def createUser(username: String): Unit = { 50 | // connect() 51 | // try { 52 | // System.out.println(s"Creating a user: $username") 53 | // } finally { 54 | // close() 55 | // } 56 | // } 57 | // 58 | // def getUser(username: String): Unit = { 59 | // connect() 60 | // try { 61 | // System.out.println(s"Getting a user: $username") 62 | // } finally { 63 | // close() 64 | // } 65 | // } 66 | //} 67 | // 68 | //trait UserService extends UserDB { 69 | // def bad(): Unit = { 70 | // dropDatabase() 71 | // } 72 | //} 73 | -------------------------------------------------------------------------------- /abstract-types/src/main/scala/com/ivan/nikolov/self_types/PersisterExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.self_types 2 | 3 | import scala.collection.mutable 4 | 5 | 6 | trait Persister[T] { 7 | this: Database[T] with History with Mystery => 8 | def persist(data: T): Unit = { 9 | System.out.println("Calling persist.") 10 | save(data) 11 | add() 12 | } 13 | } 14 | 15 | trait Database[T] { 16 | def save(data: T) 17 | } 18 | 19 | trait MemoryDatabase[T] extends Database[T] { 20 | val db: mutable.MutableList[T] = mutable.MutableList.empty 21 | 22 | override def save(data: T): Unit = { 23 | System.out.println("Saving to in memory database.") 24 | db.+=:(data) 25 | } 26 | } 27 | 28 | trait FileDatabase[T] extends Database[T] { 29 | override def save(data: T): Unit = { 30 | System.out.println("Saving to file.") 31 | } 32 | } 33 | 34 | trait History { 35 | def add(): Unit = { 36 | System.out.println("Action added to history.") 37 | } 38 | } 39 | 40 | trait Mystery { 41 | def add(): Unit = { 42 | System.out.println("Mystery added!") 43 | } 44 | } 45 | 46 | class FilePersister[T] extends Persister[T] with FileDatabase[T] with History with Mystery { 47 | override def add(): Unit ={ 48 | super[History].add() 49 | } 50 | } 51 | class MemoryPersister[T] extends Persister[T] with MemoryDatabase[T] with History with Mystery { 52 | override def add(): Unit ={ 53 | super[Mystery].add() 54 | } 55 | } 56 | 57 | object PersisterExample { 58 | def main(args: Array[String]): Unit = { 59 | val fileStringPersister = new FilePersister[String] 60 | val memoryIntPersister = new MemoryPersister[Int] 61 | 62 | fileStringPersister.persist("Something") 63 | fileStringPersister.persist("Something else") 64 | 65 | memoryIntPersister.persist(100) 66 | memoryIntPersister.persist(123) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /aop/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | aop 13 | 14 | 15 | 16 | org.json4s 17 | json4s-jackson_2.11 18 | ${json4s.jackson.version} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /aop/src/main/resources/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "firstName": "Ivan", 4 | "lastName": "Nikolov", 5 | "age": 26 6 | }, 7 | { 8 | "firstName": "John", 9 | "lastName": "Smith", 10 | "age": 55 11 | }, 12 | { 13 | "firstName": "Maria", 14 | "lastName": "Cooper", 15 | "age": 19 16 | } 17 | ] -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/aop/DataReader.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.aop 2 | 3 | import com.ivan.nikolov.aop.model.Person 4 | import org.json4s._ 5 | import org.json4s.jackson.JsonMethods._ 6 | 7 | trait DataReader { 8 | def readData(): List[Person] 9 | def readDataInefficiently(): List[Person] 10 | } 11 | 12 | class DataReaderImpl extends DataReader { 13 | implicit val formats = DefaultFormats 14 | 15 | private def readUntimed(): List[Person] = 16 | parse(StreamInput(getClass.getResourceAsStream("/users.json"))).extract[List[Person]] 17 | 18 | override def readData(): List[Person] = 19 | readUntimed() 20 | 21 | override def readDataInefficiently(): List[Person] = { 22 | (1 to 10000).foreach { 23 | case num => 24 | readUntimed() 25 | } 26 | readUntimed() 27 | } 28 | 29 | 30 | /* 31 | // Uncomment for using the timing without AOP. 32 | 33 | override def readData(): List[Person] = { 34 | val startMillis = System.currentTimeMillis() 35 | val result = readUntimed() 36 | val time = System.currentTimeMillis() - startMillis 37 | System.err.println(s"readData took ${time} milliseconds.") 38 | result 39 | } 40 | 41 | override def readDataInefficiently(): List[Person] = { 42 | val startMillis = System.currentTimeMillis() 43 | (1 to 10000).foreach { 44 | case num => 45 | readUntimed() 46 | } 47 | val result = readUntimed() 48 | val time = System.currentTimeMillis() - startMillis 49 | System.err.println(s"readDataInefficiently took ${time} milliseconds.") 50 | result 51 | } 52 | 53 | */ 54 | } 55 | 56 | object DataReaderExample { 57 | def main(args: Array[String]): Unit = { 58 | val dataReader = new DataReaderImpl 59 | System.out.println(s"I just read the following data efficiently: ${dataReader.readData()}") 60 | System.out.println(s"I just read the following data inefficiently: ${dataReader.readDataInefficiently()}") 61 | } 62 | } 63 | 64 | object DataReaderAOPExample { 65 | def main(args: Array[String]): Unit = { 66 | val dataReader = new DataReaderImpl with LoggingDataReader 67 | System.out.println(s"I just read the following data efficiently: ${dataReader.readData()}") 68 | System.out.println(s"I just read the following data inefficiently: ${dataReader.readDataInefficiently()}") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/aop/LoggingDataReader.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.aop 2 | 3 | import com.ivan.nikolov.aop.model.Person 4 | 5 | trait LoggingDataReader extends DataReader { 6 | 7 | abstract override def readData(): List[Person] = { 8 | val startMillis = System.currentTimeMillis() 9 | val result = super.readData() 10 | val time = System.currentTimeMillis() - startMillis 11 | System.err.println(s"readData took ${time} milliseconds.") 12 | result 13 | } 14 | 15 | abstract override def readDataInefficiently(): List[Person] = { 16 | val startMillis = System.currentTimeMillis() 17 | val result = super.readDataInefficiently() 18 | val time = System.currentTimeMillis() - startMillis 19 | System.err.println(s"readDataInefficiently took ${time} milliseconds.") 20 | result 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/aop/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.aop.model 2 | 3 | case class Person(firstName: String, lastName: String, age: Int) 4 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/CookingComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components 2 | 3 | import com.ivan.nikolov.components.base.Cooker 4 | import com.ivan.nikolov.components.model.Food 5 | 6 | trait CookingComponent { 7 | this: RecipeComponent => 8 | 9 | val cooker: Cooker 10 | 11 | class CookerImpl extends Cooker { 12 | override def cook(what: String): Food = { 13 | val recipeText = recipe.findRecipe(what) 14 | Food(s"We just cooked $what using the following recipe: '$recipeText'.") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/RecipeComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components 2 | 3 | import com.ivan.nikolov.components.base.RecipeFinder 4 | 5 | 6 | trait RecipeComponent { 7 | val recipe: RecipeFinder 8 | 9 | class RecipeFinderImpl extends RecipeFinder { 10 | override def findRecipe(dish: String): String = dish match { 11 | case "chips" => "Fry the potatoes for 10 minutes." 12 | case "fish" => "Clean the fish and put in the oven for 30 minutes." 13 | case "sandwich" => "Put butter, ham and cheese on the bread, toast and add tomatoes." 14 | case _ => throw new RuntimeException(s"${dish} is unknown recipe.") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/Robot.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components 2 | 3 | class Robot extends RobotRegistry { 4 | 5 | def cook(what: String) = cooker.cook(what) 6 | def getTime() = time.getTime() 7 | } 8 | 9 | 10 | object RobotExample { 11 | def main(args: Array[String]): Unit = { 12 | val robot = new Robot 13 | System.out.println(robot.getTime()) 14 | System.out.println(robot.cook("chips")) 15 | System.out.println(robot.cook("sandwich")) 16 | } 17 | } -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/RobotRegistry.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components 2 | 3 | import com.ivan.nikolov.components.base.{Time, RecipeFinder, Cooker} 4 | 5 | class RobotRegistry extends TimeComponent with RecipeComponent with CookingComponent { 6 | override val time: Time = new TimeImpl 7 | override val recipe: RecipeFinder = new RecipeFinderImpl 8 | override val cooker: Cooker = new CookerImpl 9 | } 10 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/TimeComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components 2 | 3 | import java.time.LocalDateTime 4 | import java.time.format.DateTimeFormatter 5 | 6 | import com.ivan.nikolov.components.base.Time 7 | 8 | trait TimeComponent { 9 | val time: Time 10 | 11 | class TimeImpl extends Time { 12 | val formatter = DateTimeFormatter.ofPattern("HH:mm:ss") 13 | override def getTime(): String = s"The time is: ${LocalDateTime.now().format(formatter)}" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/base/Cooker.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components.base 2 | 3 | import com.ivan.nikolov.components.model.Food 4 | 5 | trait Cooker { 6 | def cook(what: String): Food 7 | } 8 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/base/RecipeFinder.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components.base 2 | 3 | trait RecipeFinder { 4 | def findRecipe(dish: String): String 5 | } 6 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/base/Time.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components.base 2 | 3 | trait Time { 4 | def getTime(): String 5 | } 6 | -------------------------------------------------------------------------------- /aop/src/main/scala/com/ivan/nikolov/components/model/Food.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.components.model 2 | 3 | case class Food(name: String) 4 | -------------------------------------------------------------------------------- /behavioral-design-patterns/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | behavioral-design-patterns 13 | 14 | 15 | 16 | commons-codec 17 | commons-codec 18 | ${commons.codec.version} 19 | 20 | 21 | org.slf4j 22 | slf4j-log4j12 23 | ${slf4j.version} 24 | 25 | 26 | com.typesafe.scala-logging 27 | scala-logging_2.11 28 | ${scalalogging.version} 29 | 30 | 31 | org.json4s 32 | json4s-native_2.11 33 | ${json4s.native.version} 34 | 35 | 36 | org.json4s 37 | json4s-jackson_2.11 38 | ${json4s.jackson.version} 39 | 40 | 41 | com.github.tototoshi 42 | scala-csv_2.11 43 | 1.2.2 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/resources/com/ivan/nikolov/behavioral/strategy/people.csv: -------------------------------------------------------------------------------- 1 | Ivan,26,London 2 | Maria,23,Edinburgh 3 | John,36,New York 4 | Anna,24,Moscow -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/resources/com/ivan/nikolov/behavioral/strategy/people.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Ivan", 4 | "age": 26, 5 | "address": "London" 6 | }, 7 | { 8 | "name": "Maria", 9 | "age": 23, 10 | "address": "Edinburgh" 11 | }, 12 | { 13 | "name": "John", 14 | "age": 36, 15 | "address": "New York" 16 | }, 17 | { 18 | "name": "Anna", 19 | "age": 24, 20 | "address": "Moscow" 21 | } 22 | ] -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/resources/com/ivan/nikolov/behavioral/template/people.csv: -------------------------------------------------------------------------------- 1 | Ivan,26,London 2 | Maria,23,Edinburgh 3 | John,36,New York 4 | Anna,24,Moscow -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/resources/com/ivan/nikolov/behavioral/template/people.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Ivan", 4 | "age": 26, 5 | "address": "London" 6 | }, 7 | { 8 | "name": "Maria", 9 | "age": 23, 10 | "address": "Edinburgh" 11 | }, 12 | { 13 | "name": "John", 14 | "age": 36, 15 | "address": "New York" 16 | }, 17 | { 18 | "name": "Anna", 19 | "age": 24, 20 | "address": "Moscow" 21 | } 22 | ] -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=[%t] %-5p %c %x - %m%n -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/chain_of_responsibility/ATM.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.chain_of_responsibility 2 | 3 | import scala.io.Source 4 | 5 | class ATM { 6 | val dispenser: Dispenser = { 7 | val d1 = new Dispenser5(None) 8 | val d2 = new Dispenser10(Some(d1)) 9 | val d3 = new Dispenser20(Some(d2)) 10 | new Dispenser50(Some(d3)) 11 | } 12 | 13 | def requestMoney(money: Money): Unit = { 14 | if (money.amount % 5 != 0) { 15 | System.err.println("The smallest nominal is 5 and we cannot satisfy your request.") 16 | } else { 17 | dispenser.dispense(money) 18 | } 19 | } 20 | } 21 | 22 | class PartialFunctionATM extends PartialFunctionDispenser { 23 | 24 | val dispenser = dispense(50).andThen(dispense(20)).andThen(dispense(10)).andThen(dispense(5)) 25 | 26 | def requestMoney(money: Money): Unit = { 27 | if (money.amount % 5 != 0) { 28 | System.err.println("The smallest nominal is 5 and we cannot satisfy your request.") 29 | } else { 30 | dispenser(money) 31 | } 32 | } 33 | } 34 | 35 | object ATMExample { 36 | def main(args: Array[String]): Unit = { 37 | val atm = new ATM 38 | printHelp() 39 | Source.stdin.getLines().foreach { 40 | case line => 41 | processLine(line, atm) 42 | } 43 | } 44 | 45 | def printHelp(): Unit = { 46 | System.out.println("Usage: ") 47 | System.out.println("1. Write an amount to withdraw...") 48 | System.out.println("2. Write EXIT to quit the application.") 49 | } 50 | 51 | def processLine(line: String, atm: ATM): Unit = { 52 | line match { 53 | case "EXIT" => 54 | System.out.println("Bye!") 55 | System.exit(0) 56 | case l => 57 | try { 58 | atm.requestMoney(Money(l.toInt)) 59 | System.out.println("Thanks!") 60 | } catch { 61 | case _: Throwable => 62 | System.err.println(s"Invalid input: $l.") 63 | printHelp() 64 | } 65 | 66 | } 67 | } 68 | } 69 | 70 | object PartialFunctionATMExample { 71 | def main(args: Array[String]): Unit = { 72 | val atm = new PartialFunctionATM 73 | printHelp() 74 | Source.stdin.getLines().foreach { 75 | case line => 76 | processLine(line, atm) 77 | } 78 | } 79 | 80 | def printHelp(): Unit = { 81 | System.out.println("Usage: ") 82 | System.out.println("1. Write an amount to withdraw...") 83 | System.out.println("2. Write EXIT to quit the application.") 84 | } 85 | 86 | def processLine(line: String, atm: PartialFunctionATM): Unit = { 87 | line match { 88 | case "EXIT" => 89 | System.out.println("Bye!") 90 | System.exit(0) 91 | case l => 92 | try { 93 | atm.requestMoney(Money(l.toInt)) 94 | System.out.println("Thanks!") 95 | } catch { 96 | case _: Throwable => 97 | System.err.println(s"Invalid input: $l.") 98 | printHelp() 99 | } 100 | 101 | } 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/chain_of_responsibility/Dispenser.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.chain_of_responsibility 2 | 3 | trait Dispenser { 4 | val amount: Int 5 | val next: Option[Dispenser] 6 | 7 | def dispense(money: Money): Unit = { 8 | if (money.amount >= amount) { 9 | val notes = money.amount / amount 10 | val left = money.amount % amount 11 | System.out.println(s"Dispensing $notes note/s of $amount.") 12 | if (left > 0) next.map(_.dispense(Money(left))) 13 | } else { 14 | next.foreach(_.dispense(money)) 15 | } 16 | } 17 | } 18 | 19 | class Dispenser50(val next: Option[Dispenser]) extends Dispenser { 20 | override val amount = 50 21 | } 22 | 23 | class Dispenser20(val next: Option[Dispenser]) extends Dispenser { 24 | override val amount: Int = 20 25 | } 26 | 27 | class Dispenser10(val next: Option[Dispenser]) extends Dispenser { 28 | override val amount: Int = 10 29 | } 30 | 31 | class Dispenser5(val next: Option[Dispenser]) extends Dispenser { 32 | override val amount: Int = 5 33 | } 34 | 35 | trait PartialFunctionDispenser { 36 | 37 | def dispense(dispenserAmount: Int): PartialFunction[Money, Money] = { 38 | case Money(amount) if amount >= dispenserAmount => 39 | val notes = amount / dispenserAmount 40 | val left = amount % dispenserAmount 41 | System.out.println(s"Dispensing $notes note/s of $dispenserAmount.") 42 | Money(left) 43 | case m @ Money(amount) => 44 | m 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/chain_of_responsibility/Money.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.chain_of_responsibility 2 | 3 | case class Money(amount: Int) 4 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/command/Robot.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.command 2 | 3 | case class Robot() { 4 | def cleanUp(): Unit = System.out.println("Cleaning up.") 5 | 6 | def pourJuice(): Unit = System.out.println("Pouring juice.") 7 | 8 | def makeSandwich(): Unit = System.out.println("Making a sandwich.") 9 | } 10 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/command/RobotCommand.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.command 2 | 3 | import scala.collection.mutable.ListBuffer 4 | 5 | trait RobotCommand { 6 | def execute(): Unit 7 | } 8 | 9 | case class MakeSandwichCommand(robot: Robot) extends RobotCommand { 10 | override def execute(): Unit = robot.makeSandwich() 11 | } 12 | 13 | case class PourJuiceCommand(robot: Robot) extends RobotCommand { 14 | override def execute(): Unit = robot.pourJuice() 15 | } 16 | 17 | case class CleanUpCommand(robot: Robot) extends RobotCommand { 18 | override def execute(): Unit = robot.cleanUp() 19 | } 20 | 21 | class RobotController { 22 | val history = ListBuffer[RobotCommand]() 23 | 24 | def issueCommand(command: RobotCommand): Unit = { 25 | command +=: history 26 | command.execute() 27 | } 28 | 29 | def showHistory(): Unit = { 30 | history.foreach(println) 31 | } 32 | } 33 | 34 | class RobotByNameController { 35 | val history = ListBuffer[() => Unit]() 36 | 37 | def issueCommand(command: => Unit): Unit = { 38 | command _ +=: history 39 | command 40 | } 41 | 42 | def showHistory(): Unit = { 43 | history.foreach(println) 44 | } 45 | } 46 | 47 | object RobotExample { 48 | def main(args: Array[String]): Unit = { 49 | val robot = Robot() 50 | val robotController = new RobotController 51 | 52 | robotController.issueCommand(MakeSandwichCommand(robot)) 53 | robotController.issueCommand(PourJuiceCommand(robot)) 54 | System.out.println("I'm eating and having some juice.") 55 | robotController.issueCommand(CleanUpCommand(robot)) 56 | 57 | System.out.println("Here is what I asked my robot to do:") 58 | robotController.showHistory() 59 | } 60 | } 61 | 62 | object RobotByNameExample { 63 | def main(args: Array[String]): Unit = { 64 | val robot = Robot() 65 | val robotController = new RobotByNameController 66 | 67 | robotController.issueCommand(MakeSandwichCommand(robot).execute()) 68 | robotController.issueCommand(PourJuiceCommand(robot).execute()) 69 | System.out.println("I'm eating and having some juice.") 70 | robotController.issueCommand(CleanUpCommand(robot).execute()) 71 | 72 | System.out.println("Here is what I asked my robot to do:") 73 | robotController.showHistory() 74 | } 75 | } -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/interpreter/Expression.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.interpreter 2 | 3 | trait Expression { 4 | def interpret(): Int 5 | } 6 | 7 | class Add(right: Expression, left: Expression) extends Expression { 8 | override def interpret(): Int = left.interpret() + right.interpret() 9 | } 10 | 11 | class Subtract(right: Expression, left: Expression) extends Expression { 12 | override def interpret(): Int = left.interpret() - right.interpret() 13 | } 14 | 15 | class Multiply(right: Expression, left: Expression) extends Expression { 16 | override def interpret(): Int = left.interpret() * right.interpret() 17 | } 18 | 19 | class Number(n: Int) extends Expression { 20 | override def interpret(): Int = n 21 | } 22 | 23 | object Expression { 24 | def apply(operator: String, left: => Expression, right: => Expression): Option[Expression] = 25 | operator match { 26 | case "+" => Some(new Add(right, left)) 27 | case "-" => Some(new Subtract(right, left)) 28 | case "*" => Some(new Multiply(right, left)) 29 | case i if i.matches("\\d+") => Some(new Number(i.toInt)) 30 | case _ => None 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/interpreter/RPNParser.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.interpreter 2 | 3 | import java.util.StringTokenizer 4 | 5 | import scala.collection.JavaConverters._ 6 | import scala.collection.mutable 7 | 8 | class RPNParser { 9 | 10 | def parse(expression: String): Expression = { 11 | val tokenizer = new StringTokenizer(expression) 12 | tokenizer.asScala.foldLeft(mutable.Stack[Expression]()) { 13 | case (result, token) => 14 | val item = Expression(token.toString, result.pop(), result.pop()) 15 | item.foreach(result.push) 16 | result 17 | }.pop() 18 | } 19 | } 20 | 21 | class RPNInterpreter { 22 | def interpret(expression: Expression): Int = expression.interpret() 23 | } 24 | 25 | object RPNExample { 26 | def main(args: Array[String]): Unit = { 27 | val expr1 = "1 2 + 3 * 9 10 + -" // (1 + 2) * 3 - (9 + 10) = -10 28 | val expr2 = "1 2 3 4 5 * * - +" // 1 + 2 - 3 * 4 * 5 = -57 29 | val expr3 = "12 -" // invalid 30 | val parser = new RPNParser 31 | val interpreter = new RPNInterpreter 32 | 33 | System.out.println(s"The result of '${expr1}' is: ${interpreter.interpret(parser.parse(expr1))}") 34 | System.out.println(s"The result of '${expr2}' is: ${interpreter.interpret(parser.parse(expr2))}") 35 | try { 36 | System.out.println(s"The result is: ${interpreter.interpret(parser.parse(expr3))}") 37 | } catch { 38 | case _: Throwable => System.out.println(s"'$expr3' is invalid.") 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/iterator/Student.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.iterator 2 | 3 | import scala.collection.mutable.ListBuffer 4 | 5 | case class Student(name: String, age: Int) 6 | 7 | class StudentIterator(students: Array[Student]) extends Iterator[Student] { 8 | var currentPos = 0 9 | 10 | override def hasNext: Boolean = currentPos < students.size 11 | 12 | override def next(): Student = { 13 | val result = students(currentPos) 14 | currentPos = currentPos + 1 15 | result 16 | } 17 | } 18 | 19 | class ClassRoom extends Iterable[Student] { 20 | 21 | val students: ListBuffer[Student] = ListBuffer[Student]() 22 | 23 | def add(student: Student): Unit = { 24 | student +=: students 25 | } 26 | 27 | override def iterator: Iterator[Student] = new StudentIterator(students.toArray) 28 | } 29 | 30 | object ClassRoomExample { 31 | def main(args: Array[String]): Unit = { 32 | val classRoom = new ClassRoom 33 | classRoom.add(Student("Ivan", 26)) 34 | classRoom.add(Student("Maria", 26)) 35 | classRoom.add(Student("John", 25)) 36 | classRoom.foreach(println) 37 | } 38 | } -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/mediator/Student.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.mediator 2 | 3 | import scala.collection.mutable.Map 4 | import scala.collection.mutable.Set 5 | 6 | trait Notifiable { 7 | def notify(message: String) 8 | } 9 | 10 | case class Student(name: String, age: Int) extends Notifiable { 11 | override def notify(message: String): Unit = { 12 | System.out.println(s"Student $name was notified with message: '$message'.") 13 | } 14 | } 15 | 16 | case class Group(name: String) 17 | 18 | trait Mediator { 19 | def addStudentToGroup(student: Student, group: Group) 20 | 21 | def isStudentInGroup(student: Student, group: Group): Boolean 22 | 23 | def removeStudentFromGroup(student: Student, group: Group) 24 | 25 | def getStudentsInGroup(group: Group): List[Student] 26 | 27 | def getGroupsForStudent(student: Student): List[Group] 28 | 29 | def notifyStudentsInGroup(group: Group, message: String) 30 | } 31 | 32 | class School extends Mediator { 33 | val studentsToGroups: Map[Student, Set[Group]] = Map() 34 | val groupsToStudents: Map[Group, Set[Student]] = Map() 35 | 36 | override def addStudentToGroup(student: Student, group: Group): Unit = { 37 | studentsToGroups.getOrElseUpdate(student, Set()) += group 38 | groupsToStudents.getOrElseUpdate(group, Set()) += student 39 | } 40 | 41 | override def isStudentInGroup(student: Student, group: Group): Boolean = 42 | groupsToStudents.getOrElse(group, Set()).contains(student) && studentsToGroups.getOrElse(student, Set()).contains(group) 43 | 44 | override def getStudentsInGroup(group: Group): List[Student] = 45 | groupsToStudents.getOrElse(group, Set()).toList 46 | 47 | override def getGroupsForStudent(student: Student): List[Group] = 48 | studentsToGroups.getOrElse(student, Set()).toList 49 | 50 | override def notifyStudentsInGroup(group: Group, message: String): Unit = { 51 | groupsToStudents.getOrElse(group, Set()).foreach(_.notify(message)) 52 | } 53 | 54 | override def removeStudentFromGroup(student: Student, group: Group): Unit = { 55 | studentsToGroups.getOrElse(student, Set()) -= group 56 | groupsToStudents.getOrElse(group, Set()) -= student 57 | } 58 | } 59 | 60 | object SchoolExample { 61 | def main(args: Array[String]): Unit = { 62 | val school = new School 63 | // create students 64 | val student1 = Student("Ivan", 26) 65 | val student2 = Student("Maria", 26) 66 | val student3 = Student("John", 25) 67 | // create groups 68 | val group1 = Group("Scala design patterns") 69 | val group2 = Group("Databases") 70 | val group3 = Group("Cloud computing") 71 | 72 | school.addStudentToGroup(student1, group1) 73 | school.addStudentToGroup(student1, group2) 74 | school.addStudentToGroup(student1, group3) 75 | 76 | school.addStudentToGroup(student2, group1) 77 | school.addStudentToGroup(student2, group3) 78 | 79 | school.addStudentToGroup(student3, group1) 80 | school.addStudentToGroup(student3, group2) 81 | 82 | // notify 83 | school.notifyStudentsInGroup(group1, "Design patterns in Scala are amazing!") 84 | 85 | // see groups 86 | System.out.println(s"$student3 is in groups: ${school.getGroupsForStudent(student3)}") 87 | // remove from group 88 | school.removeStudentFromGroup(student3, group2) 89 | System.out.println(s"$student3 is in groups: ${school.getGroupsForStudent(student3)}") 90 | 91 | // see students in group 92 | System.out.println(s"Students in $group1 are ${school.getStudentsInGroup(group1)}") 93 | } 94 | } -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/memento/Memento.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.memento 2 | 3 | import scala.collection.mutable 4 | 5 | trait Memento[T] { 6 | protected val state: T 7 | def getState(): T = state 8 | } 9 | 10 | trait Caretaker[T] { 11 | val states: mutable.Stack[Memento[T]] = mutable.Stack[Memento[T]]() 12 | } 13 | 14 | trait Originator[T] { 15 | def createMemento: Memento[T] 16 | def restore(memento: Memento[T]) 17 | } 18 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/memento/TextEditor.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.memento 2 | 3 | class TextEditor extends Originator[String] { 4 | private var builder: StringBuilder = new StringBuilder 5 | 6 | def append(text: String): Unit = { 7 | builder.append(text) 8 | } 9 | 10 | def delete(): Unit = { 11 | if (builder.nonEmpty) { 12 | builder.deleteCharAt(builder.length - 1) 13 | } 14 | } 15 | 16 | override def createMemento: Memento[String] = 17 | new TextEditorMemento(builder.toString) 18 | 19 | override def restore(memento: Memento[String]): Unit = 20 | this.builder = new StringBuilder(memento.getState()) 21 | 22 | def text(): String = builder.toString 23 | 24 | private class TextEditorMemento(val state: String) extends Memento[String] 25 | } 26 | 27 | class TextEditorManipulator extends Caretaker[String] { 28 | private val textEditor = new TextEditor 29 | 30 | def save(): Unit = { 31 | states.push(textEditor.createMemento) 32 | } 33 | 34 | def undo(): Unit = { 35 | if (states.nonEmpty) { 36 | textEditor.restore(states.pop()) 37 | } 38 | } 39 | 40 | def append(text: String): Unit = { 41 | save() 42 | textEditor.append(text) 43 | } 44 | 45 | def delete(): Unit = { 46 | save() 47 | textEditor.delete() 48 | } 49 | 50 | def readText(): String = textEditor.text() 51 | } 52 | 53 | object TextEditorExample { 54 | def main(args: Array[String]): Unit = { 55 | val textEditorManipulator = new TextEditorManipulator 56 | textEditorManipulator.append("This is a chapter about memento.") 57 | System.out.println(s"The text is: '${textEditorManipulator.readText()}'") 58 | // delete 2 characters 59 | System.out.println("Deleting 2 characters...") 60 | textEditorManipulator.delete() 61 | textEditorManipulator.delete() 62 | // see the text 63 | System.out.println(s"The text is: '${textEditorManipulator.readText()}'") 64 | // undo 65 | System.out.println("Undoing...") 66 | textEditorManipulator.undo() 67 | System.out.println(s"The text is: '${textEditorManipulator.readText()}'") 68 | // undo again 69 | System.out.println("Undoing...") 70 | textEditorManipulator.undo() 71 | System.out.println(s"The text is: '${textEditorManipulator.readText()}'") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/null_object/DataGenerator.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.null_object 2 | 3 | import java.util.concurrent.ConcurrentLinkedQueue 4 | 5 | import scala.util.Random 6 | 7 | class DataGenerator extends Runnable { 8 | 9 | val MAX_VAL = 10 10 | val MAX_TIME = 10000 11 | 12 | private var isStop = false 13 | 14 | private val queue: ConcurrentLinkedQueue[Int] = new ConcurrentLinkedQueue[Int]() 15 | 16 | override def run(): Unit = { 17 | val random = new Random() 18 | while (!isStop) { 19 | Thread.sleep(random.nextInt(MAX_TIME)) 20 | queue.add(random.nextInt(MAX_VAL)) 21 | } 22 | } 23 | 24 | def getMessage(): Option[Message] = 25 | Option(queue.poll()).map { 26 | case number => Message(number) 27 | } 28 | 29 | def requestStop(): Unit = this.synchronized { 30 | isStop = true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/null_object/Message.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.null_object 2 | 3 | import scala.util.Random 4 | 5 | case class Message(number: Int) { 6 | def print(): String = s"This is a message with number: $number." 7 | } 8 | 9 | object MessageExample { 10 | val TIMES_TO_TRY = 10 11 | val MAX_TIME = 5000 12 | 13 | def main(args: Array[String]): Unit = { 14 | val generator = new DataGenerator 15 | // start the generator in another thread 16 | new Thread(generator).start() 17 | 18 | val random = new Random() 19 | (0 to TIMES_TO_TRY).foreach { 20 | case time => 21 | Thread.sleep(random.nextInt(MAX_TIME)) 22 | System.out.println("Getting next message...") 23 | generator.getMessage().foreach(m => System.out.println(m.print())) 24 | } 25 | 26 | generator.requestStop() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/observer/Observer.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.observer 2 | 3 | import scala.collection.mutable.ListBuffer 4 | 5 | trait Observer[T] { 6 | def handleUpdate(subject: T) 7 | } 8 | 9 | trait Observable[T] { 10 | this: T => 11 | 12 | private val observers = ListBuffer[Observer[T]]() 13 | 14 | def addObserver(observer: Observer[T]): Unit = { 15 | observers.+=:(observer) 16 | } 17 | 18 | def notifyObservers(): Unit = { 19 | observers.foreach(_.handleUpdate(this)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/observer/Post.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.observer 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | 5 | import scala.collection.mutable.ListBuffer 6 | 7 | case class Post(user: User, text: String) extends Observable[Post] { 8 | 9 | val comments = ListBuffer[Comment]() 10 | 11 | def addComment(comment: Comment): Unit = { 12 | comments.+=:(comment) 13 | notifyObservers() 14 | } 15 | } 16 | 17 | case class Comment(user: User, text: String) 18 | 19 | case class User(name: String) extends Observer[Post] { 20 | override def handleUpdate(subject: Post): Unit = { 21 | System.out.println(s"Hey, I'm ${name}. The post got some new comments: ${subject.comments}") 22 | } 23 | } 24 | 25 | object PostExample extends LazyLogging { 26 | def main(args: Array[String]): Unit = { 27 | val userIvan = User("Ivan") 28 | val userMaria = User("Maria") 29 | val userJohn = User("John") 30 | 31 | logger.info("Create a post") 32 | val post = Post(userIvan, "This is a post about the observer design pattern") 33 | logger.info("Add a comment") 34 | post.addComment(Comment(userIvan, "I hope you like the post!")) 35 | 36 | logger.info("John and Maria subscribe to the comments.") 37 | post.addObserver(userJohn) 38 | post.addObserver(userMaria) 39 | 40 | logger.info("Add a comment") 41 | post.addComment(Comment(userIvan, "Why are you so quiet? Do you like it?")) 42 | logger.info("Add a comment") 43 | post.addComment(Comment(userMaria, "It is amazing! Thanks!")) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/state/State.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.state 2 | 3 | import com.ivan.nikolov.behavioral.state.model.MediaPlayer 4 | 5 | trait State[T] { 6 | def press(context: T) 7 | } 8 | 9 | class Playing extends State[MediaPlayer] { 10 | override def press(context: MediaPlayer): Unit = { 11 | System.out.println("Pressing pause.") 12 | context.setState(new Paused) 13 | } 14 | } 15 | 16 | class Paused extends State[MediaPlayer] { 17 | override def press(context: MediaPlayer): Unit = { 18 | System.out.println("Pressing play.") 19 | context.setState(new Playing) 20 | } 21 | } 22 | 23 | 24 | object MediaPlayerExample { 25 | def main(args: Array[String]): Unit = { 26 | val player = MediaPlayer() 27 | 28 | player.pressPlayOrPauseButton() 29 | player.pressPlayOrPauseButton() 30 | player.pressPlayOrPauseButton() 31 | player.pressPlayOrPauseButton() 32 | } 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/state/model/MediaPlayer.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.state.model 2 | 3 | import com.ivan.nikolov.behavioral.state.{Paused, State} 4 | 5 | case class MediaPlayer() { 6 | private var state: State[MediaPlayer] = new Paused 7 | 8 | def pressPlayOrPauseButton(): Unit = { 9 | state.press(this) 10 | } 11 | 12 | def setState(state: State[MediaPlayer]): Unit = { 13 | this.state = state 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/strategy/Parser.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.strategy 2 | 3 | import java.io.InputStreamReader 4 | 5 | import com.github.tototoshi.csv.CSVReader 6 | import com.ivan.nikolov.behavioral.strategy.model.Person 7 | 8 | import org.json4s._ 9 | import org.json4s.jackson.JsonMethods 10 | 11 | trait Parser[T] { 12 | def parse(file: String): List[T] 13 | } 14 | 15 | class CSVParser extends Parser[Person] { 16 | override def parse(file: String): List[Person] = 17 | CSVReader.open(new InputStreamReader(this.getClass.getResourceAsStream(file))).all().map { 18 | case List(name, age, address) => 19 | Person(name, age.toInt, address) 20 | } 21 | } 22 | 23 | class JsonParser extends Parser[Person] { 24 | implicit val formats = DefaultFormats 25 | 26 | override def parse(file: String): List[Person] = 27 | JsonMethods.parse(StreamInput(this.getClass.getResourceAsStream(file))).extract[List[Person]] 28 | } 29 | 30 | object Parser { 31 | def apply(filename: String): Parser[Person] = 32 | filename match { 33 | case f if f.endsWith(".json") => new JsonParser 34 | case f if f.endsWith(".csv") => new CSVParser 35 | case f => throw new RuntimeException(s"Unknown format: $f") 36 | } 37 | } 38 | 39 | class PersonApplication[T](parser: Parser[T]) { 40 | 41 | def write(file: String): Unit = { 42 | System.out.println(s"Got the following data ${parser.parse(file)}") 43 | } 44 | } 45 | 46 | object ParserExample { 47 | def main(args: Array[String]): Unit = { 48 | val csvPeople = Parser("people.csv") 49 | val jsonPeople = Parser("people.json") 50 | 51 | val applicationCsv = new PersonApplication(csvPeople) 52 | val applicationJson = new PersonApplication(jsonPeople) 53 | 54 | System.out.println("Using the csv: ") 55 | applicationCsv.write("people.csv") 56 | 57 | System.out.println("Using the json: ") 58 | applicationJson.write("people.json") 59 | } 60 | } -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/strategy/ParsingStrategy.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.strategy 2 | 3 | import java.io.InputStreamReader 4 | 5 | import com.github.tototoshi.csv.CSVReader 6 | import com.ivan.nikolov.behavioral.strategy.model.Person 7 | import org.json4s.{StreamInput, DefaultFormats} 8 | import org.json4s.jackson.JsonMethods 9 | 10 | class Application[T](strategy: (String) => List[T]) { 11 | def write(file: String): Unit = { 12 | System.out.println(s"Got the following data ${strategy(file)}") 13 | } 14 | } 15 | 16 | object StrategyFactory { 17 | implicit val formats = DefaultFormats 18 | 19 | def apply(filename: String): (String) => List[Person] = 20 | filename match { 21 | case f if f.endsWith(".json") => parseJson 22 | case f if f.endsWith(".csv") => parseCsv 23 | case f => throw new RuntimeException(s"Unknown format: $f") 24 | } 25 | 26 | def parseJson(file: String): List[Person] = 27 | JsonMethods.parse(StreamInput(this.getClass.getResourceAsStream(file))).extract[List[Person]] 28 | 29 | def parseCsv(file: String): List[Person] = 30 | CSVReader.open(new InputStreamReader(this.getClass.getResourceAsStream(file))).all().map { 31 | case List(name, age, address) => 32 | Person(name, age.toInt, address) 33 | } 34 | } 35 | 36 | object StrategyExample { 37 | def main(args: Array[String]): Unit = { 38 | val applicationCsv = new Application[Person](StrategyFactory("people.csv")) 39 | val applicationJson = new Application[Person](StrategyFactory("people.json")) 40 | 41 | System.out.println("Using the csv: ") 42 | applicationCsv.write("people.csv") 43 | 44 | System.out.println("Using the json: ") 45 | applicationJson.write("people.json") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/strategy/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.strategy.model 2 | 3 | case class Person(name: String, age: Int, address: String) 4 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/template/DataFinder.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.template 2 | 3 | import java.io.{InputStreamReader, ByteArrayInputStream} 4 | 5 | import com.github.tototoshi.csv.CSVReader 6 | import com.ivan.nikolov.behavioral.template.model.Person 7 | import org.json4s.{StringInput, DefaultFormats} 8 | import org.json4s.jackson.JsonMethods 9 | 10 | abstract class DataFinder[T, Y] { 11 | 12 | def find(f: T => Option[Y]): Option[Y] = { 13 | try { 14 | val data = readData() 15 | val parsed = parse(data) 16 | f(parsed) 17 | } finally { 18 | cleanup() 19 | } 20 | } 21 | 22 | def readData(): Array[Byte] 23 | def parse(data: Array[Byte]): T 24 | def cleanup() 25 | } 26 | 27 | class JsonDataFinder extends DataFinder[List[Person], Person] { 28 | implicit val formats = DefaultFormats 29 | 30 | override def readData(): Array[Byte] = { 31 | val stream = this.getClass.getResourceAsStream("people.json") 32 | Stream.continually(stream.read).takeWhile(_ != -1).map(_.toByte).toArray 33 | } 34 | 35 | override def cleanup(): Unit = { 36 | System.out.println("Reading json: nothing to do.") 37 | } 38 | 39 | override def parse(data: Array[Byte]): List[Person] = 40 | JsonMethods.parse(StringInput(new String(data, "UTF-8"))).extract[List[Person]] 41 | } 42 | 43 | class CSVDataFinder extends DataFinder[List[Person], Person] { 44 | override def readData(): Array[Byte] = { 45 | val stream = this.getClass.getResourceAsStream("people.csv") 46 | Stream.continually(stream.read).takeWhile(_ != -1).map(_.toByte).toArray 47 | } 48 | 49 | override def cleanup(): Unit = { 50 | System.out.println("Reading csv: nothing to do.") 51 | } 52 | 53 | override def parse(data: Array[Byte]): List[Person] = 54 | CSVReader.open(new InputStreamReader(new ByteArrayInputStream(data))).all().map { 55 | case List(name, age, address) => 56 | Person(name, age.toInt, address) 57 | } 58 | } 59 | 60 | 61 | object DataFinderExample { 62 | def main(args: Array[String]): Unit = { 63 | val jsonDataFinder: DataFinder[List[Person], Person] = new JsonDataFinder 64 | val csvDataFinder: DataFinder[List[Person], Person] = new CSVDataFinder 65 | 66 | System.out.println(s"Find a person with name Ivan in the json: ${jsonDataFinder.find(_.find(_.name == "Ivan"))}") 67 | System.out.println(s"Find a person with name James in the json: ${jsonDataFinder.find(_.find(_.name == "James"))}") 68 | 69 | System.out.println(s"Find a person with name Maria in the csv: ${csvDataFinder.find(_.find(_.name == "Maria"))}") 70 | System.out.println(s"Find a person with name Alice in the csv: ${csvDataFinder.find(_.find(_.name == "Alice"))}") 71 | } 72 | } -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/template/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.template.model 2 | 3 | case class Person(name: String, age: Int, address: String) 4 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/value_object/Date.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.value_object 2 | 3 | case class Date( 4 | day: Int, 5 | month: String, 6 | year: Int 7 | ) 8 | 9 | object DateExample { 10 | def main(args: Array[String]): Unit = { 11 | val thirdOfMarch = Date(3, "MARCH", 2016) 12 | val fourthOfJuly = Date(4, "JULY", 2016) 13 | val newYear1 = Date(31, "DECEMBER", 2015) 14 | val newYear2 = Date(31, "DECEMBER", 2015) 15 | 16 | System.out.println(s"The 3rd of March 2016 is the same as the 4th of July 2016: ${thirdOfMarch == fourthOfJuly}") 17 | System.out.println(s"The new year of 2015 is here twice: ${newYear1 == newYear2}") 18 | } 19 | } 20 | 21 | 22 | class BadDate( 23 | day: Int, 24 | month: String, 25 | year: Int 26 | ) 27 | 28 | object BadDateExample { 29 | def main(args: Array[String]): Unit = { 30 | val thirdOfMarch = new BadDate(3, "MARCH", 2016) 31 | val fourthOfJuly = new BadDate(4, "JULY", 2016) 32 | val newYear1 = new BadDate(31, "DECEMBER", 2015) 33 | val newYear2 = new BadDate(31, "DECEMBER", 2015) 34 | 35 | System.out.println(s"The 3rd of March 2016 is the same as the 4th of July 2016: ${thirdOfMarch == fourthOfJuly}") 36 | System.out.println(s"The new year of 2015 is here twice: ${newYear1 == newYear2}") 37 | } 38 | } -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/visitor/Element.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.visitor 2 | 3 | abstract class Element(val text: String) { 4 | def accept(visitor: Visitor) 5 | } 6 | 7 | class Title(text: String) extends Element(text) { 8 | override def accept(visitor: Visitor): Unit = { 9 | visitor.visit(this) 10 | } 11 | } 12 | 13 | class Text(text: String) extends Element(text) { 14 | override def accept(visitor: Visitor): Unit = { 15 | visitor.visit(this) 16 | } 17 | } 18 | 19 | class Hyperlink(text: String, val url: String) extends Element(text) { 20 | override def accept(visitor: Visitor): Unit = { 21 | visitor.visit(this) 22 | } 23 | } 24 | 25 | class Document(parts: List[Element]) { 26 | 27 | def accept(visitor: Visitor): Unit = { 28 | parts.foreach(p => p.accept(visitor)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/visitor/Visitor.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.visitor 2 | 3 | trait Visitor { 4 | def visit(title: Title) 5 | def visit(text: Text) 6 | def visit(hyperlink: Hyperlink) 7 | } 8 | 9 | class HtmlExporterVisitor extends Visitor { 10 | val line = System.getProperty("line.separator") 11 | val builder = new StringBuilder 12 | 13 | def getHtml(): String = builder.toString 14 | 15 | override def visit(title: Title): Unit = { 16 | builder.append(s"

${title.text}

").append(line) 17 | } 18 | 19 | override def visit(text: Text): Unit = { 20 | builder.append(s"

${text.text}

").append(line) 21 | } 22 | 23 | override def visit(hyperlink: Hyperlink): Unit = { 24 | builder.append(s"""${hyperlink.text}""").append(line) 25 | } 26 | } 27 | 28 | class PlainTextExporterVisitor extends Visitor { 29 | val line = System.getProperty("line.separator") 30 | val builder = new StringBuilder 31 | 32 | def getText(): String = builder.toString 33 | 34 | override def visit(title: Title): Unit = { 35 | builder.append(title.text).append(line) 36 | } 37 | 38 | override def visit(text: Text): Unit = { 39 | builder.append(text.text).append(line) 40 | } 41 | 42 | override def visit(hyperlink: Hyperlink): Unit = { 43 | builder.append(s"${hyperlink.text} (${hyperlink.url})").append(line) 44 | } 45 | } 46 | 47 | object VisitorExample { 48 | def main(args: Array[String]): Unit = { 49 | val document = new Document( 50 | List( 51 | new Title("The Visitor Pattern Example"), 52 | new Text("The visitor pattern helps us add extra functionality without changing the classes."), 53 | new Hyperlink("Go check it online!", "https://www.google.com/"), 54 | new Text("Thanks!") 55 | ) 56 | ) 57 | val htmlExporter = new HtmlExporterVisitor 58 | val plainTextExporter = new PlainTextExporterVisitor 59 | 60 | System.out.println(s"Export to html:") 61 | document.accept(htmlExporter) 62 | System.out.println(htmlExporter.getHtml()) 63 | 64 | System.out.println(s"Export to plain:") 65 | document.accept(plainTextExporter) 66 | System.out.println(plainTextExporter.getText()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/visitor/better/Element.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.visitor.better 2 | 3 | 4 | abstract class Element(text: String) { 5 | def accept(visitor: Element => Unit): Unit = { 6 | visitor(this) 7 | } 8 | } 9 | 10 | case class Title(text: String) extends Element(text) 11 | case class Text(text: String) extends Element(text) 12 | case class Hyperlink(text: String, val url: String) extends Element(text) 13 | 14 | class Document(parts: List[Element]) { 15 | 16 | def accept(visitor: Element => Unit): Unit = { 17 | parts.foreach(p => p.accept(visitor)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /behavioral-design-patterns/src/main/scala/com/ivan/nikolov/behavioral/visitor/better/Visitor.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.behavioral.visitor.better 2 | 3 | object VisitorExample { 4 | val line = System.getProperty("line.separator") 5 | 6 | def htmlExporterVisitor(builder: StringBuilder): Element => Unit = element => element match { 7 | case Title(text) => 8 | builder.append(s"

${text}

").append(line) 9 | case Text(text) => 10 | builder.append(s"

${text}

").append(line) 11 | case Hyperlink(text, url) => 12 | builder.append(s"""${text}""").append(line) 13 | } 14 | 15 | def plainTextExporterVisitor(builder: StringBuilder): Element => Unit = element => element match { 16 | case Title(text) => 17 | builder.append(text).append(line) 18 | case Text(text) => 19 | builder.append(text).append(line) 20 | case Hyperlink(text, url) => 21 | builder.append(s"${text} (${url})").append(line) 22 | } 23 | 24 | def main(args: Array[String]): Unit = { 25 | val document = new Document( 26 | List( 27 | Title("The Visitor Pattern Example"), 28 | Text("The visitor pattern helps us add extra functionality without changing the classes."), 29 | Hyperlink("Go check it online!", "https://www.google.com/"), 30 | Text("Thanks!") 31 | ) 32 | ) 33 | 34 | val html = new StringBuilder 35 | System.out.println(s"Export to html:") 36 | document.accept(htmlExporterVisitor(html)) 37 | System.out.println(html.toString()) 38 | 39 | val plain = new StringBuilder 40 | System.out.println(s"Export to plain:") 41 | document.accept(plainTextExporterVisitor(plain)) 42 | System.out.println(plain.toString()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /creational-design-patterns/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | creational-design-patterns 13 | 14 | 15 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/resources/com/ivan/nikolov/creational/lazy_init/pi.properties: -------------------------------------------------------------------------------- 1 | pi.high=3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679 -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/builder/case_classes/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.builder.case_classes 2 | 3 | case class Person( 4 | firstName: String = "", 5 | lastName: String = "", 6 | age: Int = 0/*, // skipped for simplicity. 7 | departmentId: Int = 0, 8 | emailAddress: String = "", 9 | city: String = "", 10 | address: String = "", 11 | mobilePhone: String = ""*/ 12 | ) 13 | 14 | object PersonCaseClassExample { 15 | def main(args: Array[String]): Unit = { 16 | val person1 = Person( 17 | firstName = "Ivan", 18 | lastName = "Nikolov", 19 | age = 26/*, // skipped for simplicity. 20 | departmentId = 1, 21 | city = "London"*/ 22 | ) 23 | 24 | val person2 = Person( 25 | firstName = "John"/*, // skipped for simplicity. 26 | address = "21 Regent Street", 27 | mobilePhone = "1234567890", 28 | city = "London"*/ 29 | ) 30 | 31 | System.out.println(s"Person 1: ${person1}") 32 | System.out.println(s"Person 2: ${person2}") 33 | } 34 | } -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/builder/java_way/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.builder.java_way 2 | 3 | class Person(builder: PersonBuilder) { 4 | val firstName = builder.firstName 5 | val lastName = builder.lastName 6 | val age = builder.age 7 | // skipped for simplicity. 8 | // val departmentId = builder.departmentId 9 | // val emailAddress = builder.emailAddress 10 | // val city = builder.city 11 | // val address = builder.address 12 | // val mobilePhone = builder.mobilePhone 13 | } 14 | 15 | class PersonBuilder { 16 | var firstName = "" 17 | var lastName = "" 18 | var age = 0 19 | // skipped for simplicity 20 | // var departmentId = 0 21 | // var emailAddress = "" 22 | // var city = "" 23 | // var address = "" 24 | // var mobilePhone = "" 25 | 26 | def setFirstName(firstName: String): PersonBuilder = { 27 | this.firstName = firstName 28 | this 29 | } 30 | 31 | def setLastName(lastName: String): PersonBuilder = { 32 | this.lastName = lastName 33 | this 34 | } 35 | 36 | def setAge(age: Int): PersonBuilder = { 37 | this.age = age 38 | this 39 | } 40 | 41 | // skipped for simplicity 42 | // def setDepartmentId(departmentId: Int): PersonBuilder = { 43 | // this.departmentId = departmentId 44 | // this 45 | // } 46 | // 47 | // def setEmailAddress(emailAddress: String): PersonBuilder = { 48 | // this.emailAddress = emailAddress 49 | // this 50 | // } 51 | // 52 | // def setCity(city: String): PersonBuilder = { 53 | // this.city = city 54 | // this 55 | // } 56 | // 57 | // def setAddress(address: String): PersonBuilder = { 58 | // this.address = address 59 | // this 60 | // } 61 | // 62 | // def setMobilePhone(mobilePhone: String): PersonBuilder = { 63 | // this.mobilePhone = mobilePhone 64 | // this 65 | // } 66 | 67 | def build(): Person = new Person(this) 68 | } 69 | 70 | object PersonBuilderExample { 71 | def main(args: Array[String]): Unit = { 72 | val person: Person = new PersonBuilder() 73 | .setFirstName("Ivan") 74 | .setLastName("Nikolov") 75 | //.setCity("London") 76 | .setAge(26) 77 | //.setDepartmentId(1) 78 | .build() 79 | //System.out.println(s"Person: ${person.firstName} ${person.lastName}. Age: ${person.age}. City: ${person.city}") 80 | System.out.println(s"Person: ${person.firstName} ${person.lastName}. Age: ${person.age}.") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/builder/type_safe/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.builder.type_safe 2 | 3 | class Person( 4 | val firstName: String, 5 | val lastName: String, 6 | val age: Int/*, // skipped for simplicity 7 | val departmentId: Int, 8 | val emailAddress: String, 9 | val city: String, 10 | val address: String, 11 | val mobilePhone: String*/) 12 | 13 | sealed trait BuildStep 14 | sealed trait HasFirstName extends BuildStep 15 | sealed trait HasLastName extends BuildStep 16 | // skipped for simplicity 17 | //sealed trait HasAge extends BuildStep 18 | //sealed trait HasDepartmentId extends BuildStep 19 | 20 | class PersonBuilder[PassedStep <: BuildStep] private ( 21 | var firstName: String, 22 | var lastName: String, 23 | var age: Int/*, // skipped for simplicity 24 | var departmentId: Int, 25 | var emailAddress: String, 26 | var city: String, 27 | var address: String, 28 | var mobilePhone: String*/ 29 | ) { 30 | protected def this() = this("","",0/*,0,"","","",""*/) 31 | protected def this(pb: PersonBuilder[_]) = this( 32 | pb.firstName, 33 | pb.lastName, 34 | pb.age/*, // skipped for simplicity 35 | pb.departmentId, 36 | pb.emailAddress, 37 | pb.city, 38 | pb.address, 39 | pb.mobilePhone*/ 40 | ) 41 | 42 | def setFirstName(firstName: String): PersonBuilder[HasFirstName] = { 43 | this.firstName = firstName 44 | new PersonBuilder[HasFirstName](this) 45 | } 46 | 47 | def setLastName(lastName: String)(implicit ev: PassedStep =:= HasFirstName): PersonBuilder[HasLastName] = { 48 | this.lastName = lastName 49 | new PersonBuilder[HasLastName](this) 50 | } 51 | 52 | def setAge(age: Int): PersonBuilder[PassedStep] = { 53 | this.age = age 54 | this 55 | } 56 | // def setAge(age: Int)(implicit ev: PassedStep =:= HasLastName): PersonBuilder[HasAge] = { 57 | // this.age = age 58 | // new PersonBuilder[HasAge](this) 59 | // } 60 | 61 | // skipped for simplicity 62 | // def setDepartmentId(departmentId: Int)(implicit ev: PassedStep =:= HasAge): PersonBuilder[HasDepartmentId] = { 63 | // this.departmentId = departmentId 64 | // new PersonBuilder[HasDepartmentId](this) 65 | // } 66 | // 67 | // def setEmailAddress(emailAddress: String): PersonBuilder[PassedStep] = { 68 | // this.emailAddress = emailAddress 69 | // this 70 | // } 71 | // 72 | // def setCity(city: String): PersonBuilder[PassedStep] = { 73 | // this.city = city 74 | // this 75 | // } 76 | // 77 | // def setAddress(address: String): PersonBuilder[PassedStep] = { 78 | // this.address = address 79 | // this 80 | // } 81 | // 82 | // def setMobilePhone(mobilePhone: String): PersonBuilder[PassedStep] = { 83 | // this.mobilePhone = mobilePhone 84 | // this 85 | // } 86 | 87 | def build()(implicit ev: PassedStep =:= /*HasDepartmentId*/ HasLastName): Person = new Person( 88 | firstName, 89 | lastName, 90 | age/*, // skipped for simplicity 91 | departmentId, 92 | emailAddress, 93 | city, 94 | address, 95 | mobilePhone*/ 96 | ) 97 | } 98 | 99 | object PersonBuilder { 100 | def apply() = new PersonBuilder[BuildStep]() 101 | } 102 | 103 | object PersonBuilderTypeSafeExample { 104 | def main(args: Array[String]): Unit = { 105 | val person = PersonBuilder() 106 | .setFirstName("Ivan") 107 | .setLastName("Nikolov") 108 | .setAge(26) 109 | //.setDepartmentId(1) 110 | //.setCity("London") 111 | .build() 112 | //System.out.println(s"Person: ${person.firstName} ${person.lastName}. Age: ${person.age}. City: ${person.city}. Department: ${person.departmentId}") 113 | System.out.println(s"Person: ${person.firstName} ${person.lastName}. Age: ${person.age}.") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/builder/type_safe/case_require/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.builder.type_safe.case_require 2 | 3 | case class Person( 4 | firstName: String = "", 5 | lastName: String = "", 6 | age: Int = 0/*, // skipped for simplicity. 7 | departmentId: Int = 0, 8 | emailAddress: String = "", 9 | city: String = "", 10 | address: String = "", 11 | mobilePhone: String = ""*/ 12 | ) { 13 | require(firstName != "", "First name is required.") 14 | require(lastName != "", "Last name is required.") 15 | } 16 | 17 | object PersonCaseClassRequireExample { 18 | def main(args: Array[String]): Unit = { 19 | val person1 = Person( 20 | firstName = "Ivan", 21 | lastName = "Nikolov", 22 | age = 26/*, // skipped for simplicity. 23 | departmentId = 1, 24 | city = "London"*/ 25 | ) 26 | System.out.println(s"Person 1: ${person1}") 27 | 28 | try { 29 | val person2 = Person( 30 | firstName = "John" /*, // skipped for simplicity. 31 | address = "21 Regent Street", 32 | mobilePhone = "1234567890", 33 | city = "London"*/ 34 | ) 35 | System.out.println(s"Person 2: ${person2}") 36 | } catch { 37 | case e: Throwable => 38 | e.printStackTrace() 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/SimpleConnection.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories 2 | 3 | trait SimpleConnection { 4 | def getName(): String 5 | def executeQuery(query: String): Unit 6 | } 7 | 8 | class SimpleMysqlConnection extends SimpleConnection { 9 | override def getName(): String = "SimpleMysqlConnection" 10 | 11 | override def executeQuery(query: String): Unit = { 12 | System.out.println(s"Executing the query '$query' the MySQL way.") 13 | } 14 | } 15 | 16 | class SimplePgSqlConnection extends SimpleConnection { 17 | override def getName(): String = "SimplePgSqlConnection" 18 | 19 | override def executeQuery(query: String): Unit = { 20 | System.out.println(s"Executing the query '$query' the PgSQL way.") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/abstract_factory/DatabaseClient.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.abstract_factory 2 | 3 | class DatabaseClient(connectorFactory: DatabaseConnectorFactory) { 4 | def executeQuery(query: String): Unit = { 5 | val connection = connectorFactory.connect() 6 | connection.executeQuery(query) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/abstract_factory/DatabaseConnectorFactory.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.abstract_factory 2 | 3 | import com.ivan.nikolov.creational.factories.{SimplePgSqlConnection, SimpleMysqlConnection, SimpleConnection} 4 | 5 | trait DatabaseConnectorFactory { 6 | def connect(): SimpleConnection 7 | } 8 | 9 | class MySqlFactory extends DatabaseConnectorFactory { 10 | override def connect(): SimpleConnection = new SimpleMysqlConnection 11 | } 12 | 13 | class PgSqlFactory extends DatabaseConnectorFactory { 14 | override def connect(): SimpleConnection = new SimplePgSqlConnection 15 | } 16 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/abstract_factory/Example.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.abstract_factory 2 | 3 | object Example { 4 | def main(args: Array[String]): Unit = { 5 | val clientMySql: DatabaseClient = new DatabaseClient(new MySqlFactory) 6 | val clientPgSql: DatabaseClient = new DatabaseClient(new PgSqlFactory) 7 | clientMySql.executeQuery("SELECT * FROM users") 8 | clientPgSql.executeQuery("SELECT * FROM employees") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/factory_method/BadDatabaseClient.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.factory_method 2 | 3 | import com.ivan.nikolov.creational.factories.{SimpleMysqlConnection, SimplePgSqlConnection, SimpleConnection} 4 | 5 | abstract class BadDatabaseClient { 6 | def executeQuery(query: String): Unit = { 7 | val connection = connect() 8 | val connectionPrinter = getConnectionPrinter() 9 | connectionPrinter.printSimpleConnection(connection) 10 | connection.executeQuery(query) 11 | } 12 | 13 | protected def connect(): SimpleConnection 14 | protected def getConnectionPrinter(): SimpleConnectionPrinter 15 | } 16 | 17 | class BadMySqlClient extends BadDatabaseClient { 18 | override protected def connect(): SimpleConnection = new SimpleMysqlConnection 19 | 20 | override protected def getConnectionPrinter(): SimpleConnectionPrinter = new SimpleMySqlConnectionPrinter 21 | } 22 | 23 | class BadPgSqlClient extends BadDatabaseClient { 24 | override protected def connect(): SimpleConnection = new SimplePgSqlConnection 25 | 26 | override protected def getConnectionPrinter(): SimpleConnectionPrinter = new SimpleMySqlConnectionPrinter 27 | } 28 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/factory_method/BadExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.factory_method 2 | 3 | object BadExample { 4 | def main(args: Array[String]): Unit = { 5 | val clientMySql: BadDatabaseClient = new BadMySqlClient 6 | val clientPgSql: BadDatabaseClient = new BadPgSqlClient 7 | clientMySql.executeQuery("SELECT * FROM users") 8 | clientPgSql.executeQuery("SELECT * FROM employees") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/factory_method/DatabaseClient.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.factory_method 2 | 3 | import com.ivan.nikolov.creational.factories.{SimplePgSqlConnection, SimpleMysqlConnection, SimpleConnection} 4 | 5 | abstract class DatabaseClient { 6 | def executeQuery(query: String): Unit = { 7 | val connection = connect() 8 | connection.executeQuery(query) 9 | } 10 | 11 | protected def connect(): SimpleConnection 12 | } 13 | 14 | class MysqlClient extends DatabaseClient { 15 | override protected def connect(): SimpleConnection = new SimpleMysqlConnection 16 | } 17 | 18 | class PgSqlClient extends DatabaseClient { 19 | override protected def connect(): SimpleConnection = new SimplePgSqlConnection 20 | } 21 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/factory_method/Example.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.factory_method 2 | 3 | object Example { 4 | def main(args: Array[String]): Unit = { 5 | val clientMySql: DatabaseClient = new MysqlClient 6 | val clientPgSql: DatabaseClient = new PgSqlClient 7 | clientMySql.executeQuery("SELECT * FROM users") 8 | clientPgSql.executeQuery("SELECT * FROM employees") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/factory_method/SimpleConnectionPrinter.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.factory_method 2 | 3 | import com.ivan.nikolov.creational.factories.SimpleConnection 4 | 5 | trait SimpleConnectionPrinter { 6 | def printSimpleConnection(connection: SimpleConnection): Unit 7 | } 8 | 9 | class SimpleMySqlConnectionPrinter extends SimpleConnectionPrinter { 10 | override def printSimpleConnection(connection: SimpleConnection): Unit = { 11 | System.out.println(s"I require a MySQL connection. It is: '${connection.getName()}'") 12 | } 13 | } 14 | 15 | class SimplePgSqlConnectionPrinter extends SimpleConnectionPrinter { 16 | override def printSimpleConnection(connection: SimpleConnection): Unit = { 17 | System.out.println(s"I require a PgSQL connection. It is: '${connection.getName()}'") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/factories/simple/Animal.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.factories.simple 2 | 3 | trait Animal 4 | class Bird extends Animal 5 | class Mammal extends Animal 6 | class Fish extends Animal 7 | 8 | object Animal { 9 | def apply(animal: String): Animal = animal.toLowerCase match { 10 | case "bird" => new Bird 11 | case "mammal" => new Mammal 12 | case "fish" => new Fish 13 | case x: String => throw new RuntimeException(s"Unknown animal: $x") 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/lazy_init/CircleUtils.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.lazy_init 2 | 3 | import java.util.Properties 4 | 5 | object CircleUtils { 6 | val basicPi = 3.14 7 | lazy val precisePi: Double = { 8 | System.out.println("Reading properties for the precise PI.") 9 | val props = new Properties() 10 | props.load(getClass.getResourceAsStream("pi.properties")) 11 | props.getProperty("pi.high").toDouble 12 | } 13 | 14 | def area(radius: Double, isPrecise: Boolean = false): Double = { 15 | val pi: Double = if (isPrecise) precisePi else basicPi 16 | pi * Math.pow(radius, 2) 17 | } 18 | } 19 | 20 | object Example { 21 | def main(args: Array[String]): Unit = { 22 | System.out.println(s"The basic area for a circle with radius 2.5 is ${CircleUtils.area(2.5)}") 23 | System.out.println(s"The precise area for a circle with radius 2.5 is ${CircleUtils.area(2.5, true)}") 24 | System.out.println(s"The basic area for a circle with radius 6.78 is ${CircleUtils.area(6.78)}") 25 | System.out.println(s"The precise area for a circle with radius 6.78 is ${CircleUtils.area(6.78, true)}") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/prototype/Cell.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.prototype 2 | 3 | /** 4 | * Represents a bio cell 5 | */ 6 | case class Cell(dna: String, proteins: List[String]) 7 | 8 | object PrototypeExample { 9 | def main(args: Array[String]): Unit = { 10 | val initialCell = Cell("abcd", List("protein1", "protein2")) 11 | val copy1 = initialCell.copy() 12 | val copy2 = initialCell.copy() 13 | val copy3 = initialCell.copy(dna = "1234") 14 | System.out.println(s"The prototype is: ${initialCell}") 15 | System.out.println(s"Cell 1: ${copy1}") 16 | System.out.println(s"Cell 2: ${copy2}") 17 | System.out.println(s"Cell 3: ${copy3}") 18 | System.out.println(s"1 and 2 are equal: ${copy1 == copy2}") 19 | } 20 | } -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/singleton/AppRegistry.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.singleton 2 | 3 | import scala.collection.concurrent.{TrieMap, Map} 4 | 5 | object AppRegistry { 6 | System.out.println("Registry initialization block called.") 7 | private val users: Map[String, String] = TrieMap.empty 8 | 9 | def addUser(id: String, name: String): Unit = { 10 | users.put(id, name) 11 | } 12 | 13 | def removeUser(id: String): Unit = { 14 | users.remove(id) 15 | } 16 | 17 | def isUserRegistered(id: String): Boolean = 18 | users.contains(id) 19 | 20 | def getAllUserNames(): List[String] = 21 | users.map(_._2).toList 22 | } 23 | 24 | object AppRegistryExample { 25 | def main(args: Array[String]): Unit = { 26 | System.out.println("Sleeping for 5 seconds.") 27 | Thread.sleep(5000) 28 | System.out.println("I woke up.") 29 | AppRegistry.addUser("1", "Ivan") 30 | AppRegistry.addUser("2", "John") 31 | AppRegistry.addUser("3", "Martin") 32 | System.out.println(s"Is user with ID=1 registered? ${AppRegistry.isUserRegistered("1")}") 33 | System.out.println("Removing ID=2") 34 | AppRegistry.removeUser("2") 35 | System.out.println(s"Is user with ID=2 registered? ${AppRegistry.isUserRegistered("2")}") 36 | System.out.println(s"All users registered are: ${AppRegistry.getAllUserNames().mkString(",")}") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /creational-design-patterns/src/main/scala/com/ivan/nikolov/creational/singleton/StringUtils.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.creational.singleton 2 | 3 | object StringUtils { 4 | 5 | def countNumberOfSpaces(text: String): Int = 6 | text.split("\\s+").length - 1 7 | } 8 | 9 | 10 | object UtilsExample { 11 | def main(args: Array[String]): Unit = { 12 | val sentence = "Hello there! I am a utils example." 13 | System.out.println( 14 | s"The number of spaces in '$sentence' is: ${StringUtils.countNumberOfSpaces(sentence)}" 15 | ) 16 | } 17 | } -------------------------------------------------------------------------------- /deep-theory/input.txt: -------------------------------------------------------------------------------- 1 | this is a file, which 2 | will be completely capitalized 3 | in a monadic way. 4 | 5 | Enjoy! -------------------------------------------------------------------------------- /deep-theory/output.txt: -------------------------------------------------------------------------------- 1 | THIS IS A FILE, WHICH 2 | WILL BE COMPLETELY CAPITALIZED 3 | IN A MONADIC WAY. 4 | 5 | ENJOY! 6 | -------------------------------------------------------------------------------- /deep-theory/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | deep-theory 13 | 14 | 15 | 16 | org.slf4j 17 | slf4j-log4j12 18 | ${slf4j.version} 19 | 20 | 21 | com.typesafe.scala-logging 22 | scala-logging_2.11 23 | ${scalalogging.version} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/functors/Functor.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.functors 2 | 3 | trait Functor[F[_]] { 4 | def map[T, Y](l: F[T])(f: T => Y): F[Y] 5 | } 6 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/functors/FunctorsExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.functors 2 | 3 | object FunctorsExample { 4 | def main(args: Array[String]): Unit = { 5 | val numbers = List(1, 2, 3, 4, 5, 6) 6 | val mapping = Map( 7 | 1 -> "one", 8 | 2 -> "two", 9 | 3 -> "three", 10 | 4 -> "four", 11 | 5 -> "five", 12 | 6 -> "six" 13 | ) 14 | 15 | System.out.println(s"The numbers doubled are: ${listFunctor.map(numbers)(_ * 2)}") 16 | System.out.println(s"The numbers with strings are: ${listFunctor.map(numbers)(i => (i, mapping(i)))}") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/functors/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov 2 | 3 | package object functors { 4 | 5 | val listFunctor = new Functor[List] { 6 | override def map[T, Y](l: List[T])(f: (T) => Y): List[Y] = l.map(f) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monads/ListWrapper.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monads 2 | 3 | import scala.collection.GenTraversableOnce 4 | 5 | case class ListWrapper(list: List[Int]) { 6 | 7 | // just wrap 8 | def map[B](f: Int => B): List[B] = list.map(f) 9 | 10 | // just wrap 11 | def flatMap[B](f: Int => GenTraversableOnce[B]): List[B] = list.flatMap(f) 12 | } 13 | 14 | object ForComprehensionWithLists { 15 | def main(args: Array[String]): Unit = { 16 | val l1 = List(1, 2, 3, 4) 17 | val l2 = List(5, 6, 7, 8) 18 | val result = for { 19 | x <- l1 20 | y <- l2 21 | } yield x * y 22 | // same as 23 | // val result = l1.flatMap(i => l2.map(_ * i)) 24 | System.out.println(s"The result is: ${result}") 25 | } 26 | } 27 | 28 | 29 | object ForComprehensionWithObjects { 30 | def main(args: Array[String]): Unit = { 31 | val wrapper1 = ListWrapper(List(1, 2, 3, 4)) 32 | val wrapper2 = ListWrapper(List(5, 6, 7, 8)) 33 | val result = for { 34 | x <- wrapper1 35 | y <- wrapper2 36 | } yield x * y 37 | System.out.println(s"The result is: ${result}") 38 | } 39 | } -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monads/Monad.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monads 2 | 3 | trait Functor[T] { 4 | def map[Y](f: T => Y): Functor[Y] 5 | } 6 | 7 | trait Monad[T] extends Functor[T] { 8 | 9 | def unit[Y](value: Y): Monad[Y] 10 | 11 | def flatMap[Y](f: T => Monad[Y]): Monad[Y] 12 | 13 | override def map[Y](f: T => Y): Monad[Y] = 14 | flatMap(i => unit(f(i))) 15 | } -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monads/Option.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monads 2 | 3 | sealed trait Option[A] extends Monad[A] 4 | 5 | case class Some[A](a: A) extends Option[A] { 6 | override def unit[Y](value: Y): Monad[Y] = Some(value) 7 | override def flatMap[Y](f: (A) => Monad[Y]): Monad[Y] = f(a) 8 | } 9 | 10 | case class None[A]() extends Option[A] { 11 | override def unit[Y](value: Y): Monad[Y] = None() 12 | override def flatMap[Y](f: (A) => Monad[Y]): Monad[Y] = None() 13 | } 14 | 15 | case class Doer() { 16 | def getAlgorithm(isFail: Boolean) = 17 | if (isFail) { 18 | null 19 | } else { 20 | Algorithm() 21 | } 22 | } 23 | 24 | case class Algorithm() { 25 | def getImplementation(isFail: Boolean, left: Int, right: Int): Implementation = 26 | if (isFail) { 27 | null 28 | } else { 29 | Implementation(left, right) 30 | } 31 | } 32 | 33 | case class Implementation(left: Int, right: Int) { 34 | def compute: Int = left + right 35 | } 36 | 37 | object NoMonadExample { 38 | def main(args: Array[String]): Unit = { 39 | System.out.println(s"The result is: ${compute(Doer(), 10, 16)}") 40 | } 41 | 42 | def compute(doer: Doer, left: Int, right: Int): Int = 43 | if (doer != null) { 44 | val algorithm = doer.getAlgorithm(false) 45 | if (algorithm != null) { 46 | val implementation = algorithm.getImplementation(false, left, right) 47 | if (implementation != null) { 48 | implementation.compute 49 | } else { 50 | -1 51 | } 52 | } else { 53 | -1 54 | } 55 | } else { 56 | -1 57 | } 58 | } 59 | 60 | case class Doer_v2() { 61 | def getAlgorithm(isFail: Boolean): Option[Algorithm_v2] = 62 | if (isFail) { 63 | None() 64 | } else { 65 | Some(Algorithm_v2()) 66 | } 67 | } 68 | 69 | case class Algorithm_v2() { 70 | def getImplementation(isFail: Boolean, left: Int, right: Int): Option[Implementation] = 71 | if (isFail) { 72 | None() 73 | } else { 74 | Some(Implementation(left, right)) 75 | } 76 | } 77 | 78 | object MonadExample { 79 | def main(args: Array[String]): Unit = { 80 | System.out.println(s"The result is: ${compute(Some(Doer_v2()), 10, 16)}") 81 | } 82 | 83 | def compute(doer: Option[Doer_v2], left: Int, right: Int) = 84 | for { 85 | d <- doer 86 | a <- d.getAlgorithm(false) 87 | i <- a.getImplementation(false, left, right) 88 | } yield i.compute 89 | // OR THIS WAY: 90 | // doer.flatMap { 91 | // d => d.getAlgorithm(false).flatMap { 92 | // a => a.getImplementation(false, left, right).map { 93 | // i => i.compute 94 | // } 95 | // } 96 | // } 97 | } -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monads/io/State.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monads.io 2 | 3 | sealed trait State { 4 | def next: State 5 | } 6 | 7 | abstract class FileIO { 8 | // this makes sure nobody can create a state 9 | private class FileIOState(id: Int) extends State { 10 | override def next: State = new FileIOState(id + 1) 11 | } 12 | 13 | def run(args: Array[String]): Unit = { 14 | val action = runIO(args(0), args(1)) 15 | action(new FileIOState(0)) 16 | } 17 | 18 | def runIO(readPath: String, writePath: String): IOAction[_] 19 | } 20 | 21 | sealed abstract class IOAction[T] extends ((State) => (State, T)) { 22 | 23 | // START: we don't have to extend. We could also do this... 24 | def unit[Y](value: Y): IOAction[Y] = IOAction.unit(value) 25 | 26 | def flatMap[Y](f: (T) => IOAction[Y]): IOAction[Y] = { 27 | val self = this 28 | new IOAction[Y] { 29 | override def apply(state: State): (State, Y) = { 30 | val (state2, res) = self(state) 31 | val action2 = f(res) 32 | action2(state2) 33 | } 34 | } 35 | } 36 | 37 | def map[Y](f: T => Y): IOAction[Y] = 38 | flatMap(i => unit(f(i))) 39 | // END: we don't have to extend. We could also do this... 40 | } 41 | 42 | object IOAction { 43 | 44 | def apply[T](result: => T): IOAction[T] = 45 | new SimpleAction[T](result) 46 | 47 | def unit[T](value: T): IOAction[T] = 48 | new EmptyAction[T](value) 49 | 50 | private class SimpleAction[T](result: => T) extends IOAction[T] { 51 | override def apply(state: State): (State, T) = 52 | (state.next, result) 53 | } 54 | 55 | private class EmptyAction[T](value: T) extends IOAction[T] { 56 | override def apply(state: State): (State, T) = 57 | (state, value) 58 | } 59 | } 60 | 61 | object FileIOExample extends FileIO { 62 | 63 | def main(args: Array[String]): Unit = { 64 | run(args) 65 | } 66 | 67 | override def runIO(readPath: String, writePath: String): IOAction[_] = 68 | for { 69 | lines <- readFile(readPath) 70 | _ <- writeFile(writePath, lines.map(_.toUpperCase)) 71 | } yield () 72 | } 73 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monads/io/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monads 2 | 3 | import java.io.{PrintWriter, File} 4 | 5 | import scala.io.Source 6 | 7 | package object io { 8 | def readFile(path: String) = 9 | IOAction(Source.fromFile(path).getLines()) 10 | 11 | def writeFile(path: String, lines: Iterator[String]) = 12 | IOAction({ 13 | val file = new File(path) 14 | printToFile(file) { p => lines.foreach(p.println) } 15 | }) 16 | 17 | private def printToFile(file: File)(writeOp: PrintWriter => Unit): Unit = { 18 | val writer = new PrintWriter(file) 19 | try { 20 | writeOp(writer) 21 | } finally { 22 | writer.close() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monoids/Monoid.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monoids 2 | 3 | trait Monoid[T] { 4 | def op(l: T, r: T): T 5 | def zero: T 6 | } 7 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monoids/MonoidFolding.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monoids 2 | 3 | object MonoidFolding { 4 | def main(args: Array[String]): Unit = { 5 | val strings = List("This is\n", "a list of\n", "strings!") 6 | val numbers = List(1, 2, 3, 4, 5, 6) 7 | 8 | System.out.println(s"Left folded:\n ${strings.foldLeft(stringConcatenation.zero)(stringConcatenation.op)}") 9 | System.out.println(s"Right folded:\n ${strings.foldRight(stringConcatenation.zero)(stringConcatenation.op)}") 10 | System.out.println(s"6! is: ${numbers.foldLeft(intMultiplication.zero)(intMultiplication.op)}") 11 | } 12 | } 13 | 14 | object MonoidFoldingGeneric { 15 | def main(args: Array[String]): Unit = { 16 | val strings = List("This is\n", "a list of\n", "strings!") 17 | val numbers = List(1, 2, 3, 4, 5, 6) 18 | 19 | System.out.println(s"Left folded:\n ${MonoidOperations.fold(strings, stringConcatenation)}") 20 | System.out.println(s"Right folded:\n ${MonoidOperations.fold(strings, stringConcatenation)}") 21 | System.out.println(s"6! is: ${MonoidOperations.fold(numbers, intMultiplication)}") 22 | } 23 | } 24 | 25 | object MonoidBalancedFold { 26 | def main(args: Array[String]): Unit = { 27 | val numbers = Array(1, 2, 3, 4) 28 | System.out.println(s"4! is: ${MonoidOperations.balancedFold(numbers, intMultiplication)(identity)}") 29 | } 30 | } 31 | 32 | object MonoidFoldingGenericPar { 33 | def main(args: Array[String]): Unit = { 34 | val strings = List("This is\n", "a list of\n", "strings!") 35 | val numbers = List(1, 2, 3, 4, 5, 6) 36 | 37 | System.out.println(s"Left folded:\n ${MonoidOperations.foldPar(strings, stringConcatenation)}") 38 | System.out.println(s"Right folded:\n ${MonoidOperations.foldPar(strings, stringConcatenation)}") 39 | System.out.println(s"6! is: ${MonoidOperations.foldPar(numbers, intMultiplication)}") 40 | } 41 | } 42 | 43 | object ComposedMonoid { 44 | def main(args: Array[String]): Unit = { 45 | val numbers = Array(1, 2, 3, 4, 5, 6) 46 | val sumAndProduct = compose(intAddition, intMultiplication) 47 | 48 | System.out.println(s"The sum and product is: ${MonoidOperations.balancedFold(numbers, sumAndProduct)(i => (i, i))}") 49 | } 50 | } 51 | 52 | object FeatureCounting { 53 | def main(args: Array[String]): Unit = { 54 | val features = Array("hello", "features", "for", "ml", "hello", "for", "features") 55 | val counterMonoid: Monoid[Map[String, Int]] = mapMerge(intAddition) 56 | 57 | System.out.println(s"The features are: ${MonoidOperations.balancedFold(features, counterMonoid)(i => Map(i -> 1))}") 58 | } 59 | } 60 | 61 | object FeatureCountingOneOff { 62 | def main(args: Array[String]): Unit = { 63 | val features = Array("hello", "features", "for", "ml", "hello", "for", "features") 64 | System.out.println(s"The features are: ${ 65 | features.foldLeft(Map[String, Int]()) { 66 | case (res, feature) => 67 | res.updated(feature, res.getOrElse(feature, 0) + 1) 68 | } 69 | }") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /deep-theory/src/main/scala/com/ivan/nikolov/monoids/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov 2 | 3 | package object monoids { 4 | 5 | val intAddition: Monoid[Int] = new Monoid[Int] { 6 | val zero: Int = 0 7 | 8 | override def op(l: Int, r: Int): Int = l + r 9 | } 10 | 11 | val intMultiplication: Monoid[Int] = new Monoid[Int] { 12 | val zero: Int = 1 13 | 14 | override def op(l: Int, r: Int): Int = l * r 15 | } 16 | 17 | val stringConcatenation: Monoid[String] = new Monoid[String] { 18 | val zero: String = "" 19 | 20 | override def op(l: String, r: String): String = l + r 21 | } 22 | 23 | def compose[T, Y](a: Monoid[T], b: Monoid[Y]): Monoid[(T, Y)] = 24 | new Monoid[(T, Y)] { 25 | val zero: (T, Y) = (a.zero, b.zero) 26 | override def op(l: (T, Y), r: (T, Y)): (T, Y) = 27 | (a.op(l._1, r._1), b.op(l._2, r._2)) 28 | } 29 | 30 | def mapMerge[K, V](a: Monoid[V]): Monoid[Map[K, V]] = 31 | new Monoid[Map[K, V]] { 32 | override def zero: Map[K, V] = Map() 33 | override def op(l: Map[K, V], r: Map[K, V]): Map[K, V] = 34 | (l.keySet ++ r.keySet).foldLeft(zero) { 35 | case (res, key) => 36 | res.updated(key, a.op(l.getOrElse(key, a.zero), r.getOrElse(key, a.zero))) 37 | } 38 | } 39 | 40 | object MonoidOperations { 41 | def fold[T](list: List[T], m: Monoid[T]): T = 42 | foldMap(list, m)(identity) 43 | 44 | def foldMap[T, Y](list: List[T], m: Monoid[Y])(f: T => Y): Y = 45 | list.foldLeft(m.zero) { 46 | case (t, y) => m.op(t, f(y)) 47 | } 48 | 49 | def foldPar[T](list: List[T], m: Monoid[T]): T = 50 | foldMapPar(list, m)(identity) 51 | 52 | def foldMapPar[T, Y](list: List[T], m: Monoid[Y])(f: T => Y): Y = 53 | list.par.foldLeft(m.zero) { 54 | case (t, y) => m.op(t, f(y)) 55 | } 56 | 57 | def balancedFold[T, Y](list: IndexedSeq[T], m: Monoid[Y])(f: T => Y): Y = 58 | if (list.length == 0) { 59 | m.zero 60 | } else if (list.length == 1) { 61 | f(list(0)) 62 | } else { 63 | val (left, right) = list.splitAt(list.length / 2) 64 | m.op(balancedFold(left, m)(f), balancedFold(right, m)(f)) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /functional-design-patterns/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | functional-design-patterns 13 | 14 | 15 | 16 | commons-codec 17 | commons-codec 18 | ${commons.codec.version} 19 | 20 | 21 | org.slf4j 22 | slf4j-log4j12 23 | ${slf4j.version} 24 | 25 | 26 | com.typesafe.scala-logging 27 | scala-logging_2.11 28 | ${scalalogging.version} 29 | 30 | 31 | org.scalaz 32 | scalaz-core_2.11 33 | ${scalaz.version} 34 | 35 | 36 | com.h2database 37 | h2 38 | ${h2.version} 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/cake/Application.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.cake 2 | 3 | object Application { 4 | import ApplicationComponentRegistry._ 5 | def main(args: Array[String]): Unit = { 6 | migrationService.runMigrations() 7 | System.out.println(dao.getPeople) 8 | System.out.println(dao.getClasses) 9 | System.out.println(dao.getPeopleInClass("Scala Design Patterns")) 10 | System.out.println(dao.getPeopleInClass("Mountain Biking")) 11 | System.out.println(s"Average age of everyone in Scala Design Patterns: ${userService.getAverageAgeOfUsersInClass("Scala Design Patterns")}") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/cake/ApplicationComponentRegistry.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.cake 2 | 3 | object ApplicationComponentRegistry 4 | extends UserComponent 5 | with DaoComponent 6 | with DatabaseComponent 7 | with MigrationComponent { 8 | override val dao: ApplicationComponentRegistry.Dao = new Dao 9 | override val databaseService: DatabaseService = new H2DatabaseService("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "", "") 10 | override val migrationService: ApplicationComponentRegistry.MigrationService = new MigrationService 11 | override val userService: ApplicationComponentRegistry.UserService = new UserService 12 | } 13 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/cake/DaoComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.cake 2 | 3 | import java.sql.{ResultSet, PreparedStatement} 4 | 5 | import com.ivan.nikolov.cake.model.{Person, Class} 6 | 7 | trait DaoComponent { 8 | this: DatabaseComponent => 9 | 10 | val dao: Dao 11 | 12 | class Dao() { 13 | def getPeople: List[Person] = { 14 | val connection = databaseService.getConnection 15 | try { 16 | executeSelect( 17 | connection.prepareStatement("SELECT id, name, age FROM people") 18 | ) { 19 | rs => 20 | readResultSet(rs) { 21 | row => 22 | Person(row.getInt(1), row.getString(2), row.getInt(3)) 23 | } 24 | } 25 | } finally { 26 | connection.close() 27 | } 28 | } 29 | 30 | def getClasses: List[Class] = { 31 | val connection = databaseService.getConnection 32 | try { 33 | executeSelect( 34 | connection.prepareStatement("SELECT id, name FROM classes") 35 | ) { 36 | rs => 37 | readResultSet(rs) { 38 | row => 39 | Class(row.getInt(1), row.getString(2)) 40 | } 41 | } 42 | } finally { 43 | connection.close() 44 | } 45 | } 46 | 47 | def getPeopleInClass(className: String): List[Person] = { 48 | val connection = databaseService.getConnection 49 | try { 50 | val statement = connection.prepareStatement( 51 | """ 52 | |SELECT p.id, p.name, p.age 53 | |FROM people p 54 | | JOIN people_classes pc ON p.id = pc.person_id 55 | | JOIN classes c ON c.id = pc.class_id 56 | |WHERE c.name = ? 57 | """.stripMargin 58 | ) 59 | statement.setString(1, className) 60 | executeSelect( 61 | statement 62 | ) { 63 | rs => 64 | readResultSet(rs) { 65 | row => 66 | Person(row.getInt(1), row.getString(2), row.getInt(3)) 67 | } 68 | } 69 | } finally { 70 | connection.close() 71 | } 72 | 73 | } 74 | 75 | private def executeSelect[T](preparedStatement: PreparedStatement)(f: (ResultSet) => List[T]): List[T] = 76 | try { 77 | f(preparedStatement.executeQuery()) 78 | } finally { 79 | preparedStatement.close() 80 | } 81 | 82 | private def readResultSet[T](rs: ResultSet)(f: ResultSet => T): List[T] = 83 | Iterator.continually((rs.next(), rs)).takeWhile(_._1).map { 84 | case (_, row) => 85 | f(rs) 86 | }.toList 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/cake/DatabaseComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.cake 2 | 3 | import java.sql.Connection 4 | 5 | import org.h2.jdbcx.JdbcConnectionPool 6 | 7 | trait DatabaseService { 8 | val dbDriver: String 9 | val connectionString: String 10 | val username: String 11 | val password: String 12 | 13 | val ds = { 14 | JdbcConnectionPool.create(connectionString, username, password) 15 | } 16 | 17 | def getConnection: Connection = ds.getConnection 18 | } 19 | 20 | trait DatabaseComponent { 21 | 22 | val databaseService: DatabaseService 23 | 24 | class H2DatabaseService(val connectionString: String, val username: String, val password: String) extends DatabaseService { 25 | val dbDriver = "org.h2.Driver" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/cake/UserComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.cake 2 | 3 | trait UserComponent { 4 | this: DaoComponent => 5 | 6 | val userService: UserService 7 | class UserService { 8 | def getAverageAgeOfUsersInClass(className: String): Double = { 9 | val (ageSum, peopleCount) = dao.getPeopleInClass(className).foldLeft((0, 0)) { 10 | case ((sum, count), person) => 11 | (sum + person.age, count + 1) 12 | } 13 | if (peopleCount != 0) { 14 | ageSum.toDouble / peopleCount.toDouble 15 | } else { 16 | 0.0 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/cake/model/Class.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.cake.model 2 | 3 | case class Class(id: Int, name: String) 4 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/cake/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.cake.model 2 | 3 | case class Person(id: Int, name: String, age: Int) 4 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/duck/DuckTypingExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.duck 2 | 3 | object DuckTypingExample { 4 | 5 | def printSentenceParts(sentence: String, parser: {def parse(sentence: String): Array[String]}) = 6 | parser.parse(sentence).foreach(println) 7 | 8 | def main(args: Array[String]): Unit = { 9 | val tokenizerParser = new SentenceParserTokenize 10 | val splitParser = new SentenceParserSplit 11 | 12 | val sentence = "This is the sentence we will be splitting." 13 | 14 | System.out.println("Using the tokenize parser: ") 15 | printSentenceParts(sentence, tokenizerParser) 16 | 17 | System.out.println("Using the split parser: ") 18 | printSentenceParts(sentence, splitParser) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/duck/SentenceParserSplit.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.duck 2 | 3 | class SentenceParserSplit { 4 | 5 | def parse(sentence: String): Array[String] = 6 | sentence.split("\\s") 7 | } 8 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/duck/SentenceParserTokenize.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.duck 2 | 3 | import java.util.StringTokenizer 4 | 5 | class SentenceParserTokenize { 6 | 7 | def parse(sentence: String): Array[String] = { 8 | val tokenizer = new StringTokenizer(sentence) 9 | Iterator.continually({ 10 | val hasMore = tokenizer.hasMoreTokens 11 | if (hasMore) { 12 | (hasMore, tokenizer.nextToken()) 13 | } else { 14 | (hasMore, null) 15 | } 16 | }).takeWhile(_._1).map(_._2).toArray 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/implicits/ImplicitExamples.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.implicits 2 | 3 | 4 | object ImplicitExamples { 5 | def main(args: Array[String]): Unit = { 6 | val number: Int = 7.6 7 | System.out.println(s"The integer value for 7.6 is ${number}") 8 | // prints HELLO! 9 | printAsciiString(List(72, 69, 76, 76, 79, 33)) 10 | } 11 | 12 | def printAsciiString(s: String): Unit = { 13 | System.out.println(s) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/implicits/di/DatabaseService.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.implicits.di 2 | 3 | import com.ivan.nikolov.implicits.di.model.Person 4 | 5 | trait DatabaseService { 6 | def getPeople(): List[Person] 7 | } 8 | 9 | class DatabaseServiceImpl extends DatabaseService { 10 | override def getPeople(): List[Person] = List( 11 | Person("Ivan", 26), 12 | Person("Maria", 26), 13 | Person("John", 25) 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/implicits/di/ImplicitDIExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.implicits.di 2 | 3 | object ImplicitDIExample { 4 | def main(args: Array[String]): Unit = { 5 | System.out.println(s"The average age of the people is: ${userService.getAverageAgeOfPeople()}") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/implicits/di/UserService.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.implicits.di 2 | 3 | trait UserService { 4 | def getAverageAgeOfPeople()(implicit ds: DatabaseService): Double 5 | } 6 | 7 | class UserServiceImpl extends UserService { 8 | override def getAverageAgeOfPeople()(implicit ds: DatabaseService): Double = { 9 | val (s, c) = ds.getPeople().foldLeft((0, 0)) { 10 | case ((sum, count), person) => 11 | (sum + person.age, count + 1) 12 | } 13 | s.toDouble / c.toDouble 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/implicits/di/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.implicits.di.model 2 | 3 | case class Person(name: String, age: Int) 4 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/implicits/di/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.implicits 2 | 3 | package object di { 4 | implicit val databaseService = new DatabaseServiceImpl 5 | implicit val userService = new UserServiceImpl 6 | } 7 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/implicits/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov 2 | 3 | package object implicits { 4 | 5 | implicit def doubleToInt(a: Double): Int = 6 | Math.round(a).toInt 7 | 8 | 9 | implicit def intsToString(ints: List[Int]): String = 10 | ints.map(_.toChar).mkString 11 | } 12 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/laziness/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.laziness 2 | 3 | case class Person(name: String, age: Int) 4 | 5 | object Person { 6 | 7 | def getFromDatabase(): List[Person] = { 8 | // simulate we're getting people from database by sleeping 9 | System.out.println("Retrieving people...") 10 | Thread.sleep(3000) 11 | List( 12 | Person("Ivan", 26), 13 | Person("Maria", 26), 14 | Person("John", 25) 15 | ) 16 | } 17 | 18 | def printPeopleBad(people: => List[Person]): Unit = { 19 | System.out.println(s"Print first time: ${people}") 20 | System.out.println(s"Print second time: ${people}") 21 | } 22 | 23 | def printPeopleGood(people: => List[Person]): Unit = { 24 | lazy val peopleCopy = people 25 | System.out.println(s"Print first time: ${peopleCopy}") 26 | System.out.println(s"Print second time: ${peopleCopy}") 27 | } 28 | 29 | def printPeopleGood2(people: => List[Person]): Unit = { 30 | val peopleCopy = () => people 31 | System.out.println(s"Print first time: ${peopleCopy()}") 32 | System.out.println(s"Print second time: ${peopleCopy()}") 33 | } 34 | } 35 | 36 | object Example { 37 | import Person._ 38 | def main(args: Array[String]): Unit = { 39 | System.out.println("Now printing bad.") 40 | printPeopleBad(getFromDatabase()) 41 | System.out.println("Now printing good.") 42 | printPeopleGood2(getFromDatabase()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/lens/User.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.lens 2 | 3 | import scalaz.{LensFamily, Lens} 4 | 5 | case class Country(name: String, code: String) 6 | case class City(name: String, country: Country) 7 | case class Address(number: Int, street: String, city: City) 8 | case class Company(name: String, address: Address) 9 | case class User(name: String, company: Company, address: Address) 10 | 11 | object User { 12 | val userCompany = Lens.lensu[User, Company]( 13 | (u, company) => u.copy(company = company), 14 | _.company 15 | ) 16 | 17 | val userAddress = Lens.lensu[User, Address]( 18 | (u, address) => u.copy(address = address), 19 | _.address 20 | ) 21 | 22 | val companyAddress = Lens.lensu[Company, Address]( 23 | (c, address) => c.copy(address = address), 24 | _.address 25 | ) 26 | 27 | val addressCity = Lens.lensu[Address, City]( 28 | (a, city) => a.copy(city = city), 29 | _.city 30 | ) 31 | 32 | val cityCountry = Lens.lensu[City, Country]( 33 | (c, country) => c.copy(country = country), 34 | _.country 35 | ) 36 | 37 | val countryCode = Lens.lensu[Country, String]( 38 | (c, code) => c.copy(code = code), 39 | _.code 40 | ) 41 | 42 | val userCompanyCountryCode = userCompany >=> companyAddress >=> addressCity >=> cityCountry >=> countryCode 43 | 44 | val userCompanyCountryCodeCompose = countryCode <=< cityCountry <=< addressCity <=< companyAddress <=< userCompany 45 | } 46 | 47 | object UserVerboseExample { 48 | def main(args: Array[String]): Unit = { 49 | val uk = Country("United Kingdom", "uk") 50 | val london = City("London", uk) 51 | val buckinghamPalace = Address(1, "Buckingham Palace Road", london) 52 | val castleBuilders = Company("Castle Builders", buckinghamPalace) 53 | 54 | val switzerland = Country("Switzerland", "CH") 55 | val geneva = City("geneva", switzerland) 56 | val genevaAddress = Address(1, "Geneva Lake", geneva) 57 | 58 | val ivan = User("Ivan", castleBuilders, genevaAddress) 59 | System.out.println(ivan) 60 | 61 | System.out.println("Capitalize UK code...") 62 | 63 | val ivanFixed = ivan.copy( 64 | company = ivan.company.copy( 65 | address = ivan.company.address.copy( 66 | city = ivan.company.address.city.copy( 67 | country = ivan.company.address.city.country.copy( 68 | code = ivan.company.address.city.country.code.toUpperCase 69 | ) 70 | ) 71 | ) 72 | ) 73 | ) 74 | System.out.println(ivanFixed) 75 | } 76 | } 77 | 78 | object UserLensExample { 79 | import User._ 80 | 81 | def main(args: Array[String]): Unit = { 82 | val uk = Country("United Kingdom", "uk") 83 | val london = City("London", uk) 84 | val buckinghamPalace = Address(1, "Buckingham Palace Road", london) 85 | val castleBuilders = Company("Castle Builders", buckinghamPalace) 86 | 87 | val switzerland = Country("Switzerland", "CH") 88 | val geneva = City("geneva", switzerland) 89 | val genevaAddress = Address(1, "Geneva Lake", geneva) 90 | 91 | val ivan = User("Ivan", castleBuilders, genevaAddress) 92 | System.out.println(ivan) 93 | 94 | System.out.println("Capitalize UK code...") 95 | 96 | val ivanFixed = userCompanyCountryCode.mod(_.toUpperCase, ivan) 97 | System.out.println(ivanFixed) 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/lens/bad/User.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.lens.bad 2 | 3 | case class Country(var name: String, var code: String) 4 | case class City(var name: String, var country: Country) 5 | case class Address(var number: Int, var street: String, var city: City) 6 | case class Company(var name: String, var address: Address) 7 | case class User(var name: String, var company: Company, var address: Address) 8 | 9 | object UserBadExample { 10 | def main(args: Array[String]): Unit = { 11 | val uk = Country("United Kingdom", "uk") 12 | val london = City("London", uk) 13 | val buckinghamPalace = Address(1, "Buckingham Palace Road", london) 14 | val castleBuilders = Company("Castle Builders", buckinghamPalace) 15 | 16 | val switzerland = Country("Switzerland", "CH") 17 | val geneva = City("geneva", switzerland) 18 | val genevaAddress = Address(1, "Geneva Lake", geneva) 19 | 20 | val ivan = User("Ivan", castleBuilders, genevaAddress) 21 | System.out.println(ivan) 22 | 23 | System.out.println("Capitalize UK code...") 24 | 25 | ivan.company.address.city.country.code = ivan.company.address.city.country.code.toUpperCase 26 | System.out.println(ivan) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/memo/Hasher.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.memo 2 | 3 | import java.security.MessageDigest 4 | 5 | import org.apache.commons.codec.binary.Hex 6 | 7 | import scalaz.Memo 8 | 9 | class Hasher extends Memoizer { 10 | 11 | def md5(input: String) = { 12 | System.out.println(s"Calling md5 for $input.") 13 | new String(Hex.encodeHex(MessageDigest.getInstance("MD5").digest(input.getBytes))) 14 | } 15 | 16 | val memoMd5 = memo(md5) 17 | 18 | val memoMd5Scalaz: String => String = Memo.mutableHashMapMemo { 19 | md5 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/memo/MemoizationExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.memo 2 | 3 | object MemoizationExample { 4 | 5 | def main(args: Array[String]): Unit = { 6 | val hasher = new Hasher 7 | 8 | System.out.println(s"MD5 for 'hello' is '${hasher.memoMd5("hello")}'.") 9 | System.out.println(s"MD5 for 'bye' is '${hasher.memoMd5("bye")}'.") 10 | System.out.println(s"MD5 for 'hello' is '${hasher.memoMd5("hello")}'.") 11 | System.out.println(s"MD5 for 'bye1' is '${hasher.memoMd5("bye1")}'.") 12 | System.out.println(s"MD5 for 'bye' is '${hasher.memoMd5("bye")}'.") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/memo/Memoizer.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.memo 2 | 3 | import scala.collection.mutable.Map 4 | 5 | trait Memoizer { 6 | 7 | def memo[X, Y](f: X => Y): (X => Y) = { 8 | val cache = Map[X, Y]() 9 | (x: X) => cache.getOrElseUpdate(x, f(x)) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/partial_functions/PartiallyAppliedFunctions.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.partial_functions 2 | 3 | /** 4 | * Note that these are not partially defined functions! 5 | */ 6 | object PartiallyAppliedFunctions { 7 | 8 | val greaterOrEqual = (a: Int, b: Int) => a >= b 9 | val lessOrEqual = (a: Int, b: Int) => a <= b 10 | 11 | def greaterOrEqualCurried(b: Int)(a: Int) = a >= b 12 | def lessOrEqualCurried(b: Int)(a: Int) = a <= b 13 | 14 | val greaterOrEqualCurriedVal: (Int) => (Int) => Boolean = b => a => a >= b 15 | val lessOrEqualCurriedVal: (Int) => (Int) => Boolean = b => a => a <= b 16 | } 17 | 18 | object PartiallyAppliedExample { 19 | import PartiallyAppliedFunctions._ 20 | val MAX = 20 21 | val MIN = 5 22 | 23 | def main(args: Array[String]): Unit = { 24 | val numbers = List(1, 5, 6, 11, 18, 19, 20, 21, 25, 30) 25 | // partially applied 26 | val ge = greaterOrEqual(_: Int, MIN) 27 | val le = lessOrEqual(_: Int, MAX) 28 | 29 | // curried 30 | val geCurried = greaterOrEqualCurried(MIN) _ 31 | val leCurried = lessOrEqualCurried(MAX) _ 32 | 33 | // won't work because of the argument order 34 | // val geCurried = greaterOrEqual.curried(MIN) 35 | // val leCurried = lessOrEqual.curried(MAX) 36 | 37 | // will work normally 38 | // val geCurried = greaterOrEqualCurriedVal(MIN) 39 | // val leCurried = lessOrEqualCurriedVal(MAX) 40 | 41 | System.out.println(s"Filtered list: ${numbers.filter(i => ge(i) && le(i))}") 42 | System.out.println(s"Filtered list: ${numbers.filter(i => geCurried(i) && leCurried(i))}") 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/partial_functions/PartiallyDefinedFunctions.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.partial_functions 2 | 3 | object PartiallyDefinedFunctions { 4 | val squareRoot: PartialFunction[Int, Double] = { 5 | case a if a >= 0 => Math.sqrt(a) 6 | } 7 | 8 | val square: PartialFunction[Int, Double] = { 9 | case a if a < 0 => Math.pow(a, 2) 10 | } 11 | } 12 | 13 | object PartiallyDefinedExample { 14 | import PartiallyDefinedFunctions._ 15 | 16 | def main(args: Array[String]): Unit = { 17 | val items = List(-1, 10, 11, -36, 36, -49, 49, 81) 18 | System.out.println(s"Can we calculate a root for -10: ${squareRoot.isDefinedAt(-10)}") 19 | System.out.println(s"Square roots: ${items.collect(squareRoot)}") 20 | System.out.println(s"Square roots or squares: ${items.collect(squareRoot.orElse(square))}") 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/pimp/PimpExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.pimp 2 | 3 | import com.ivan.nikolov.pimp.model.Person 4 | 5 | object PimpExample { 6 | 7 | def main(args: Array[String]): Unit = { 8 | System.out.println(s"Is 'test' all upper case: ${"test".isAllUpperCase}") 9 | System.out.println(s"Is 'Tes' all upper case: ${"Test".isAllUpperCase}") 10 | System.out.println(s"Is 'TESt' all upper case: ${"TESt".isAllUpperCase}") 11 | System.out.println(s"Is 'TEST' all upper case: ${"TEST".isAllUpperCase}") 12 | } 13 | } 14 | 15 | object PimpExample2 { 16 | def main(args: Array[String]): Unit = { 17 | val people = List( 18 | Person("Ivan", 26), 19 | Person("Maria", 26), 20 | Person("John", 25) 21 | ) 22 | people.saveToDatabase() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/pimp/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.pimp.model 2 | 3 | case class Person(name: String, age: Int) 4 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/pimp/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov 2 | 3 | import com.ivan.nikolov.pimp.model.Person 4 | 5 | package object pimp { 6 | 7 | implicit class StringExtensions(val s: String) extends AnyVal { 8 | 9 | def isAllUpperCase: Boolean = 10 | (0 to s.size - 1).find { 11 | case index => 12 | !s.charAt(index).isUpper 13 | }.isEmpty 14 | 15 | } 16 | 17 | implicit class PersonSeqExtensions(val seq: Iterable[Person]) extends AnyVal { 18 | 19 | def saveToDatabase(): Unit = { 20 | seq.foreach { 21 | case person => 22 | System.out.println(s"Saved: ${person} to the database.") 23 | } 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/stackable/IntQueue.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.stackable 2 | 3 | import scala.collection.mutable.ArrayBuffer 4 | 5 | abstract class IntQueue { 6 | def get(): Int 7 | def put(x: Int) 8 | } 9 | 10 | class BasicIntQueue extends IntQueue { 11 | private val buf = new ArrayBuffer[Int] 12 | def get() = buf.remove(0) 13 | def put(x: Int) { buf += x } 14 | } 15 | 16 | trait Doubling extends IntQueue { 17 | abstract override def put(x: Int) { super.put(2 * x) } 18 | } 19 | 20 | trait Incrementing extends IntQueue { 21 | abstract override def put(x: Int) { super.put(x + 1) } 22 | } 23 | 24 | trait Filtering extends IntQueue { 25 | abstract override def put(x: Int) { 26 | if (x >= 0) super.put(x) 27 | } 28 | } 29 | 30 | object Ex { 31 | def main(args: Array[String]): Unit = { 32 | val queue = new BasicIntQueue with Filtering with Incrementing 33 | queue.put(-1) 34 | queue.put(0) 35 | queue.put(1) 36 | System.out.println(queue.get()) 37 | System.out.println(queue.get()) 38 | System.out.println(queue.get()) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/stackable/StringWriter.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.stackable 2 | 3 | 4 | abstract class StringWriter { 5 | def write(data: String): String 6 | } 7 | 8 | class BasicStringWriter extends StringWriter { 9 | override def write(data: String): String = 10 | s"Writing the following data: ${data}" 11 | } 12 | 13 | trait CapitalizingStringWriter extends StringWriter { 14 | abstract override def write(data: String): String = { 15 | super.write(data.split("\\s+").map(_.capitalize).mkString(" ")) 16 | } 17 | } 18 | 19 | trait UppercasingStringWriter extends StringWriter { 20 | abstract override def write(data: String): String = { 21 | super.write(data.toUpperCase) 22 | } 23 | } 24 | 25 | trait LowercasingStringWriter extends StringWriter { 26 | abstract override def write(data: String): String = { 27 | super.write(data.toLowerCase) 28 | } 29 | } 30 | 31 | object Example { 32 | def main(args: Array[String]): Unit = { 33 | val writer1 = new BasicStringWriter with UppercasingStringWriter with CapitalizingStringWriter 34 | val writer2 = new BasicStringWriter with CapitalizingStringWriter with LowercasingStringWriter 35 | val writer3 = new BasicStringWriter with CapitalizingStringWriter with UppercasingStringWriter with LowercasingStringWriter 36 | val writer4 = new BasicStringWriter with CapitalizingStringWriter with LowercasingStringWriter with UppercasingStringWriter 37 | 38 | System.out.println(s"Writer 1: '${writer1.write("we like learning scala!")}'") 39 | System.out.println(s"Writer 2: '${writer2.write("we like learning scala!")}'") 40 | System.out.println(s"Writer 3: '${writer3.write("we like learning scala!")}'") 41 | System.out.println(s"Writer 4: '${writer4.write("we like learning scala!")}'") 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/type_classes/Number.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.type_classes 2 | 3 | import Math.round 4 | 5 | trait Number[T] { 6 | def plus(x: T, y: T): T 7 | def minus(x: T, y: T): T 8 | def divide(x: T, y: Int): T 9 | def multiply(x: T, y: T): T 10 | def sqrt(x: T): T 11 | } 12 | 13 | object Number { 14 | implicit object DoubleNumber extends Number[Double] { 15 | override def plus(x: Double, y: Double): Double = x + y 16 | override def divide(x: Double, y: Int): Double = x / y 17 | override def multiply(x: Double, y: Double): Double = x * y 18 | override def minus(x: Double, y: Double): Double = x - y 19 | override def sqrt(x: Double): Double = Math.sqrt(x) 20 | } 21 | 22 | implicit object IntNumber extends Number[Int] { 23 | override def plus(x: Int, y: Int): Int = x + y 24 | override def divide(x: Int, y: Int): Int = round(x.toDouble / y.toDouble).toInt 25 | override def multiply(x: Int, y: Int): Int = x * y 26 | override def minus(x: Int, y: Int): Int = x - y 27 | override def sqrt(x: Int): Int = round(Math.sqrt(x)).toInt 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/type_classes/Stats.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.type_classes 2 | 3 | object Stats { 4 | // same as 5 | // def mean[T](xs: Vector[T])(implicit ev: Number[T]): T = 6 | // ev.divide(xs.reduce(ev.plus(_, _)), xs.size) 7 | def mean[T: Number](xs: Vector[T]): T = 8 | implicitly[Number[T]].divide( 9 | xs.reduce(implicitly[Number[T]].plus(_, _)), 10 | xs.size 11 | ) 12 | 13 | // assumes the vector is sorted 14 | def median[T: Number](xs: Vector[T]): T = 15 | xs(xs.size / 2) 16 | 17 | def variance[T: Number](xs: Vector[T]): T = { 18 | val simpleMean = mean(xs) 19 | val sqDiff = xs.map { 20 | case x => 21 | val diff = implicitly[Number[T]].minus(x, simpleMean) 22 | implicitly[Number[T]].multiply(diff, diff) 23 | } 24 | mean(sqDiff) 25 | } 26 | 27 | def stddev[T: Number](xs: Vector[T]): T = 28 | implicitly[Number[T]].sqrt(variance(xs)) 29 | } 30 | -------------------------------------------------------------------------------- /functional-design-patterns/src/main/scala/com/ivan/nikolov/type_classes/StatsExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.type_classes 2 | import Stats._ 3 | 4 | object StatsExample { 5 | def main(args: Array[String]): Unit = { 6 | val intVector = Vector(1, 3, 5, 6, 10, 12, 17, 18, 19, 30, 36, 40, 42, 66) 7 | val doubleVector = Vector(1.5, 3.6, 5.0, 6.6, 10.9, 12.1, 17.3, 18.4, 19.2, 30.9, 36.6, 40.2, 42.3, 66.0) 8 | 9 | System.out.println(s"Mean (int): ${mean(intVector)}") 10 | System.out.println(s"Median (int): ${median(intVector)}") 11 | System.out.println(s"Std dev (int): ${stddev(intVector)}") 12 | 13 | System.out.println(s"Mean (double): ${mean(doubleVector)}") 14 | System.out.println(s"Median (double): ${median(doubleVector)}") 15 | System.out.println(s"Std dev (double): ${stddev(doubleVector)}") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /job-scheduler/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 13 | 2.4.1 14 | 1.3.0 15 | 16 | 17 | job-scheduler 18 | 19 | 20 | 21 | org.slf4j 22 | slf4j-log4j12 23 | ${slf4j.version} 24 | 25 | 26 | com.typesafe.scala-logging 27 | scala-logging_2.11 28 | ${scalalogging.version} 29 | 30 | 31 | com.typesafe 32 | config 33 | ${typesafe.config.version} 34 | 35 | 36 | org.json4s 37 | json4s-native_2.11 38 | ${json4s.native.version} 39 | 40 | 41 | org.json4s 42 | json4s-jackson_2.11 43 | ${json4s.jackson.version} 44 | 45 | 46 | com.typesafe.akka 47 | akka-actor_2.11 48 | ${akka.version} 49 | 50 | 51 | com.h2database 52 | h2 53 | ${h2.version} 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /job-scheduler/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | job-scheduler { 2 | config-path="/etc/scheduler/conf.d" 3 | config-extension="json" 4 | workers=4 5 | db { 6 | connection-string="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" 7 | username="" 8 | password="" 9 | } 10 | } -------------------------------------------------------------------------------- /job-scheduler/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=[%t] %-5p %c %x - %m%n -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/Scheduler.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import com.ivan.nikolov.scheduler.actors.messages.Schedule 5 | import com.typesafe.scalalogging.LazyLogging 6 | 7 | import scala.concurrent.Await 8 | import scala.concurrent.duration.Duration 9 | 10 | object Scheduler extends LazyLogging { 11 | import com.ivan.nikolov.scheduler.registry.ComponentRegistry._ 12 | def main(args: Array[String]): Unit = { 13 | logger.info("Running migrations before doing anything else.") 14 | migrationService.runMigrations() 15 | logger.info("Migrations done!") 16 | 17 | val system = ActorSystem("scheduler") 18 | 19 | val master = system.actorOf( 20 | Props(actorFactory.createMasterActor()), 21 | "scheduler-master" 22 | ) 23 | 24 | sys.addShutdownHook({ 25 | logger.info("Awaiting actor system termination.") 26 | // not great... 27 | Await.result(system.terminate(), Duration.Inf) 28 | logger.info("Actor system terminated. Bye!") 29 | }) 30 | 31 | master ! Schedule(jobConfigReaderService.readJobConfigs()) 32 | logger.info("Started! Use CTRL+C to exit.") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/actors/ActorFactoryComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.actors 2 | 3 | import com.ivan.nikolov.scheduler.config.app.AppConfigComponent 4 | import com.ivan.nikolov.scheduler.dao.DaoServiceComponent 5 | 6 | trait ActorFactory { 7 | def createMasterActor(): Master 8 | def createWorkerActor(): Worker 9 | } 10 | 11 | trait ActorFactoryComponent { 12 | this: AppConfigComponent 13 | with DaoServiceComponent => 14 | 15 | val actorFactory: ActorFactory 16 | 17 | class ActorFactoryImpl extends ActorFactory { 18 | override def createMasterActor(): Master = new Master(appConfigService.workers, this) 19 | 20 | override def createWorkerActor(): Worker = new Worker(daoService) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/actors/Master.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.actors 2 | 3 | import java.time.LocalDateTime 4 | import java.util.concurrent.TimeUnit 5 | 6 | import akka.actor.{Props, Cancellable, Actor} 7 | import akka.routing.RoundRobinPool 8 | import com.ivan.nikolov.scheduler.actors.messages.{Work, Schedule, Done} 9 | import com.ivan.nikolov.scheduler.config.job.{Daily, Hourly} 10 | import com.typesafe.scalalogging.LazyLogging 11 | 12 | import scala.concurrent.duration.Duration 13 | import scala.collection.mutable.ListBuffer 14 | import scala.concurrent.ExecutionContext.Implicits.global 15 | 16 | class Master(numWorkers: Int, actorFactory: ActorFactory) extends Actor with LazyLogging { 17 | val cancelables = ListBuffer[Cancellable]() 18 | 19 | val router = context.actorOf( 20 | Props(actorFactory.createWorkerActor()).withRouter(RoundRobinPool(numWorkers)), 21 | "scheduler-master-worker-router" 22 | ) 23 | 24 | override def receive: Receive = { 25 | case Done(name, command, jobType, success) => 26 | if (success) { 27 | logger.info("Successfully completed {} ({}).", name, command) 28 | } else { 29 | logger.error("Failure! Command {} ({}) returned a non-zero result code.", name, command) 30 | } 31 | case Schedule(configs) => 32 | configs.foreach { 33 | case config => 34 | val cancellable = this.context.system.scheduler.schedule( 35 | config.timeOptions.getInitialDelay(LocalDateTime.now(), config.frequency), 36 | config.frequency match { 37 | case Hourly => Duration.create(1, TimeUnit.HOURS) 38 | case Daily => Duration.create(1, TimeUnit.DAYS) 39 | }, 40 | router, 41 | Work(config.name, config.command, config.jobType) 42 | ) 43 | cancellable +: cancelables 44 | logger.info("Scheduled: {}", config) 45 | } 46 | } 47 | 48 | override def postStop(): Unit = { 49 | cancelables.foreach(_.cancel()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/actors/Worker.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.actors 2 | 3 | import akka.actor.Actor 4 | import com.ivan.nikolov.scheduler.actors.messages.{Done, Work} 5 | import com.ivan.nikolov.scheduler.config.job.{Sql, Console} 6 | import com.ivan.nikolov.scheduler.dao.DaoService 7 | import com.typesafe.scalalogging.LazyLogging 8 | 9 | import sys.process._ 10 | 11 | class Worker(daoService: DaoService) extends Actor with LazyLogging { 12 | 13 | private def doWork(work: Work): Unit = { 14 | work.jobType match { 15 | case Console => 16 | val result = work.command.! // note - the ! are different methods 17 | sender ! Done(work.name, work.command, work.jobType, result == 0) 18 | case Sql => 19 | val connection = daoService.getConnection() 20 | try { 21 | val statement = connection.prepareStatement(work.command) 22 | val result: List[String] = daoService.executeSelect(statement) { 23 | case rs => 24 | val metadata = rs.getMetaData 25 | val numColumns = metadata.getColumnCount 26 | daoService.readResultSet(rs) { 27 | case row => 28 | (1 to numColumns).map { 29 | case i => 30 | row.getObject(i) 31 | }.mkString("\t") 32 | } 33 | } 34 | logger.info("Sql query results: ") 35 | result.foreach(r => logger.info(r)) 36 | sender ! Done(work.name, work.command, work.jobType, true) 37 | } finally { 38 | connection.close() 39 | } 40 | } 41 | } 42 | 43 | override def receive: Receive = { 44 | case w @ Work(name, command, jobType) => doWork(w) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/actors/messages/SchedulerMessage.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.actors.messages 2 | 3 | import com.ivan.nikolov.scheduler.config.job.{JobConfig, JobType} 4 | 5 | sealed trait SchedulerMessage 6 | case class Work(name: String, command: String, jobType: JobType) 7 | case class Done(name: String, command: String, jobType: JobType, success: Boolean) 8 | case class Schedule(configs: List[JobConfig]) -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/config/app/AppConfigComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.config.app 2 | 3 | import com.typesafe.config.ConfigFactory 4 | 5 | trait AppConfigComponent { 6 | 7 | val appConfigService: AppConfigService 8 | 9 | class AppConfigService() { 10 | //-Dconfig.resource=production.conf for overriding 11 | private val conf = ConfigFactory.load() 12 | private val appConf = conf.getConfig("job-scheduler") 13 | private val db = appConf.getConfig("db") 14 | 15 | val configPath = appConf.getString("config-path") 16 | val configExtension = appConf.getString("config-extension") 17 | val workers = appConf.getInt("workers") 18 | 19 | val dbConnectionString = db.getString("connection-string") 20 | val dbUsername = db.getString("username") 21 | val dbPassword = db.getString("password") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/config/job/JobConfig.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.config.job 2 | 3 | 4 | import org.json4s.FieldSerializer 5 | import org.json4s.JsonAST.JField 6 | 7 | case class JobConfig(name: String, command: String, jobType: JobType, frequency: JobFrequency, timeOptions: TimeOptions) 8 | 9 | object JobConfig { 10 | val jobConfigFieldSerializer = FieldSerializer[JobConfig]( 11 | { 12 | case ("timeOptions", x) => Some("time_options", x) 13 | case ("jobType", x) => Some("type", x) 14 | }, 15 | { 16 | case JField("time_options", x) => JField("timeOptions", x) 17 | case JField("type", x) => JField("jobType", x) 18 | } 19 | ) 20 | 21 | } -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/config/job/JobFrequency.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.config.job 2 | 3 | import org.json4s.CustomSerializer 4 | import org.json4s.JsonAST.{JNull, JString} 5 | 6 | sealed trait JobFrequency 7 | case object Daily extends JobFrequency 8 | case object Hourly extends JobFrequency 9 | 10 | case object JobFrequencySerializer extends CustomSerializer[JobFrequency](format => ( 11 | { 12 | case JString(frequency) => frequency match { 13 | case "Daily" => Daily 14 | case "Hourly" => Hourly 15 | } 16 | case JNull => null 17 | }, 18 | { 19 | case frequency: JobFrequency => JString(frequency.getClass.getSimpleName.replace("$", "")) 20 | } 21 | )) 22 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/config/job/JobType.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.config.job 2 | 3 | import org.json4s.CustomSerializer 4 | import org.json4s.JsonAST.{JNull, JString} 5 | 6 | sealed trait JobType 7 | case object Console extends JobType 8 | case object Sql extends JobType 9 | 10 | object JobTypeSerializer extends CustomSerializer[JobType](format => ( 11 | { 12 | case JString(jobType) => jobType match { 13 | case "Console" => Console 14 | case "Sql" => Sql 15 | } 16 | case JNull => null 17 | }, 18 | { 19 | case jobType: JobType => JString(jobType.getClass.getSimpleName.replace("$", "")) 20 | } 21 | )) -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/config/job/TimeOptions.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.config.job 2 | 3 | import java.time.LocalDateTime 4 | import java.time.temporal.ChronoUnit 5 | import java.util.concurrent.TimeUnit 6 | 7 | import scala.concurrent.duration.{FiniteDuration, Duration} 8 | 9 | case class TimeOptions(hours: Int, minutes: Int) { 10 | if (hours < 0 || hours > 23) { 11 | throw new IllegalArgumentException("Hours must be between 0 and 23: " + hours) 12 | } else if (minutes < 0 || minutes > 59) { 13 | throw new IllegalArgumentException("Minutes must be between 0 and 59: " + minutes) 14 | } 15 | 16 | def getInitialDelay(now: LocalDateTime, frequency: JobFrequency): FiniteDuration = { 17 | val firstRun = now.withHour(hours).withMinute(minutes) 18 | val isBefore = firstRun.isBefore(now) 19 | val actualFirstRun = frequency match { 20 | case Hourly => 21 | var tmp = firstRun 22 | Iterator.continually({tmp = tmp.plusHours(1); tmp}).takeWhile(d => d.isBefore(now)).toList.lastOption.getOrElse(if (isBefore) firstRun else firstRun.minusHours(1)).plusHours(1) 23 | case Daily => 24 | var tmp = firstRun 25 | Iterator.continually({tmp = tmp.plusDays(1); tmp}).takeWhile(d => d.isBefore(now)).toList.lastOption.getOrElse(if (isBefore) firstRun else firstRun.minusDays(1)).plusDays(1) 26 | } 27 | val secondsUntilRun = now.until(actualFirstRun, ChronoUnit.SECONDS) 28 | Duration.create(secondsUntilRun, TimeUnit.SECONDS) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/dao/DaoServiceComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.dao 2 | 3 | import java.sql.{Connection, ResultSet, PreparedStatement} 4 | 5 | trait DaoService { 6 | def getConnection(): Connection 7 | 8 | def executeSelect[T](preparedStatement: PreparedStatement)(f: (ResultSet) => List[T]): List[T] = 9 | try { 10 | f(preparedStatement.executeQuery()) 11 | } finally { 12 | preparedStatement.close() 13 | } 14 | 15 | def readResultSet[T](rs: ResultSet)(f: ResultSet => T): List[T] = 16 | Iterator.continually((rs.next(), rs)).takeWhile(_._1).map { 17 | case (_, row) => 18 | f(rs) 19 | }.toList 20 | } 21 | 22 | trait DaoServiceComponent { 23 | this: DatabaseServiceComponent => 24 | 25 | val daoService: DaoService 26 | 27 | class DaoServiceImpl extends DaoService { 28 | override def getConnection(): Connection = databaseService.getConnection 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/dao/DatabaseServiceComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.dao 2 | 3 | import java.sql.Connection 4 | import javax.sql.DataSource 5 | 6 | import com.ivan.nikolov.scheduler.config.app.AppConfigComponent 7 | import org.h2.jdbcx.JdbcConnectionPool 8 | 9 | trait DatabaseService { 10 | val dbDriver: String 11 | val connectionString: String 12 | val username: String 13 | val password: String 14 | val ds: DataSource 15 | 16 | def getConnection: Connection = ds.getConnection 17 | } 18 | 19 | trait DatabaseServiceComponent { 20 | this: AppConfigComponent => 21 | 22 | val databaseService: DatabaseService 23 | 24 | class H2DatabaseService extends DatabaseService { 25 | override val dbDriver: String = "org.h2.Driver" 26 | override val connectionString: String = appConfigService.dbConnectionString 27 | override val username: String = appConfigService.dbUsername 28 | override val password: String = appConfigService.dbPassword 29 | override val ds: DataSource = JdbcConnectionPool.create(connectionString, username, password) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/io/IOServiceComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.io 2 | 3 | import java.io.File 4 | 5 | trait IOServiceComponent { 6 | 7 | val ioService: IOService 8 | 9 | class IOService { 10 | def getAllFilesWithExtension(basePath: String, extension: String): List[String] = { 11 | val dir = new File(basePath) 12 | if (dir.exists() && dir.isDirectory) { 13 | dir.listFiles().filter(f => f.isFile && f.getPath.toLowerCase.endsWith(s".${extension}")).map { 14 | case f => f.getAbsolutePath 15 | }.toList 16 | } else { 17 | List.empty 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/registry/ComponentRegistry.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.registry 2 | 3 | import com.ivan.nikolov.scheduler.actors.{ActorFactory, ActorFactoryComponent} 4 | import com.ivan.nikolov.scheduler.config.app.AppConfigComponent 5 | import com.ivan.nikolov.scheduler.dao._ 6 | import com.ivan.nikolov.scheduler.io.IOServiceComponent 7 | import com.ivan.nikolov.scheduler.services.JobConfigReaderServiceComponent 8 | 9 | object ComponentRegistry extends AppConfigComponent 10 | with IOServiceComponent 11 | with JobConfigReaderServiceComponent 12 | with DatabaseServiceComponent 13 | with MigrationComponent 14 | with DaoServiceComponent 15 | with ActorFactoryComponent { 16 | 17 | override val appConfigService: ComponentRegistry.AppConfigService = new AppConfigService 18 | override val ioService: ComponentRegistry.IOService = new IOService 19 | override val jobConfigReaderService: ComponentRegistry.JobConfigReaderService = new JobConfigReaderService 20 | override val databaseService: DatabaseService = new H2DatabaseService 21 | override val migrationService: ComponentRegistry.MigrationService = new MigrationService 22 | override val daoService: DaoService = new DaoServiceImpl 23 | override val actorFactory: ActorFactory = new ActorFactoryImpl 24 | } 25 | -------------------------------------------------------------------------------- /job-scheduler/src/main/scala/com/ivan/nikolov/scheduler/services/JobConfigReaderServiceComponent.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.services 2 | 3 | import java.io.File 4 | 5 | import com.ivan.nikolov.scheduler.config.app.AppConfigComponent 6 | import com.ivan.nikolov.scheduler.config.job.{JobTypeSerializer, JobFrequencySerializer, JobConfig} 7 | import com.ivan.nikolov.scheduler.io.IOServiceComponent 8 | import com.typesafe.scalalogging.LazyLogging 9 | import org.json4s._ 10 | import org.json4s.jackson.JsonMethods._ 11 | 12 | trait JobConfigReaderServiceComponent { 13 | this: AppConfigComponent 14 | with IOServiceComponent => 15 | 16 | val jobConfigReaderService: JobConfigReaderService 17 | 18 | class JobConfigReaderService() extends LazyLogging { 19 | private val customSerializers = List( 20 | JobFrequencySerializer, 21 | JobTypeSerializer 22 | ) 23 | implicit val formats = DefaultFormats ++ customSerializers + JobConfig.jobConfigFieldSerializer 24 | 25 | def readJobConfigs(): List[JobConfig] = 26 | ioService.getAllFilesWithExtension(appConfigService.configPath, appConfigService.configExtension).flatMap { 27 | case path => try { 28 | val config = parse(FileInput(new File(path))).extract[JobConfig] 29 | Some(config) 30 | } catch { 31 | case ex: Throwable => 32 | logger.error("Error reading config: {}", path, ex) 33 | None 34 | } 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /job-scheduler/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | job-scheduler { 2 | config-path="/etc/scheduler/conf.d" 3 | config-extension="json" 4 | workers=4 5 | db { 6 | connection-string="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" 7 | username="" 8 | password="" 9 | } 10 | } -------------------------------------------------------------------------------- /job-scheduler/src/test/resources/command.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Test Command", 3 | "command": "ping google.com -c 10", 4 | "frequency": "Daily", 5 | "type": "Console", 6 | "time_options": { 7 | "hours": 12, 8 | "minutes": 10 9 | } 10 | } -------------------------------------------------------------------------------- /job-scheduler/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=[%t] %-5p %c %x - %m%n -------------------------------------------------------------------------------- /job-scheduler/src/test/scala/com/ivan/nikolov/scheduler/TestEnvironment.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler 2 | 3 | import com.ivan.nikolov.scheduler.actors.{ActorFactory, ActorFactoryComponent} 4 | import com.ivan.nikolov.scheduler.config.app.AppConfigComponent 5 | import com.ivan.nikolov.scheduler.dao._ 6 | import com.ivan.nikolov.scheduler.io.IOServiceComponent 7 | import com.ivan.nikolov.scheduler.services.JobConfigReaderServiceComponent 8 | import org.scalatest.mock.MockitoSugar 9 | 10 | import org.mockito.Mockito._ 11 | 12 | trait TestEnvironment 13 | extends AppConfigComponent 14 | with IOServiceComponent 15 | with JobConfigReaderServiceComponent 16 | with DatabaseServiceComponent 17 | with MigrationComponent 18 | with DaoServiceComponent 19 | with ActorFactoryComponent 20 | with MockitoSugar { 21 | 22 | // use the test configuration file. 23 | override val appConfigService: AppConfigService = spy(new AppConfigService) 24 | // override the path here to use the test resources. 25 | when(appConfigService.configPath).thenReturn(this.getClass.getResource("/").getPath) 26 | 27 | override val ioService: IOService = mock[IOService] 28 | override val jobConfigReaderService: JobConfigReaderService = mock[JobConfigReaderService] 29 | override val databaseService: DatabaseService = mock[DatabaseService] 30 | override val migrationService: MigrationService = mock[MigrationService] 31 | override val daoService: DaoService = mock[DaoService] 32 | override val actorFactory: ActorFactory = mock[ActorFactory] 33 | } 34 | -------------------------------------------------------------------------------- /job-scheduler/src/test/scala/com/ivan/nikolov/scheduler/config/job/TimeOptionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.config.job 2 | 3 | import java.time.LocalDateTime 4 | 5 | import org.scalatest.{ShouldMatchers, FlatSpec} 6 | 7 | class TimeOptionsTest extends FlatSpec with ShouldMatchers { 8 | 9 | "getInitialDelay" should "get the right initial delay for hourly less than an hour after now." in { 10 | val now = LocalDateTime.now() 11 | val later = now.plusMinutes(20) 12 | // because of the logic and it will fail otherwise 13 | if (now.getDayOfWeek == later.getDayOfWeek) { 14 | val timeOptions = TimeOptions(later.getHour, later.getMinute) 15 | val result = timeOptions.getInitialDelay(now, Hourly) 16 | result.toMinutes should equal(20) 17 | } 18 | } 19 | 20 | it should "get the right initial delay for hourly more than an hour after now." in { 21 | val now = LocalDateTime.now() 22 | val later = now.plusHours(3) 23 | // because of the logic and it will fail otherwise 24 | if (now.getDayOfWeek == later.getDayOfWeek) { 25 | val timeOptions = TimeOptions(later.getHour, later.getMinute) 26 | val result = timeOptions.getInitialDelay(now, Hourly) 27 | result.toHours should equal(3) 28 | } 29 | } 30 | 31 | it should "get the right initial delay for hourly less than an hour before now." in { 32 | val now = LocalDateTime.now() 33 | val earlier = now.minusMinutes(25) 34 | // because of the logic and it will fail otherwise. 35 | if (earlier.getDayOfWeek == now.getDayOfWeek) { 36 | val timeOptions = TimeOptions(earlier.getHour, earlier.getMinute) 37 | val result = timeOptions.getInitialDelay(now, Hourly) 38 | result.toMinutes should equal(35) 39 | } 40 | } 41 | 42 | it should "get the right initial delay for hourly more than an hour before now." in { 43 | val now = LocalDateTime.now() 44 | val earlier = now.minusHours(1).minusMinutes(25) 45 | // because of the logic and it will fail otherwise. 46 | if (earlier.getDayOfWeek == now.getDayOfWeek) { 47 | val timeOptions = TimeOptions(earlier.getHour, earlier.getMinute) 48 | val result = timeOptions.getInitialDelay(now, Hourly) 49 | result.toMinutes should equal(35) 50 | } 51 | } 52 | 53 | it should "get the right initial delay for daily before now." in { 54 | val now = LocalDateTime.now() 55 | val earlier = now.minusMinutes(25) 56 | // because of the logic and it will fail otherwise. 57 | if (earlier.getDayOfWeek == now.getDayOfWeek) { 58 | val timeOptions = TimeOptions(earlier.getHour, earlier.getMinute) 59 | val result = timeOptions.getInitialDelay(now, Daily) 60 | result.toMinutes should equal(24 * 60 - 25) 61 | } 62 | } 63 | 64 | it should "get the right initial delay for daily after now." in { 65 | val now = LocalDateTime.now() 66 | val later = now.plusMinutes(20) 67 | // because of the logic and it will fail otherwise 68 | if (now.getDayOfWeek == later.getDayOfWeek) { 69 | val timeOptions = TimeOptions(later.getHour, later.getMinute) 70 | val result = timeOptions.getInitialDelay(now, Daily) 71 | result.toMinutes should equal(20) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /job-scheduler/src/test/scala/com/ivan/nikolov/scheduler/dao/DaoServiceTest.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.dao 2 | 3 | import com.ivan.nikolov.scheduler.TestEnvironment 4 | import org.scalatest.{BeforeAndAfter, ShouldMatchers, FlatSpec} 5 | 6 | class DaoServiceTest extends FlatSpec with ShouldMatchers with BeforeAndAfter with TestEnvironment { 7 | 8 | override val databaseService = new H2DatabaseService 9 | override val migrationService = new MigrationService 10 | override val daoService = new DaoServiceImpl 11 | 12 | before { 13 | // we run this here. Generally migrations will only 14 | // be dealing with data layout and we will be able to have 15 | // test classes that insert test data. 16 | migrationService.runMigrations() 17 | } 18 | 19 | after { 20 | migrationService.cleanupDatabase() 21 | } 22 | 23 | "readResultSet" should "properly iterate over a result set and apply a function to it." in { 24 | val connection = daoService.getConnection() 25 | try { 26 | val result = daoService.executeSelect( 27 | connection.prepareStatement( 28 | "SELECT name FROM people" 29 | ) 30 | ) { 31 | case rs => 32 | daoService.readResultSet(rs) { 33 | case row => 34 | row.getString("name") 35 | } 36 | } 37 | result should have size(3) 38 | result should contain("Ivan") 39 | result should contain("Maria") 40 | result should contain("John") 41 | } finally { 42 | connection.close() 43 | 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /job-scheduler/src/test/scala/com/ivan/nikolov/scheduler/services/JobConfigReaderServiceTest.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.scheduler.services 2 | 3 | import com.ivan.nikolov.scheduler.TestEnvironment 4 | import com.ivan.nikolov.scheduler.config.job.{TimeOptions, Daily, JobConfig, Console} 5 | import org.scalatest.{ShouldMatchers, FlatSpec} 6 | 7 | class JobConfigReaderServiceTest extends FlatSpec with ShouldMatchers with TestEnvironment { 8 | 9 | override val ioService: IOService = new IOService 10 | override val jobConfigReaderService: JobConfigReaderService = new JobConfigReaderService 11 | 12 | "readJobConfigs" should "read and parse configurations successfully." in { 13 | val result = jobConfigReaderService.readJobConfigs() 14 | result should have size(1) 15 | result should contain( 16 | JobConfig( 17 | "Test Command", 18 | "ping google.com -c 10", 19 | Console, 20 | Daily, 21 | TimeOptions(12, 10) 22 | ) 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /real-life-applications/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | real-life-applications 13 | 14 | 15 | 16 | org.slf4j 17 | slf4j-log4j12 18 | ${slf4j.version} 19 | 20 | 21 | com.typesafe.scala-logging 22 | scala-logging_2.11 23 | ${scalalogging.version} 24 | 25 | 26 | org.scala-lang 27 | scala-library 28 | ${scala.version} 29 | 30 | 31 | org.scalaz 32 | scalaz-core_2.11 33 | ${scalaz.version} 34 | 35 | 36 | org.scalaz 37 | scalaz-effect_2.11 38 | ${scalaz.version} 39 | 40 | 41 | org.scalaz 42 | scalaz-scalacheck-binding_2.11 43 | ${scalacheck.version} 44 | test 45 | 46 | 47 | com.h2database 48 | h2 49 | ${h2.version} 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /real-life-applications/src/main/scala/com/ivan/nikolov/monads/IOMonadExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monads 2 | 3 | import com.ivan.nikolov.monads.model.Person 4 | 5 | import scalaz._ 6 | import effect._ 7 | import Scalaz._ 8 | import IO._ 9 | 10 | object IOMonadExample { 11 | 12 | def main(args: Array[String]): Unit = { 13 | args match { 14 | case Array(inputFile, isWriteToFile) => 15 | val people = { 16 | for { 17 | line <- readFile(inputFile) 18 | person <- Person.fromArray(line.split("\t")) 19 | } yield person 20 | }.pure[IO] 21 | 22 | System.out.println("Still haven't done any IO!") 23 | System.out.println("About to do some...") 24 | if (isWriteToFile.toBoolean) { 25 | val writePeople = for { 26 | _ <- putStrLn("Read people successfully. Where to write them down?") 27 | outputFile <- readLn 28 | p <- people 29 | _ <- writeFile(outputFile, p.map(_.toString)).pure[IO] 30 | } yield () 31 | System.out.println("Writing to file using toString.") 32 | writePeople.unsafePerformIO 33 | } else { 34 | System.out.println(s"Just got the following people: ${people.unsafePerformIO.toList}") 35 | } 36 | case _ => 37 | System.err.println("Please provide input file and true/false whether to write to file.") 38 | System.exit(-1) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /real-life-applications/src/main/scala/com/ivan/nikolov/monads/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monads.model 2 | 3 | case class Person(name: String, age: Int) 4 | 5 | object Person { 6 | def fromArray(arr: Array[String]): Option[Person] = 7 | arr match { 8 | case Array(name, age) => Some(Person(name, age.toInt)) 9 | case _ => None 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /real-life-applications/src/main/scala/com/ivan/nikolov/monads/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov 2 | 3 | import java.io.{PrintWriter, File} 4 | 5 | import scala.io.Source 6 | 7 | package object monads { 8 | def readFile(path: String) = { 9 | System.out.println(s"Reading file ${path}") 10 | Source.fromFile(path).getLines() 11 | } 12 | 13 | def writeFile(path: String, lines: Iterator[String]) = { 14 | System.out.println(s"Writing file ${path}") 15 | val file = new File(path) 16 | printToFile(file) { p => lines.foreach(p.println) } 17 | } 18 | 19 | private def printToFile(file: File)(writeOp: PrintWriter => Unit): Unit = { 20 | val writer = new PrintWriter(file) 21 | try { 22 | writeOp(writer) 23 | } finally { 24 | writer.close() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /real-life-applications/src/main/scala/com/ivan/nikolov/monoids/MonoidsExample.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monoids 2 | 3 | import scalaz._ 4 | import Scalaz._ 5 | 6 | object MonoidsExample { 7 | 8 | def main(args: Array[String]): Unit = { 9 | val numbers = List(1, 2, 3, 4, 5, 6) 10 | System.out.println(s"The sum is: ${numbers.foldMap(identity)}") 11 | System.out.println(s"The product (6!) is: ${numbers.foldMap(Tags.Multiplication.apply)}") 12 | val strings = List("This is\n", "a list of\n", "strings!") 13 | System.out.println(strings.foldMap(identity)(stringConcatenation)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /real-life-applications/src/main/scala/com/ivan/nikolov/monoids/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov 2 | 3 | import scalaz.Monoid 4 | 5 | package object monoids { 6 | 7 | // Int addition and int multiplication exist already, 8 | // so we will show them in an example. 9 | 10 | val stringConcatenation = new Monoid[String] { 11 | override def zero: String = "" 12 | 13 | override def append(f1: String, f2: => String): String = 14 | f1 + f2 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /real-life-applications/src/test/scala/com/ivan/nikolov/monoids/MonoidsTest.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.monoids 2 | 3 | import org.scalacheck.Arbitrary 4 | import org.scalatest.prop.Checkers 5 | import org.scalatest.{ShouldMatchers, FlatSpec} 6 | import scalaz._ 7 | import scalacheck.ScalazProperties._ 8 | 9 | class MonoidsTest extends FlatSpec with ShouldMatchers with Checkers { 10 | 11 | implicit def arbString(implicit ev: Arbitrary[String]): Arbitrary[String] = 12 | Arbitrary { ev.arbitrary.map(identity) } 13 | 14 | "stringConcatenation monoid" should "satisfy the identity rule." in { 15 | check( 16 | monoid.laws[String](stringConcatenation, Equal.equalA[String], arbString) 17 | ) 18 | // run with 19 | // monoid.laws[String](stringConcatenation, Equal.equalA[String], arbString).check 20 | // to get output 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /structural-design-patterns/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | structural-design-patterns 13 | 14 | 15 | 16 | commons-codec 17 | commons-codec 18 | ${commons.codec.version} 19 | 20 | 21 | org.slf4j 22 | slf4j-log4j12 23 | ${slf4j.version} 24 | 25 | 26 | com.typesafe.scala-logging 27 | scala-logging_2.11 28 | ${scalalogging.version} 29 | 30 | 31 | org.json4s 32 | json4s-native_2.11 33 | ${json4s.native.version} 34 | 35 | 36 | org.json4s 37 | json4s-jackson_2.11 38 | ${json4s.jackson.version} 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/resources/com/ivan/nikolov/structural/decorator/data.txt: -------------------------------------------------------------------------------- 1 | this is a data file 2 | which contains lines 3 | and those lines will be 4 | manipulated by our stream reader. -------------------------------------------------------------------------------- /structural-design-patterns/src/main/resources/com/ivan/nikolov/structural/proxy/file1.txt: -------------------------------------------------------------------------------- 1 | I am file 1. -------------------------------------------------------------------------------- /structural-design-patterns/src/main/resources/com/ivan/nikolov/structural/proxy/file2.txt: -------------------------------------------------------------------------------- 1 | I am file 2. -------------------------------------------------------------------------------- /structural-design-patterns/src/main/resources/com/ivan/nikolov/structural/proxy/file3.txt: -------------------------------------------------------------------------------- 1 | I am file 3. -------------------------------------------------------------------------------- /structural-design-patterns/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=[%t] %-5p %c %x - %m%n -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/adapter/AppLogger.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.adapter 2 | 3 | 4 | trait Log { 5 | def info(message: String) 6 | def debug(message: String) 7 | def warning(message: String) 8 | def error(message: String) 9 | } 10 | 11 | class Logger { 12 | def log(message: String, severity: String): Unit = { 13 | System.out.println(s"${severity.toUpperCase}: $message") 14 | } 15 | } 16 | 17 | class AppLogger extends Logger with Log { 18 | override def info(message: String): Unit = 19 | log(message, "info") 20 | 21 | override def warning(message: String): Unit = 22 | log(message, "warning") 23 | 24 | override def error(message: String): Unit = 25 | log(message, "error") 26 | 27 | override def debug(message: String): Unit = 28 | log(message, "debug") 29 | } 30 | 31 | // final 32 | final class FinalLogger { 33 | def log(message: String, severity: String): Unit = { 34 | System.out.println(s"${severity.toUpperCase}: $message") 35 | } 36 | } 37 | 38 | class FinalAppLogger extends Log { 39 | private val logger = new FinalLogger 40 | 41 | override def info(message: String): Unit = 42 | logger.log(message, "info") 43 | 44 | override def warning(message: String): Unit = 45 | logger.log(message, "warning") 46 | 47 | override def error(message: String): Unit = 48 | logger.log(message, "error") 49 | 50 | override def debug(message: String): Unit = 51 | logger.log(message, "debug") 52 | } 53 | // final 54 | 55 | object AdapterExample { 56 | def main(args: Array[String]): Unit = { 57 | val logger = new AppLogger 58 | logger.info("This is an info message.") 59 | logger.debug("Debug something here.") 60 | logger.error("Show an error message.") 61 | logger.warning("About to finish.") 62 | logger.info("Bye!") 63 | } 64 | } 65 | 66 | object AdapterImplicitExample { 67 | def main(args: Array[String]): Unit = { 68 | val logger: Log = new FinalLogger 69 | logger.info("This is an info message.") 70 | logger.debug("Debug something here.") 71 | logger.error("Show an error message.") 72 | logger.warning("About to finish.") 73 | logger.info("Bye!") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/adapter/package.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural 2 | 3 | package object adapter { 4 | 5 | implicit class FinalAppLoggerImplicit(logger: FinalLogger) extends Log { 6 | 7 | override def info(message: String): Unit = 8 | logger.log(message, "info") 9 | 10 | override def warning(message: String): Unit = 11 | logger.log(message, "warning") 12 | 13 | override def error(message: String): Unit = 14 | logger.log(message, "error") 15 | 16 | override def debug(message: String): Unit = 17 | logger.log(message, "debug") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/bridge/Hasher.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.bridge 2 | 3 | import com.ivan.nikolov.structural.bridge.common.Hasher 4 | import org.apache.commons.codec.binary.Hex 5 | 6 | class Sha1Hasher extends Hasher { 7 | override def hash(data: String): String = 8 | new String(Hex.encodeHex(getDigest("SHA-1", data).digest())) 9 | } 10 | 11 | class Sha256Hasher extends Hasher { 12 | override def hash(data: String): String = 13 | new String(Hex.encodeHex(getDigest("SHA-256", data).digest())) 14 | } 15 | 16 | class Md5Hasher extends Hasher { 17 | override def hash(data: String): String = 18 | new String(Hex.encodeHex(getDigest("MD5", data).digest())) 19 | } -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/bridge/PasswordConverter.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.bridge 2 | 3 | import com.ivan.nikolov.structural.bridge.common.Hasher 4 | 5 | abstract class PasswordConverter(hasher: Hasher) { 6 | def convert(password: String): String 7 | } 8 | 9 | class SimplePasswordConverter(hasher: Hasher) extends PasswordConverter(hasher) { 10 | override def convert(password: String): String = 11 | hasher.hash(password) 12 | } 13 | 14 | class SaltedPasswordConverter(salt: String, hasher: Hasher) extends PasswordConverter(hasher) { 15 | override def convert(password: String): String = 16 | hasher.hash(s"${salt}:${password}") 17 | } 18 | 19 | 20 | object BridgeExample { 21 | 22 | def main(args: Array[String]): Unit = { 23 | val p1 = new SimplePasswordConverter(new Sha256Hasher) 24 | val p2 = new SimplePasswordConverter(new Md5Hasher) 25 | val p3 = new SaltedPasswordConverter("8jsdf32T^$%", new Sha1Hasher) 26 | val p4 = new SaltedPasswordConverter("8jsdf32T^$%", new Sha256Hasher) 27 | 28 | System.out.println(s"'password' in SHA-256 is: ${p1.convert("password")}") 29 | System.out.println(s"'1234567890' in MD5 is: ${p2.convert("1234567890")}") 30 | System.out.println(s"'password' in salted SHA-1 is: ${p3.convert("password")}") 31 | System.out.println(s"'password' in salted SHA-256 is: ${p4.convert("password")}") 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/bridge/common/Hasher.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.bridge.common 2 | 3 | import java.security.MessageDigest 4 | 5 | trait Hasher { 6 | def hash(data: String): String 7 | 8 | protected def getDigest(algorithm: String, data: String) = { 9 | val crypt = MessageDigest.getInstance(algorithm) 10 | crypt.reset() 11 | crypt.update(data.getBytes("UTF-8")) 12 | crypt 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/bridge/scala/HasherImpl.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.bridge.scala 2 | 3 | import com.ivan.nikolov.structural.bridge.common.Hasher 4 | import org.apache.commons.codec.binary.Hex 5 | 6 | trait Sha1Hasher extends Hasher { 7 | override def hash(data: String): String = 8 | new String(Hex.encodeHex(getDigest("SHA-1", data).digest())) 9 | } 10 | 11 | trait Sha256Hasher extends Hasher { 12 | override def hash(data: String): String = 13 | new String(Hex.encodeHex(getDigest("SHA-256", data).digest())) 14 | } 15 | 16 | trait Md5Hasher extends Hasher { 17 | override def hash(data: String): String = 18 | new String(Hex.encodeHex(getDigest("MD5", data).digest())) 19 | } 20 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/bridge/scala/PasswordConverterBase.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.bridge.scala 2 | 3 | import com.ivan.nikolov.structural.bridge.common.Hasher 4 | 5 | abstract class PasswordConverterBase { 6 | self: Hasher => 7 | 8 | def convert(password: String): String 9 | } 10 | 11 | class SimplePasswordConverterScala extends PasswordConverterBase { 12 | self: Hasher => 13 | 14 | override def convert(password: String): String = 15 | hash(password) 16 | } 17 | 18 | class SaltedPasswordConverterScala(salt: String) extends PasswordConverterBase { 19 | self: Hasher => 20 | 21 | override def convert(password: String): String = 22 | hash(s"${salt}:${password}") 23 | } 24 | 25 | object ScalaBridgeExample { 26 | def main(args: Array[String]): Unit = { 27 | val p1 = new SimplePasswordConverterScala with Sha256Hasher 28 | val p2 = new SimplePasswordConverterScala with Md5Hasher 29 | val p3 = new SaltedPasswordConverterScala("8jsdf32T^$%") with Sha1Hasher 30 | val p4 = new SaltedPasswordConverterScala("8jsdf32T^$%") with Sha256Hasher 31 | 32 | System.out.println(s"'password' in SHA-256 is: ${p1.convert("password")}") 33 | System.out.println(s"'1234567890' in MD5 is: ${p2.convert("1234567890")}") 34 | System.out.println(s"'password' in salted SHA-1 is: ${p3.convert("password")}") 35 | System.out.println(s"'password' in salted SHA-256 is: ${p4.convert("password")}") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/composite/Node.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.composite 2 | 3 | import scala.collection.mutable.ListBuffer 4 | 5 | trait Node { 6 | def print(prefix: String): Unit 7 | } 8 | 9 | class Leaf(data: String) extends Node { 10 | override def print(prefix: String): Unit = 11 | System.out.println(s"${prefix}${data}") 12 | } 13 | 14 | class Tree extends Node { 15 | private val children = ListBuffer.empty[Node] 16 | 17 | override def print(prefix: String): Unit = { 18 | System.out.println(s"${prefix}(") 19 | children.foreach(_.print(s"${prefix}${prefix}")) 20 | System.out.println(s"${prefix})") 21 | } 22 | 23 | def add(child: Node): Unit = { 24 | children += child 25 | } 26 | 27 | def remove(): Unit = { 28 | if (children.nonEmpty) { 29 | children.remove(0) 30 | } 31 | } 32 | } 33 | 34 | 35 | object CompositeExample { 36 | def main(args: Array[String]): Unit = { 37 | val tree = new Tree 38 | 39 | tree.add(new Leaf("leaf 1")) 40 | 41 | val subtree1 = new Tree 42 | subtree1.add(new Leaf("leaf 2")) 43 | 44 | val subtree2 = new Tree 45 | subtree2.add(new Leaf("leaf 3")) 46 | subtree2.add(new Leaf("leaf 4")) 47 | subtree1.add(subtree2) 48 | 49 | tree.add(subtree1) 50 | 51 | val subtree3 = new Tree 52 | val subtree4 = new Tree 53 | subtree4.add(new Leaf("leaf 5")) 54 | subtree4.add(new Leaf("leaf 6")) 55 | 56 | subtree3.add(subtree4) 57 | tree.add(subtree3) 58 | 59 | tree.print("-") 60 | } 61 | } -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/decorator/CapitalizedInputReaderTrait.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.decorator 2 | 3 | import java.io.{BufferedInputStream, InputStreamReader, BufferedReader, ByteArrayOutputStream} 4 | import java.nio.charset.Charset 5 | import java.util.Base64 6 | import java.util.zip.GZIPOutputStream 7 | 8 | import com.ivan.nikolov.structural.decorator.common.{AdvancedInputReader, InputReader} 9 | import com.typesafe.scalalogging.LazyLogging 10 | 11 | trait CapitalizedInputReaderTrait extends InputReader { 12 | abstract override def readLines(): Stream[String] = super.readLines().map(_.toUpperCase) 13 | } 14 | 15 | trait CompressingInputReaderTrait extends InputReader with LazyLogging { 16 | abstract override def readLines(): Stream[String] = super.readLines().map { 17 | case line => 18 | val text = line.getBytes(Charset.forName("UTF-8")) 19 | logger.info("Length before compression: {}", text.length.toString) 20 | val output = new ByteArrayOutputStream() 21 | val compressor = new GZIPOutputStream(output) 22 | try { 23 | compressor.write(text, 0, text.length) 24 | val outputByteArray = output.toByteArray 25 | logger.info("Length after compression: {}", outputByteArray.length.toString) 26 | new String(outputByteArray, Charset.forName("UTF-8")) 27 | } finally { 28 | compressor.close() 29 | output.close() 30 | } 31 | } 32 | } 33 | 34 | trait Base64EncoderInputReaderTrait extends InputReader { 35 | abstract override def readLines(): Stream[String] = super.readLines().map { 36 | case line => Base64.getEncoder.encodeToString(line.getBytes(Charset.forName("UTF-8"))) 37 | } 38 | } 39 | 40 | object StackableTraitsExample { 41 | def main(args: Array[String]): Unit = { 42 | val stream = new BufferedReader( 43 | new InputStreamReader( 44 | new BufferedInputStream(this.getClass.getResourceAsStream("data.txt")) 45 | ) 46 | ) 47 | try { 48 | val reader = new AdvancedInputReader(stream) with CapitalizedInputReaderTrait 49 | reader.readLines().foreach(println) 50 | } finally { 51 | stream.close() 52 | } 53 | } 54 | } 55 | 56 | object StackableTraitsBigExample { 57 | def main(args: Array[String]): Unit = { 58 | val stream = new BufferedReader( 59 | new InputStreamReader( 60 | new BufferedInputStream(this.getClass.getResourceAsStream("data.txt")) 61 | ) 62 | ) 63 | try { 64 | val reader = new AdvancedInputReader(stream) with CapitalizedInputReaderTrait with Base64EncoderInputReaderTrait with CompressingInputReaderTrait 65 | reader.readLines().foreach(println) 66 | } finally { 67 | stream.close() 68 | } 69 | } 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/decorator/InputReaderDecorator.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.decorator 2 | 3 | import java.io.{InputStreamReader, BufferedInputStream, ByteArrayOutputStream, BufferedReader} 4 | import java.nio.charset.Charset 5 | import java.util.Base64 6 | import java.util.zip.GZIPOutputStream 7 | 8 | import com.ivan.nikolov.structural.decorator.common.{AdvancedInputReader, InputReader} 9 | import com.typesafe.scalalogging.LazyLogging 10 | 11 | abstract class InputReaderDecorator(inputReader: InputReader) extends InputReader { 12 | override def readLines(): Stream[String] = inputReader.readLines() 13 | } 14 | 15 | class CapitalizedInputReader(inputReader: InputReader) extends InputReaderDecorator(inputReader) { 16 | override def readLines(): Stream[String] = super.readLines().map(_.toUpperCase) 17 | } 18 | 19 | class CompressingInputReader(inputReader: InputReader) extends InputReaderDecorator(inputReader) with LazyLogging { 20 | override def readLines(): Stream[String] = super.readLines().map { 21 | case line => 22 | val text = line.getBytes(Charset.forName("UTF-8")) 23 | logger.info("Length before compression: {}", text.length.toString) 24 | val output = new ByteArrayOutputStream() 25 | val compressor = new GZIPOutputStream(output) 26 | try { 27 | compressor.write(text, 0, text.length) 28 | val outputByteArray = output.toByteArray 29 | logger.info("Length after compression: {}", outputByteArray.length.toString) 30 | new String(outputByteArray, Charset.forName("UTF-8")) 31 | } finally { 32 | compressor.close() 33 | output.close() 34 | } 35 | } 36 | } 37 | 38 | class Base64EncoderInputReader(inputReader: InputReader) extends InputReaderDecorator(inputReader) { 39 | override def readLines(): Stream[String] = super.readLines().map { 40 | case line => Base64.getEncoder.encodeToString(line.getBytes(Charset.forName("UTF-8"))) 41 | } 42 | } 43 | 44 | object DecoratorExample { 45 | def main(args: Array[String]): Unit = { 46 | val stream = new BufferedReader( 47 | new InputStreamReader( 48 | new BufferedInputStream(this.getClass.getResourceAsStream("data.txt")) 49 | ) 50 | ) 51 | try { 52 | val reader = new CapitalizedInputReader(new AdvancedInputReader(stream)) 53 | reader.readLines().foreach(println) 54 | } finally { 55 | stream.close() 56 | } 57 | } 58 | } 59 | 60 | object DecoratorExampleBig { 61 | def main(args: Array[String]): Unit = { 62 | val stream = new BufferedReader( 63 | new InputStreamReader( 64 | new BufferedInputStream(this.getClass.getResourceAsStream("data.txt")) 65 | ) 66 | ) 67 | try { 68 | val reader = new CompressingInputReader( 69 | new Base64EncoderInputReader( 70 | new CapitalizedInputReader( 71 | new AdvancedInputReader(stream) 72 | ) 73 | ) 74 | ) 75 | reader.readLines().foreach(println) 76 | } finally { 77 | stream.close() 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/decorator/common/InputReader.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.decorator.common 2 | 3 | import java.io.BufferedReader 4 | 5 | import scala.collection.JavaConverters._ 6 | 7 | trait InputReader { 8 | def readLines(): Stream[String] 9 | } 10 | 11 | class AdvancedInputReader(reader: BufferedReader) extends InputReader { 12 | override def readLines(): Stream[String] = reader.lines().iterator().asScala.toStream 13 | } 14 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/facade/DataDecoder.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.facade 2 | 3 | import java.util.Base64 4 | 5 | trait DataDecoder { 6 | 7 | def decode(data: Array[Byte]): String = 8 | new String(Base64.getDecoder.decode(data), "UTF-8") 9 | } 10 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/facade/DataDeserializer.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.facade 2 | 3 | import org.json4s._ 4 | import org.json4s.jackson.JsonMethods 5 | 6 | trait DataDeserializer { 7 | implicit val formats = DefaultFormats 8 | 9 | def parse[T](data: String)(implicit m: Manifest[T]): T = 10 | JsonMethods.parse(StringInput(data)).extract[T] 11 | } 12 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/facade/DataDownloader.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.facade 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | 5 | trait DataDownloader extends LazyLogging { 6 | 7 | def download(url: String): Array[Byte] = { 8 | logger.info("Downloading from: {}", url) 9 | Thread.sleep(5000) 10 | // { 11 | // "name": "Ivan", 12 | // "age": 26 13 | // } 14 | // the string below is the Base64 encoded Json above. 15 | "ew0KICAgICJuYW1lIjogIkl2YW4iLA0KICAgICJhZ2UiOiAyNg0KfQ==".getBytes 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/facade/DataReader.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.facade 2 | 3 | import com.ivan.nikolov.structural.facade.model.Person 4 | 5 | class DataReader extends DataDownloader with DataDecoder with DataDeserializer { 6 | 7 | def readPerson(url: String): Person = { 8 | val data = download(url) 9 | val json = decode(data) 10 | parse[Person](json) 11 | } 12 | } 13 | 14 | object FacadeExample { 15 | def main(args: Array[String]): Unit = { 16 | val reader = new DataReader 17 | System.out.println(s"We just read the following person: ${reader.readPerson("http://www.ivan-nikolov.com/")}") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/facade/model/Person.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.facade.model 2 | 3 | case class Person(name: String, age: Int) 4 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/flyweight/Circle.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.flyweight 2 | 3 | import scala.collection.mutable.{ListBuffer, Map} 4 | 5 | class Circle(color: Color) { 6 | System.out.println(s"Creating a circle with $color color.") 7 | 8 | override def toString(): String = 9 | s"Circle($color)" 10 | } 11 | 12 | object Circle { 13 | val cache = Map.empty[Color, Circle] 14 | 15 | def apply(color: Color): Circle = 16 | cache.getOrElseUpdate(color, new Circle(color)) 17 | 18 | def circlesCreated(): Int = cache.size 19 | } 20 | 21 | class Graphic { 22 | val items = ListBuffer.empty[(Int, Int, Double, Circle)] 23 | 24 | def addCircle(x: Int, y: Int, radius: Double, circle: Circle): Unit = { 25 | items += ((x, y, radius, circle)) 26 | } 27 | 28 | def draw(): Unit = { 29 | items.foreach { 30 | case (x, y, radius, circle) => 31 | System.out.println(s"Drawing a circle at ($x, $y) with radius $radius: $circle") 32 | } 33 | } 34 | } 35 | 36 | object FlyweightExample { 37 | def main(args: Array[String]): Unit = { 38 | val graphic = new Graphic 39 | graphic.addCircle(1, 1, 1.0, Circle(Green)) 40 | graphic.addCircle(1, 2, 1.0, Circle(Red)) 41 | graphic.addCircle(2, 1, 1.0, Circle(Blue)) 42 | graphic.addCircle(2, 2, 1.0, Circle(Green)) 43 | graphic.addCircle(2, 3, 1.0, Circle(Yellow)) 44 | graphic.addCircle(3, 2, 1.0, Circle(Magenta)) 45 | graphic.addCircle(3, 3, 1.0, Circle(Blue)) 46 | graphic.addCircle(4, 3, 1.0, Circle(Blue)) 47 | graphic.addCircle(3, 4, 1.0, Circle(Yellow)) 48 | graphic.addCircle(4, 4, 1.0, Circle(Red)) 49 | 50 | graphic.draw() 51 | 52 | System.out.println(s"Total number of circle objects created: ${Circle.circlesCreated()}") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/flyweight/Color.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.flyweight 2 | 3 | sealed abstract class Color 4 | case object Red extends Color 5 | case object Green extends Color 6 | case object Blue extends Color 7 | case object Yellow extends Color 8 | case object Magenta extends Color 9 | -------------------------------------------------------------------------------- /structural-design-patterns/src/main/scala/com/ivan/nikolov/structural/proxy/FileReader.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.structural.proxy 2 | 3 | import java.io.{BufferedReader, InputStreamReader} 4 | import scala.collection.JavaConverters._ 5 | 6 | trait FileReader { 7 | def readFileContents(): String 8 | } 9 | 10 | class FileReaderReal(filename: String) extends FileReader { 11 | val contents = { 12 | val stream = this.getClass.getResourceAsStream(filename) 13 | val reader = new BufferedReader( 14 | new InputStreamReader( 15 | stream 16 | ) 17 | ) 18 | try { 19 | reader.lines().iterator().asScala.mkString(System.getProperty("line.separator")) 20 | } finally { 21 | reader.close() 22 | stream.close() 23 | } 24 | } 25 | 26 | System.out.println(s"Finished reading the actual file: $filename") 27 | 28 | override def readFileContents(): String = contents 29 | } 30 | 31 | class FileReaderProxy(filename: String) extends FileReader { 32 | private var fileReader: FileReaderReal = null 33 | 34 | override def readFileContents(): String = { 35 | if (fileReader == null) { 36 | fileReader = new FileReaderReal(filename) 37 | } 38 | fileReader.readFileContents() 39 | } 40 | } 41 | 42 | object ProxyExample { 43 | def main(args: Array[String]): Unit = { 44 | val fileMap = Map( 45 | "file1.txt" -> new FileReaderProxy("file1.txt"), 46 | "file2.txt" -> new FileReaderProxy("file2.txt"), 47 | "file3.txt" -> new FileReaderProxy("file3.txt"), 48 | "file4.txt" -> new FileReaderReal("file1.txt") 49 | ) 50 | System.out.println("Created the map. You should have seen file1.txt read because it wasn't used in a proxy.") 51 | System.out.println(s"Reading file1.txt from the proxy: ${fileMap("file1.txt").readFileContents()}") 52 | System.out.println(s"Reading file3.txt from the proxy: ${fileMap("file3.txt").readFileContents()}") 53 | } 54 | } -------------------------------------------------------------------------------- /traits/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | traits 13 | 14 | 15 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/basic/Beeper.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.basic 2 | 3 | trait Beeper { 4 | def beep(times: Int): Unit = { 5 | assert(times >= 0) 6 | 1 to times foreach(i => System.out.println(s"Beep number: $i")) 7 | } 8 | } 9 | 10 | object BeeperRunner { 11 | val TIMES = 10 12 | 13 | def main (args: Array[String]): Unit = { 14 | val beeper = new Beeper {} 15 | beeper.beep(TIMES) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/basic/NotifierImpl.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.basic 2 | 3 | import com.ivan.nikolov.common.Notifier 4 | 5 | class NotifierImpl(val notificationMessage: String) extends Notifier { 6 | override def clear(): Unit = System.out.println("cleared") 7 | } 8 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/basic/Ping.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.basic 2 | 3 | trait Ping { 4 | def ping(): Unit = { 5 | System.out.println("ping") 6 | } 7 | } 8 | 9 | trait Pong { 10 | def pong(): Unit = { 11 | System.out.println("pong") 12 | } 13 | } 14 | 15 | /** 16 | * Do not this. Use mixin composition instead. 17 | */ 18 | trait PingPong extends Ping with Pong { 19 | def pingPong(): Unit = { 20 | ping() 21 | pong() 22 | } 23 | } 24 | 25 | object Runner extends PingPong { 26 | def main(args: Array[String]): Unit = { 27 | pingPong() 28 | } 29 | } 30 | 31 | object MixinRunner extends Ping with Pong { 32 | def main(args: Array[String]): Unit = { 33 | ping() 34 | pong() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/common/Alarm.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.common 2 | 3 | trait Alarm { 4 | def trigger(): String 5 | } 6 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/common/Connector.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.common 2 | 3 | abstract class Connector { 4 | def connect() 5 | 6 | def close() 7 | } 8 | 9 | trait ConnectorWithHelper extends Connector { 10 | 11 | def findDriver(): Unit = { 12 | System.out.println("Find driver called.") 13 | } 14 | } 15 | 16 | class PgSqlConnector extends ConnectorWithHelper { 17 | override def connect(): Unit = { 18 | System.out.println("Connected...") 19 | } 20 | 21 | override def close(): Unit = { 22 | System.out.println("Closed...") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/common/Notifier.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.common 2 | 3 | trait Notifier { 4 | val notificationMessage: String 5 | 6 | def printNotification(): Unit = { 7 | System.out.println(notificationMessage) 8 | } 9 | 10 | def clear() 11 | } 12 | 13 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/composition/Clashing.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.composition 2 | 3 | trait A { 4 | def hello(): String = "Hello, I am trait A!" 5 | def pass(a: Int): String = s"Trait A said: 'You passed $a.'" 6 | //def value(a: Int): Int = a 7 | } 8 | 9 | trait B { 10 | def hello(): String = "Hello, I am trait B!" 11 | def pass(a: String): String = s"Trait B said: 'You passed $a.'" 12 | //def value(a: Int): String = a.toString 13 | } 14 | 15 | object Clashing extends A with B { 16 | override def hello(): String = super[A].hello() 17 | def helloB(): String = super[B].hello() 18 | 19 | def main(args: Array[String]): Unit = { 20 | System.out.println(hello()) 21 | System.out.println(helloB()) 22 | System.out.println(pass(1)) 23 | System.out.println(pass("hello")) 24 | } 25 | } 26 | 27 | // Same signature, different return types example 28 | trait C { 29 | def value(a: Int): Int = a 30 | } 31 | 32 | trait D { 33 | def value(a: Int): String = a.toString 34 | } 35 | 36 | object Example { 37 | 38 | val c = new C {} 39 | val d = new D {} 40 | 41 | def main (args: Array[String]): Unit = { 42 | System.out.println(s"c.value: ${c.value(10)}") 43 | System.out.println(s"d.value: ${d.value(10)}") 44 | } 45 | } 46 | // Same signature, different return types example 47 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/composition/Greeter.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.composition 2 | 3 | 4 | trait FormalGreeting { 5 | def hello(): String 6 | } 7 | 8 | trait InformalGreeting { 9 | def hello(): String 10 | } 11 | 12 | class Greeter extends FormalGreeting with InformalGreeting { 13 | override def hello(): String = "Good morning, sir/madam!" 14 | } 15 | 16 | object GreeterUser { 17 | def main(args: Array[String]): Unit = { 18 | val greeter = new Greeter() 19 | System.out.println(greeter.hello()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/composition/Watch.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.composition 2 | 3 | import com.ivan.nikolov.common.{ConnectorWithHelper, Notifier, Alarm} 4 | import com.ivan.nikolov.composition.self_types.AlarmNotifier 5 | 6 | class Watch(brand: String, initialTime: Long) { 7 | def getTime(): Long = System.currentTimeMillis() - initialTime 8 | } 9 | 10 | object WatchUser { 11 | def main(args: Array[String]): Unit = { 12 | val expensiveWatch = new Watch("expensive brand", 1000L) with Alarm with Notifier { 13 | override def trigger(): String = "The alarm was triggered." 14 | 15 | override def clear(): Unit = { 16 | System.out.println("Alarm cleared.") 17 | } 18 | 19 | override val notificationMessage: String = "Alarm is running!" 20 | } 21 | val cheapWatch = new Watch("cheap brand", 1000L) with Alarm { 22 | override def trigger(): String = "The alarm was triggered." 23 | } 24 | // show some watch usage. 25 | System.out.println(expensiveWatch.trigger()) 26 | expensiveWatch.printNotification() 27 | System.out.println(s"The time is ${expensiveWatch.getTime()}.") 28 | expensiveWatch.clear() 29 | 30 | System.out.println(cheapWatch.trigger()) 31 | System.out.println("Cheap watches cannot manually stop the alarm...") 32 | } 33 | } 34 | 35 | // Uncomment if you want to see the error. 36 | //object ReallyExpensiveWatchUser { 37 | // def main(args: Array[String]): Unit = { 38 | // val reallyExpensiveWatch = new Watch("really expensive brand", 1000L) with ConnectorWithHelper { 39 | // override def connect(): Unit = { 40 | // System.out.println("Connected with another connector.") 41 | // } 42 | // 43 | // override def close(): Unit = { 44 | // System.out.println("Closed with another connector.") 45 | // } 46 | // } 47 | // 48 | // System.out.println("Using the really expensive watch.") 49 | // reallyExpensiveWatch.findDriver() 50 | // reallyExpensiveWatch.connect() 51 | // reallyExpensiveWatch.close() 52 | // } 53 | //} 54 | 55 | object SelfTypeWatchUser { 56 | def main(args: Array[String]): Unit = { 57 | // uncomment to see the self-type error. 58 | // val watch = new Watch("alarm with notification", 1000L) with AlarmNotifier { 59 | // } 60 | 61 | val watch = new Watch("alarm with notification", 1000L) with AlarmNotifier with Notifier { 62 | override def trigger(): String = "Alarm triggered." 63 | 64 | override def clear(): Unit = { 65 | System.out.println("Alarm cleared.") 66 | } 67 | 68 | override val notificationMessage: String = "The notification." 69 | } 70 | 71 | System.out.println(watch.trigger()) 72 | watch.printNotification() 73 | System.out.println(s"The time is ${watch.getTime()}.") 74 | watch.clear() 75 | } 76 | } -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/composition/self_types/AlarmNotifier.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.composition.self_types 2 | 3 | import com.ivan.nikolov.common.Notifier 4 | 5 | trait AlarmNotifier { 6 | this: Notifier => 7 | 8 | def trigger(): String 9 | } 10 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/diamond/Diamond.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.diamond 2 | 3 | trait A { 4 | def hello(): String = "Hello from A" 5 | } 6 | 7 | trait B extends A { 8 | override def hello(): String = "Hello from B" 9 | } 10 | 11 | trait C extends A { 12 | override def hello(): String = "Hello from C" 13 | } 14 | 15 | trait D extends B with C { 16 | 17 | } 18 | 19 | object Diamond extends D { 20 | def main(args: Array[String]): Unit = { 21 | System.out.println(hello()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/linearisation/Animal.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.linearisation 2 | 3 | // Animal -> AnyRef -> Any 4 | class Animal extends AnyRef 5 | 6 | // Dog -> Animal -> AnyRef -> Any 7 | class Dog extends Animal 8 | -------------------------------------------------------------------------------- /traits/src/main/scala/com/ivan/nikolov/linearisation/MultiplierIdentity.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.linearisation 2 | 3 | class MultiplierIdentity { 4 | def identity: Int = 1 5 | } 6 | 7 | trait DoubledMultiplierIdentity extends MultiplierIdentity { 8 | override def identity: Int = 2 * super.identity 9 | } 10 | 11 | trait TripledMultiplierIdentity extends MultiplierIdentity { 12 | override def identity: Int = 3 * super.identity 13 | } 14 | 15 | // first Doubled, then Tripled 16 | class ModifiedIdentity1 extends DoubledMultiplierIdentity with TripledMultiplierIdentity 17 | 18 | class ModifiedIdentity2 extends DoubledMultiplierIdentity with TripledMultiplierIdentity { 19 | override def identity: Int = super[DoubledMultiplierIdentity].identity 20 | } 21 | 22 | class ModifiedIdentity3 extends DoubledMultiplierIdentity with TripledMultiplierIdentity { 23 | override def identity: Int = super[TripledMultiplierIdentity].identity 24 | } 25 | // first Doubled, then Tripled 26 | 27 | // first Tripled, then Doubled 28 | class ModifiedIdentity4 extends TripledMultiplierIdentity with DoubledMultiplierIdentity 29 | 30 | class ModifiedIdentity5 extends TripledMultiplierIdentity with DoubledMultiplierIdentity { 31 | override def identity: Int = super[DoubledMultiplierIdentity].identity 32 | } 33 | 34 | class ModifiedIdentity6 extends TripledMultiplierIdentity with DoubledMultiplierIdentity { 35 | override def identity: Int = super[TripledMultiplierIdentity].identity 36 | } 37 | // first Tripled, then Doubled 38 | 39 | object ModifiedIdentityUser { 40 | 41 | def main(args: Array[String]): Unit = { 42 | val instance1 = new ModifiedIdentity1 43 | val instance2 = new ModifiedIdentity2 44 | val instance3 = new ModifiedIdentity3 45 | val instance4 = new ModifiedIdentity4 46 | val instance5 = new ModifiedIdentity5 47 | val instance6 = new ModifiedIdentity6 48 | 49 | System.out.println(s"Result 1: ${instance1.identity}") 50 | System.out.println(s"Result 2: ${instance2.identity}") 51 | System.out.println(s"Result 3: ${instance3.identity}") 52 | System.out.println(s"Result 4: ${instance4.identity}") 53 | System.out.println(s"Result 5: ${instance5.identity}") 54 | System.out.println(s"Result 6: ${instance6.identity}") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /traits/src/test/scala/com/ivan/nikolov/composition/TraitACaseScopeTest.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.composition 2 | 3 | import org.scalatest.{ShouldMatchers, FlatSpec} 4 | 5 | class TraitACaseScopeTest extends FlatSpec with ShouldMatchers { 6 | 7 | "hello" should "greet properly." in new A { 8 | hello() should equal("Hello, I am trait A!") 9 | } 10 | 11 | "pass" should "return the right string with the number." in new A { 12 | pass(10) should equal("Trait A said: 'You passed 10.'") 13 | } 14 | 15 | it should "be correct also for negative values." in new A { 16 | pass(-10) should equal("Trait A said: 'You passed -10.'") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /traits/src/test/scala/com/ivan/nikolov/composition/TraitATest.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.composition 2 | 3 | import org.scalatest.{ShouldMatchers, FlatSpec} 4 | 5 | class TraitATest extends FlatSpec with ShouldMatchers with A { 6 | 7 | "hello" should "greet properly." in { 8 | hello() should equal("Hello, I am trait A!") 9 | } 10 | 11 | "pass" should "return the right string with the number." in { 12 | pass(10) should equal("Trait A said: 'You passed 10.'") 13 | } 14 | 15 | it should "be correct also for negative values." in { 16 | pass(-10) should equal("Trait A said: 'You passed -10.'") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /traits/src/test/scala/com/ivan/nikolov/linearisation/DoubledMultiplierIdentityTest.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.linearisation 2 | 3 | import org.scalatest.{ShouldMatchers, FlatSpec} 4 | 5 | class DoubledMultiplierIdentityTest extends FlatSpec with ShouldMatchers { 6 | 7 | class DoubledMultiplierIdentityClass extends DoubledMultiplierIdentity 8 | 9 | val instance = new DoubledMultiplierIdentityClass 10 | 11 | "identity" should "return 2 * 1" in { 12 | instance.identity should equal(2) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /unification/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | scala-design-patterns 7 | com.ivan.nikolov 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | unification 13 | 14 | 15 | -------------------------------------------------------------------------------- /unification/src/main/scala/com/ivan/nikolov/unification/adts/Month.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.unification.adts 2 | 3 | sealed abstract trait Month 4 | case object January extends Month 5 | case object February extends Month 6 | case object March extends Month 7 | case object April extends Month 8 | case object May extends Month 9 | case object June extends Month 10 | case object July extends Month 11 | case object August extends Month 12 | case object September extends Month 13 | case object October extends Month 14 | case object November extends Month 15 | case object December extends Month 16 | 17 | object Month { 18 | def toInt(month: Month): Int = 19 | month match { 20 | case January => 1 21 | case February => 2 22 | case March => 3 23 | case April => 4 24 | case May => 5 25 | case June => 6 26 | case July => 7 27 | case August => 8 28 | case September => 9 29 | case October => 10 30 | case November => 11 31 | case December => 12 32 | } 33 | } 34 | 35 | object MonthDemo { 36 | def main(args: Array[String]): Unit = { 37 | val month: Month = February 38 | System.out.println(s"The current month is: $month and it's number ${Month.toInt(month)}") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /unification/src/main/scala/com/ivan/nikolov/unification/adts/RGB.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.unification.adts 2 | 3 | sealed case class RGB(red: Int, green: Int, blue: Int) 4 | 5 | object RGBDemo { 6 | def main(args: Array[String]): Unit = { 7 | val magenta = RGB(255, 0, 255) 8 | System.out.println(s"Magenta in RGB is: $magenta") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /unification/src/main/scala/com/ivan/nikolov/unification/adts/Shape.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.unification.adts 2 | 3 | case class Point(x: Double, y: Double) 4 | 5 | sealed abstract trait Shape 6 | case class Circle(centre: Point, radius: Double) extends Shape 7 | case class Rectangle(topLeft: Point, height: Double, width: Double) extends Shape 8 | 9 | object Shape { 10 | def area(shape: Shape): Double = 11 | shape match { 12 | case Circle(Point(x, y), radius) => Math.PI * Math.pow(radius, 2) 13 | case Rectangle(_, h, w) => h * w 14 | } 15 | } 16 | 17 | object ShapeDemo { 18 | def main(args: Array[String]): Unit = { 19 | val circle = Circle(Point(1, 2), 2.5) 20 | val rect = Rectangle(Point(6, 7), 5, 6) 21 | 22 | System.out.println(s"The circle area is: ${Shape.area(circle)}") 23 | System.out.println(s"The rectangle area is: ${Shape.area(rect)}") 24 | } 25 | } -------------------------------------------------------------------------------- /unification/src/main/scala/com/ivan/nikolov/unification/functions/FunctionLiterals.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.unification.functions 2 | 3 | class FunctionLiterals { 4 | val sum = (a: Int, b: Int) => a + b 5 | 6 | def runOperation(f: (Int, Int) => Int, a: Int, b: Int): Int = { 7 | f(a, b) 8 | } 9 | } 10 | 11 | object FunctionLiterals { 12 | 13 | def main(args: Array[String]): Unit = { 14 | val obj = new FunctionLiterals 15 | System.out.println(s"3 + 9 = ${obj.sum(3, 9)}") 16 | System.out.println(s"Calling run operation: ${obj.runOperation(obj.sum, 10, 20)}") 17 | System.out.println(s"Using Math.max: ${obj.runOperation(Math.max, 10, 20)}") 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /unification/src/main/scala/com/ivan/nikolov/unification/functions/FunctionObjects.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.unification.functions 2 | 3 | 4 | class SumFunction extends Function2[Int, Int, Int] { 5 | override def apply(v1: Int, v2: Int): Int = v1 + v2 6 | } 7 | 8 | class FunctionObjects { 9 | val sum = new SumFunction 10 | 11 | def runOperation(f: (Int, Int) => Int, a: Int, b: Int): Int = 12 | f(a, b) 13 | } 14 | 15 | object FunctionObjects { 16 | 17 | def main(args: Array[String]): Unit = { 18 | val obj = new FunctionObjects 19 | System.out.println(s"3 + 9 = ${obj.sum(3, 9)}") 20 | System.out.println(s"Calling run operation: ${obj.runOperation(obj.sum, 10, 20)}") 21 | System.out.println(s"Using Math.max: ${obj.runOperation(Math.max, 10, 20)}") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /unification/src/main/scala/com/ivan/nikolov/unification/modules/Alarm.scala: -------------------------------------------------------------------------------- 1 | package com.ivan.nikolov.unification.modules 2 | 3 | trait Tick { 4 | trait Ticker { 5 | def count(): Int 6 | def tick(): Unit 7 | } 8 | def ticker: Ticker 9 | } 10 | 11 | trait TickUser extends Tick { 12 | class TickUserImpl extends Ticker { 13 | var curr = 0 14 | 15 | override def count(): Int = curr 16 | 17 | override def tick(): Unit = { 18 | curr = curr + 1 19 | } 20 | } 21 | object ticker extends TickUserImpl 22 | } 23 | 24 | trait Alarm { 25 | trait Alarmer { 26 | def trigger(): Unit 27 | } 28 | def alarm: Alarmer 29 | } 30 | 31 | trait AlarmUser extends Alarm with Tick { 32 | class AlarmUserImpl extends Alarmer { 33 | override def trigger(): Unit = { 34 | if (ticker.count() % 10 == 0) { 35 | System.out.println(s"Alarm triggered at ${ticker.count()}!") 36 | } 37 | } 38 | } 39 | object alarm extends AlarmUserImpl 40 | } 41 | 42 | object ModuleDemo extends AlarmUser with TickUser { 43 | def main(args: Array[String]): Unit = { 44 | System.out.println("Running the ticker. Should trigger the alarm every 10 times.") 45 | (1 to 100).foreach { 46 | case i => 47 | ticker.tick() 48 | alarm.trigger() 49 | } 50 | } 51 | } 52 | --------------------------------------------------------------------------------