├── .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 |
--------------------------------------------------------------------------------