diff --git a/README.md b/README.md index 1fddc9f..6c65e26 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Notes on studying Scala 3, mainly from reading documentation such as: Aside from this README, the [tests](src/test/scala/Suite.scala) contain many runnable examples of some concepts explored here and many only explored there. +There is also a [Jupyter notebooks](notebooks) directory containing a few more in-depth explorations of some of the content treated here. + ## Syntax ### Assignment diff --git a/notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb new file mode 100644 index 0000000..d9de0ff --- /dev/null +++ b/notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb @@ -0,0 +1,375 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f80155d4-102c-4d6e-af61-bc22b4bf4b72", + "metadata": {}, + "source": [ + "# Notes on the \"Service-oriented design\" OOP domain modelling example\n", + "\n", + "This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.\n", + "\n", + "The example is in turn based on the paper by Martin Odersky and Matthias Zenger, [\"Scalable Component Abstractions\"](https://doi.org/10.1145/1094811.1094815).\n", + "\n", + "> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)" + ] + }, + { + "cell_type": "markdown", + "id": "00b31d02-3aaa-45c4-86f4-7aa0720c4bd5", + "metadata": {}, + "source": [ + "## The `SubjectObserver` trait" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "04b5468d-6c11-4d2b-8824-6a0210c2ca38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defined \u001b[32mtrait\u001b[39m \u001b[36mSubjectObserver\u001b[39m" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trait SubjectObserver:\n", + " type S <: Subject\n", + " type O <: Observer\n", + "\n", + " trait Subject:\n", + " self: S =>\n", + " private var observers: List[O] = List()\n", + " def subscribe(obs: O): Unit =\n", + " observers = obs :: observers\n", + " def publish() =\n", + " for obs <- observers do obs.notify(this)\n", + "\n", + " trait Observer:\n", + " def notify(sub: S): Unit" + ] + }, + { + "cell_type": "markdown", + "id": "1cc48aa5-4b2d-4b44-95d1-0eef09cc48e7", + "metadata": {}, + "source": [ + "### Top-level definitions" + ] + }, + { + "cell_type": "markdown", + "id": "b4a412a5-4252-4e57-9035-90b33581141d", + "metadata": {}, + "source": [ + "In the `SubjectObserver` trait's top level, four members are defined:\n", + "\n", + "Two abstract types:\n", + "- `S` (implying subject)\n", + "- `O` (implying observer)\n", + "\n", + "Two other traits:\n", + "- `Subject`\n", + "- `Observer`" + ] + }, + { + "cell_type": "markdown", + "id": "3e1e377f-941d-436b-a8d7-9e28683b3efa", + "metadata": {}, + "source": [ + "### Type definitions" + ] + }, + { + "cell_type": "markdown", + "id": "59db0e6d-1013-4751-a81e-2cd204af5b22", + "metadata": {}, + "source": [ + "The syntax `type S <: Subject`, called an \"**upper bound**\", is a declaration for an abstract type `S` which must be a subtype of `Subject`.\n", + "\n", + "Meaning that:\n", + "> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.\n", + "\n", + "Similarly, a type `O` is also defined, which must be a subtype of `Observer`." + ] + }, + { + "cell_type": "markdown", + "id": "a03d3dbd-e675-45af-bda2-297b634bed19", + "metadata": {}, + "source": [ + "### Trait definitions" + ] + }, + { + "cell_type": "markdown", + "id": "fc0f38b8-14af-4cfe-a47b-e27344e52a4c", + "metadata": {}, + "source": [ + "Each minor trait has the following members:\n", + "\n", + "- **Subject**\n", + " - `private var observers: List[0] = List()`\n", + " - `def subscribe(obs: O: Unit = ...`\n", + " - `def publish() = ...`\n", + "- **Observer**\n", + " - `def notify(sub: S): Unit`\n", + "\n", + "All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract." + ] + }, + { + "cell_type": "markdown", + "id": "7fc1cea9-09c3-4f08-a014-f03c6ba70c51", + "metadata": {}, + "source": [ + "#### Subject" + ] + }, + { + "cell_type": "markdown", + "id": "786b27b8-e217-430f-bfed-e0ad227b2109", + "metadata": {}, + "source": [ + "The `Subject` trait defines three concrete members:\n", + "- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`\n", + "- Two public, concrete methods whose behavior depend on this field:\n", + " - `subscribe(obs: O): Unit`\n", + " - `publish(): Unit`\n", + "\n", + "The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.\n", + "\n", + "The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument." + ] + }, + { + "cell_type": "markdown", + "id": "a28c62aa-c39a-430d-989c-91941d331b81", + "metadata": {}, + "source": [ + "Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:\n", + "\n", + "> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`." + ] + }, + { + "cell_type": "markdown", + "id": "cf4deab8-d89c-4729-a2e2-2c2eaf1d335c", + "metadata": {}, + "source": [ + "#### Observer" + ] + }, + { + "cell_type": "markdown", + "id": "e3e028d1-1913-472d-96b8-44560239dd51", + "metadata": {}, + "source": [ + "The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.\n", + "\n", + "My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something." + ] + }, + { + "cell_type": "markdown", + "id": "1ccb2747-02b3-4617-8c16-5fa6fce51e8d", + "metadata": {}, + "source": [ + "## A `SubjectObserver` implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a11f16d2-ab41-4351-9613-926bb4f5dbb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defined \u001b[32mobject\u001b[39m \u001b[36mSensorReader\u001b[39m" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "object SensorReader extends SubjectObserver:\n", + " type S = Sensor\n", + " type O = Display\n", + "\n", + " class Sensor(val label: String) extends Subject:\n", + " private var currentValue = 0.0\n", + " def value = currentValue\n", + " def changeValue(v: Double) =\n", + " currentValue = v\n", + " publish()\n", + "\n", + " class Display extends Observer:\n", + " def notify(sub: Sensor) =\n", + " println(s\"${sub.label} has value ${sub.value}\")" + ] + }, + { + "cell_type": "markdown", + "id": "96c9767d-9dcb-4a7d-9009-cc32cb319c2e", + "metadata": {}, + "source": [ + "Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.\n", + "\n", + "To satisfy its contract, it defines:\n", + "- A type `S`, referring to the trait's \"subject\" concept, which is assigned the class `Sensor`, this class defined in the body of the object\n", + "- A type `O`, referring to the trait's \"observer\" concept, which is assigned to the class `Display`, also defined in the body of the object\n", + "\n", + "It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:\n", + "\n", + "- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`\n", + "- The class `Display` takes no arguments for its constructor and extends `Observer`\n", + "\n", + "The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.\n", + "\n", + "However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0. This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers.\n", + "\n", + "The `Display` class however must implement a `notify(sub: S)` method that handles a notification event.\n", + "\n", + "It does so by printing to STDOUT the interpolated string `\"${sub.label} has value ${sub.value}\"`\n", + "\n", + "The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait." + ] + }, + { + "cell_type": "markdown", + "id": "688f3a5b-510c-48a2-8b05-8273a3d220ef", + "metadata": {}, + "source": [ + "The documentation emphasizes how this design demonstrates an object-oriented paradigm:\n", + "\n", + "> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:\n", + "\n", + "> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.\n", + "> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.\n", + "> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`." + ] + }, + { + "cell_type": "markdown", + "id": "2e11c23a-c3ec-4934-80fc-9eaacca745ab", + "metadata": {}, + "source": [ + "It also makes an interesting observation:\n", + "\n", + "> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.\n", + "\n", + "It is alluding to the contract established in the nested `Observer` trait:\n", + "\n", + "```scala\n", + "trait SubjectObserver:\n", + " type S <: Subject\n", + " // ...\n", + "\n", + " trait Subject:\n", + " self: S =>\n", + " // ...\n", + "\n", + "trait Observer:\n", + " def notify(sub: S): Unit\n", + "```\n", + "\n", + "Because `S` " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8afa140-88a1-450c-885a-af355f3ee3dc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "990c0e8a-6495-4561-8833-8e426d7af7e7", + "metadata": {}, + "source": [ + "## Invoking the `SensorReader` logic" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a62d18c3-cc2e-4368-b1a4-159e3c464ec7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sensor1 has value 2.0\n", + "sensor1 has value 2.0\n", + "sensor2 has value 3.0\n" + ] + }, + { + "data": { + "text/plain": [ + "\u001b[32mimport \u001b[39m\u001b[36mSensorReader.*\n", + "\n", + "// setting up a network\n", + "\u001b[39m\n", + "\u001b[36ms1\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@dcc17ef\n", + "\u001b[36ms2\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@1b7edae8\n", + "\u001b[36md1\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@7ea3d970\n", + "\u001b[36md2\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@4c7c8b68" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import SensorReader.*\n", + "\n", + "// setting up a network\n", + "val s1 = Sensor(\"sensor1\")\n", + "val s2 = Sensor(\"sensor2\")\n", + "val d1 = SensorReader.Display()\n", + "val d2 = SensorReader.Display()\n", + "s1.subscribe(d1)\n", + "s1.subscribe(d2)\n", + "s2.subscribe(d1)\n", + "\n", + "// propagating updates through the network\n", + "s1.changeValue(2)\n", + "s2.changeValue(3)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Scala", + "language": "scala", + "name": "scala" + }, + "language_info": { + "codemirror_mode": "text/x-scala", + "file_extension": ".sc", + "mimetype": "text/x-scala", + "name": "scala", + "nbconvert_exporter": "script", + "version": "2.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/README.md b/notebooks/README.md new file mode 100644 index 0000000..5e69aa1 --- /dev/null +++ b/notebooks/README.md @@ -0,0 +1,9 @@ + +# scala-notes notebooks + +These Jupyter Notebooks go a bit more in depth on some of the explored content: + +- [Service-oriented design](service-oriented-design) + - [Markdown](service-oriented-design/service-oriented-design.md) + - [HTML](service-oriented-design/service-oriented-design.html) + - [PDF](service-oriented-design/service-oriented-design.pdf) diff --git a/notebooks/service-oriented-design/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb b/notebooks/service-oriented-design/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb new file mode 100644 index 0000000..79e53ff --- /dev/null +++ b/notebooks/service-oriented-design/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb @@ -0,0 +1,442 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f80155d4-102c-4d6e-af61-bc22b4bf4b72", + "metadata": {}, + "source": [ + "# Notes on the \"Service-oriented design\" OOP domain modelling example\n", + "\n", + "This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.\n", + "\n", + "The example is in turn based on the paper by Martin Odersky and Matthias Zenger, [\"Scalable Component Abstractions\"](https://doi.org/10.1145/1094811.1094815).\n", + "\n", + "> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)" + ] + }, + { + "cell_type": "markdown", + "id": "00b31d02-3aaa-45c4-86f4-7aa0720c4bd5", + "metadata": {}, + "source": [ + "## The `SubjectObserver` trait" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "04b5468d-6c11-4d2b-8824-6a0210c2ca38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defined \u001b[32mtrait\u001b[39m \u001b[36mSubjectObserver\u001b[39m" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trait SubjectObserver:\n", + " type S <: Subject\n", + " type O <: Observer\n", + "\n", + " trait Subject:\n", + " self: S =>\n", + " private var observers: List[O] = List()\n", + " def subscribe(obs: O): Unit =\n", + " observers = obs :: observers\n", + " def publish() =\n", + " for obs <- observers do obs.notify(this)\n", + "\n", + " trait Observer:\n", + " def notify(sub: S): Unit" + ] + }, + { + "cell_type": "markdown", + "id": "1cc48aa5-4b2d-4b44-95d1-0eef09cc48e7", + "metadata": {}, + "source": [ + "### Top-level definitions" + ] + }, + { + "cell_type": "markdown", + "id": "b4a412a5-4252-4e57-9035-90b33581141d", + "metadata": {}, + "source": [ + "In the `SubjectObserver` trait's top level, four members are defined:\n", + "\n", + "Two abstract types:\n", + "- `S` (implying subject)\n", + "- `O` (implying observer)\n", + "\n", + "Two other traits:\n", + "- `Subject`\n", + "- `Observer`" + ] + }, + { + "cell_type": "markdown", + "id": "3e1e377f-941d-436b-a8d7-9e28683b3efa", + "metadata": {}, + "source": [ + "### Type definitions" + ] + }, + { + "cell_type": "markdown", + "id": "59db0e6d-1013-4751-a81e-2cd204af5b22", + "metadata": {}, + "source": [ + "The syntax `type S <: Subject`, called an \"**upper bound**\", is a declaration for an abstract type `S` which must be a subtype of `Subject`.\n", + "\n", + "Meaning that:\n", + "> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.\n", + "\n", + "Similarly, a type `O` is also defined, which must be a subtype of `Observer`." + ] + }, + { + "cell_type": "markdown", + "id": "a03d3dbd-e675-45af-bda2-297b634bed19", + "metadata": {}, + "source": [ + "### Trait definitions" + ] + }, + { + "cell_type": "markdown", + "id": "fc0f38b8-14af-4cfe-a47b-e27344e52a4c", + "metadata": {}, + "source": [ + "Each minor trait has the following members:\n", + "\n", + "```scala\n", + "trait Subject:\n", + " private var observers: List[O] = List()\n", + " def subscribe(obs: O): Unit = // ...\n", + " def publish() = // ...\n", + "trait Observer:\n", + " def notify(sub: S): Unit\n", + "```\n", + "\n", + "All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract." + ] + }, + { + "cell_type": "markdown", + "id": "7fc1cea9-09c3-4f08-a014-f03c6ba70c51", + "metadata": {}, + "source": [ + "#### Subject" + ] + }, + { + "cell_type": "markdown", + "id": "786b27b8-e217-430f-bfed-e0ad227b2109", + "metadata": {}, + "source": [ + "The `Subject` trait defines three concrete members:\n", + "- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`\n", + "- Two public, concrete methods whose behavior depend on this field:\n", + " - `subscribe(obs: O): Unit`\n", + " - `publish(): Unit`\n", + "\n", + "These have concrete bodies defined as such:\n", + "\n", + "```scala\n", + "def subscribe(obs: O): Unit = observers = obs :: observers\n", + "def publish() = for obs <- observers do obs.notify(this)\n", + "```\n", + "\n", + "The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.\n", + "\n", + "The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument." + ] + }, + { + "cell_type": "markdown", + "id": "a28c62aa-c39a-430d-989c-91941d331b81", + "metadata": {}, + "source": [ + "Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:\n", + "\n", + "> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`." + ] + }, + { + "cell_type": "markdown", + "id": "cf4deab8-d89c-4729-a2e2-2c2eaf1d335c", + "metadata": {}, + "source": [ + "#### Observer" + ] + }, + { + "cell_type": "markdown", + "id": "e3e028d1-1913-472d-96b8-44560239dd51", + "metadata": {}, + "source": [ + "The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.\n", + "\n", + "My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something." + ] + }, + { + "cell_type": "markdown", + "id": "1ccb2747-02b3-4617-8c16-5fa6fce51e8d", + "metadata": {}, + "source": [ + "## A `SubjectObserver` implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a11f16d2-ab41-4351-9613-926bb4f5dbb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defined \u001b[32mobject\u001b[39m \u001b[36mSensorReader\u001b[39m" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "object SensorReader extends SubjectObserver:\n", + " type S = Sensor\n", + " type O = Display\n", + "\n", + " class Sensor(val label: String) extends Subject:\n", + " private var currentValue = 0.0\n", + " def value = currentValue\n", + " def changeValue(v: Double) =\n", + " currentValue = v\n", + " publish()\n", + "\n", + " class Display extends Observer:\n", + " def notify(sub: Sensor) =\n", + " println(s\"${sub.label} has value ${sub.value}\")" + ] + }, + { + "cell_type": "markdown", + "id": "3baad2b4-c896-4a58-ba0a-9f45569eb7b1", + "metadata": {}, + "source": [ + "Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.\n", + "\n", + "To satisfy its contract, it defines:\n", + "\n", + "```scala\n", + "type S = Sensor\n", + "type O = Display\n", + "```\n", + "\n", + "- A type `S` — referring to the trait's \"subject\" concept — assigned the class `Sensor`, this class defined in the body of the object\n", + "- A type `O` — referring to the trait's \"observer\" concept — which is assigned to the class `Display`, also defined in the body of the object" + ] + }, + { + "cell_type": "markdown", + "id": "ab7f33b6-1882-4f7a-b581-18e6f0069158", + "metadata": {}, + "source": [ + "It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:\n", + "\n", + "```scala\n", + "class Sensor(val label: String) extends Subject:\n", + " // ....\n", + " \n", + "class Display extends Observer:\n", + " def notify(sub: Sensor) =\n", + " // ...\n", + "```\n", + "\n", + "- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`\n", + "- The class `Display` takes no arguments for its constructor and extends `Observer`" + ] + }, + { + "cell_type": "markdown", + "id": "55d77fd6-085a-48f9-8873-f5b0e80135fa", + "metadata": {}, + "source": [ + "The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.\n", + "\n", + "However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0:\n", + "\n", + "```scala\n", + "class Sensor(val label: String) extends Subject:\n", + " private var currentValue = 0.0\n", + " def value = currentValue\n", + " def changeValue(v: Double) =\n", + " currentValue = v\n", + " publish()\n", + "```\n", + "\n", + "This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers." + ] + }, + { + "cell_type": "markdown", + "id": "d0766a74-badf-4112-9260-c34b4d91b31c", + "metadata": {}, + "source": [ + "The `Display` class must yet implement a `notify(sub: S)` method that handles a notification event.\n", + "\n", + "It does so by printing to STDOUT the interpolated string `\"${sub.label} has value ${sub.value}\"`:\n", + "\n", + "```scala\n", + "class Display extends Observer:\n", + " def notify(sub: Sensor) =\n", + " println(s\"${sub.label} has value ${sub.value}\")\n", + "```\n", + "\n", + "The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait." + ] + }, + { + "cell_type": "markdown", + "id": "688f3a5b-510c-48a2-8b05-8273a3d220ef", + "metadata": {}, + "source": [ + "The documentation emphasizes how this design demonstrates an object-oriented paradigm:\n", + "\n", + "> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:\n", + "\n", + "> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.\n", + "> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.\n", + "> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`." + ] + }, + { + "cell_type": "markdown", + "id": "2e11c23a-c3ec-4934-80fc-9eaacca745ab", + "metadata": {}, + "source": [ + "It also makes the following observation:\n", + "\n", + "> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.\n", + "\n", + "It is alluding to the contract established in the nested `Observer` trait:\n", + "\n", + "```scala\n", + "trait SubjectObserver:\n", + " type S <: Subject\n", + " // ...\n", + "\n", + " trait Subject:\n", + " self: S =>\n", + " // ...\n", + "\n", + "trait Observer:\n", + " def notify(sub: S): Unit\n", + "```\n", + "\n", + "Considering that `S` had to be a subtype of `Subject`, as per its `type S <: Subject` definition, and given the self-type annotation `self: S =>` in the body of the `Subject` trait established that the subtypes of `Subject` (in this case, `Sensor`) have to also be subtypes of `S`, then:\n", + "\n", + "```scala\n", + "Sensor <: Subject && Sensor <: S\n", + "```\n", + "\n", + "Because of this double constraint, `Sensor` can pass itself as a subtype of `Subject` instead of as an instance of `Subject` when `publish(this)` is called.\n", + "\n", + "The implication of safety being, possibly, that [through subtyping](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#subtyping), the instance of `Sensor` can be used where an extended implementation of `Subject` is expected, but insofar as it is also the type assigned to `S`." + ] + }, + { + "cell_type": "markdown", + "id": "990c0e8a-6495-4561-8833-8e426d7af7e7", + "metadata": {}, + "source": [ + "## Invoking the `SensorReader` logic" + ] + }, + { + "cell_type": "markdown", + "id": "1a527837-272e-424f-9594-31d43bb1d4d9", + "metadata": {}, + "source": [ + "In the example below, the output is two times an identical message printed for the same change in the `currentValue` of `sensor1`, since it has two different observers subscribed (`d1` and `d2`), and a single message printed for the change to the value of `s2` from its one observer `d1`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a62d18c3-cc2e-4368-b1a4-159e3c464ec7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sensor1 has value 2.0\n", + "sensor1 has value 2.0\n", + "sensor2 has value 3.0\n" + ] + }, + { + "data": { + "text/plain": [ + "\u001b[32mimport \u001b[39m\u001b[36mSensorReader.*\n", + "\n", + "// setting up a network\n", + "\u001b[39m\n", + "\u001b[36ms1\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@2a8e138f\n", + "\u001b[36ms2\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@10fc52e9\n", + "\u001b[36md1\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@38234ace\n", + "\u001b[36md2\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@182f632a" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import SensorReader.*\n", + "\n", + "// setting up a network\n", + "val s1 = Sensor(\"sensor1\")\n", + "val s2 = Sensor(\"sensor2\")\n", + "val d1 = SensorReader.Display()\n", + "val d2 = SensorReader.Display()\n", + "s1.subscribe(d1)\n", + "s1.subscribe(d2)\n", + "s2.subscribe(d1)\n", + "\n", + "// propagating updates through the network\n", + "s1.changeValue(2)\n", + "s2.changeValue(3)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Scala", + "language": "scala", + "name": "scala" + }, + "language_info": { + "codemirror_mode": "text/x-scala", + "file_extension": ".sc", + "mimetype": "text/x-scala", + "name": "scala", + "nbconvert_exporter": "script", + "version": "2.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/service-oriented-design/service-oriented-design.html b/notebooks/service-oriented-design/service-oriented-design.html new file mode 100644 index 0000000..1d481ee --- /dev/null +++ b/notebooks/service-oriented-design/service-oriented-design.html @@ -0,0 +1,7965 @@ + + + + + +service-oriented-design + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+ + diff --git a/notebooks/service-oriented-design/service-oriented-design.ipynb b/notebooks/service-oriented-design/service-oriented-design.ipynb new file mode 100644 index 0000000..79e53ff --- /dev/null +++ b/notebooks/service-oriented-design/service-oriented-design.ipynb @@ -0,0 +1,442 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f80155d4-102c-4d6e-af61-bc22b4bf4b72", + "metadata": {}, + "source": [ + "# Notes on the \"Service-oriented design\" OOP domain modelling example\n", + "\n", + "This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.\n", + "\n", + "The example is in turn based on the paper by Martin Odersky and Matthias Zenger, [\"Scalable Component Abstractions\"](https://doi.org/10.1145/1094811.1094815).\n", + "\n", + "> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)" + ] + }, + { + "cell_type": "markdown", + "id": "00b31d02-3aaa-45c4-86f4-7aa0720c4bd5", + "metadata": {}, + "source": [ + "## The `SubjectObserver` trait" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "04b5468d-6c11-4d2b-8824-6a0210c2ca38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defined \u001b[32mtrait\u001b[39m \u001b[36mSubjectObserver\u001b[39m" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trait SubjectObserver:\n", + " type S <: Subject\n", + " type O <: Observer\n", + "\n", + " trait Subject:\n", + " self: S =>\n", + " private var observers: List[O] = List()\n", + " def subscribe(obs: O): Unit =\n", + " observers = obs :: observers\n", + " def publish() =\n", + " for obs <- observers do obs.notify(this)\n", + "\n", + " trait Observer:\n", + " def notify(sub: S): Unit" + ] + }, + { + "cell_type": "markdown", + "id": "1cc48aa5-4b2d-4b44-95d1-0eef09cc48e7", + "metadata": {}, + "source": [ + "### Top-level definitions" + ] + }, + { + "cell_type": "markdown", + "id": "b4a412a5-4252-4e57-9035-90b33581141d", + "metadata": {}, + "source": [ + "In the `SubjectObserver` trait's top level, four members are defined:\n", + "\n", + "Two abstract types:\n", + "- `S` (implying subject)\n", + "- `O` (implying observer)\n", + "\n", + "Two other traits:\n", + "- `Subject`\n", + "- `Observer`" + ] + }, + { + "cell_type": "markdown", + "id": "3e1e377f-941d-436b-a8d7-9e28683b3efa", + "metadata": {}, + "source": [ + "### Type definitions" + ] + }, + { + "cell_type": "markdown", + "id": "59db0e6d-1013-4751-a81e-2cd204af5b22", + "metadata": {}, + "source": [ + "The syntax `type S <: Subject`, called an \"**upper bound**\", is a declaration for an abstract type `S` which must be a subtype of `Subject`.\n", + "\n", + "Meaning that:\n", + "> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.\n", + "\n", + "Similarly, a type `O` is also defined, which must be a subtype of `Observer`." + ] + }, + { + "cell_type": "markdown", + "id": "a03d3dbd-e675-45af-bda2-297b634bed19", + "metadata": {}, + "source": [ + "### Trait definitions" + ] + }, + { + "cell_type": "markdown", + "id": "fc0f38b8-14af-4cfe-a47b-e27344e52a4c", + "metadata": {}, + "source": [ + "Each minor trait has the following members:\n", + "\n", + "```scala\n", + "trait Subject:\n", + " private var observers: List[O] = List()\n", + " def subscribe(obs: O): Unit = // ...\n", + " def publish() = // ...\n", + "trait Observer:\n", + " def notify(sub: S): Unit\n", + "```\n", + "\n", + "All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract." + ] + }, + { + "cell_type": "markdown", + "id": "7fc1cea9-09c3-4f08-a014-f03c6ba70c51", + "metadata": {}, + "source": [ + "#### Subject" + ] + }, + { + "cell_type": "markdown", + "id": "786b27b8-e217-430f-bfed-e0ad227b2109", + "metadata": {}, + "source": [ + "The `Subject` trait defines three concrete members:\n", + "- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`\n", + "- Two public, concrete methods whose behavior depend on this field:\n", + " - `subscribe(obs: O): Unit`\n", + " - `publish(): Unit`\n", + "\n", + "These have concrete bodies defined as such:\n", + "\n", + "```scala\n", + "def subscribe(obs: O): Unit = observers = obs :: observers\n", + "def publish() = for obs <- observers do obs.notify(this)\n", + "```\n", + "\n", + "The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.\n", + "\n", + "The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument." + ] + }, + { + "cell_type": "markdown", + "id": "a28c62aa-c39a-430d-989c-91941d331b81", + "metadata": {}, + "source": [ + "Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:\n", + "\n", + "> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`." + ] + }, + { + "cell_type": "markdown", + "id": "cf4deab8-d89c-4729-a2e2-2c2eaf1d335c", + "metadata": {}, + "source": [ + "#### Observer" + ] + }, + { + "cell_type": "markdown", + "id": "e3e028d1-1913-472d-96b8-44560239dd51", + "metadata": {}, + "source": [ + "The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.\n", + "\n", + "My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something." + ] + }, + { + "cell_type": "markdown", + "id": "1ccb2747-02b3-4617-8c16-5fa6fce51e8d", + "metadata": {}, + "source": [ + "## A `SubjectObserver` implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a11f16d2-ab41-4351-9613-926bb4f5dbb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defined \u001b[32mobject\u001b[39m \u001b[36mSensorReader\u001b[39m" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "object SensorReader extends SubjectObserver:\n", + " type S = Sensor\n", + " type O = Display\n", + "\n", + " class Sensor(val label: String) extends Subject:\n", + " private var currentValue = 0.0\n", + " def value = currentValue\n", + " def changeValue(v: Double) =\n", + " currentValue = v\n", + " publish()\n", + "\n", + " class Display extends Observer:\n", + " def notify(sub: Sensor) =\n", + " println(s\"${sub.label} has value ${sub.value}\")" + ] + }, + { + "cell_type": "markdown", + "id": "3baad2b4-c896-4a58-ba0a-9f45569eb7b1", + "metadata": {}, + "source": [ + "Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.\n", + "\n", + "To satisfy its contract, it defines:\n", + "\n", + "```scala\n", + "type S = Sensor\n", + "type O = Display\n", + "```\n", + "\n", + "- A type `S` — referring to the trait's \"subject\" concept — assigned the class `Sensor`, this class defined in the body of the object\n", + "- A type `O` — referring to the trait's \"observer\" concept — which is assigned to the class `Display`, also defined in the body of the object" + ] + }, + { + "cell_type": "markdown", + "id": "ab7f33b6-1882-4f7a-b581-18e6f0069158", + "metadata": {}, + "source": [ + "It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:\n", + "\n", + "```scala\n", + "class Sensor(val label: String) extends Subject:\n", + " // ....\n", + " \n", + "class Display extends Observer:\n", + " def notify(sub: Sensor) =\n", + " // ...\n", + "```\n", + "\n", + "- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`\n", + "- The class `Display` takes no arguments for its constructor and extends `Observer`" + ] + }, + { + "cell_type": "markdown", + "id": "55d77fd6-085a-48f9-8873-f5b0e80135fa", + "metadata": {}, + "source": [ + "The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.\n", + "\n", + "However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0:\n", + "\n", + "```scala\n", + "class Sensor(val label: String) extends Subject:\n", + " private var currentValue = 0.0\n", + " def value = currentValue\n", + " def changeValue(v: Double) =\n", + " currentValue = v\n", + " publish()\n", + "```\n", + "\n", + "This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers." + ] + }, + { + "cell_type": "markdown", + "id": "d0766a74-badf-4112-9260-c34b4d91b31c", + "metadata": {}, + "source": [ + "The `Display` class must yet implement a `notify(sub: S)` method that handles a notification event.\n", + "\n", + "It does so by printing to STDOUT the interpolated string `\"${sub.label} has value ${sub.value}\"`:\n", + "\n", + "```scala\n", + "class Display extends Observer:\n", + " def notify(sub: Sensor) =\n", + " println(s\"${sub.label} has value ${sub.value}\")\n", + "```\n", + "\n", + "The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait." + ] + }, + { + "cell_type": "markdown", + "id": "688f3a5b-510c-48a2-8b05-8273a3d220ef", + "metadata": {}, + "source": [ + "The documentation emphasizes how this design demonstrates an object-oriented paradigm:\n", + "\n", + "> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:\n", + "\n", + "> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.\n", + "> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.\n", + "> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`." + ] + }, + { + "cell_type": "markdown", + "id": "2e11c23a-c3ec-4934-80fc-9eaacca745ab", + "metadata": {}, + "source": [ + "It also makes the following observation:\n", + "\n", + "> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.\n", + "\n", + "It is alluding to the contract established in the nested `Observer` trait:\n", + "\n", + "```scala\n", + "trait SubjectObserver:\n", + " type S <: Subject\n", + " // ...\n", + "\n", + " trait Subject:\n", + " self: S =>\n", + " // ...\n", + "\n", + "trait Observer:\n", + " def notify(sub: S): Unit\n", + "```\n", + "\n", + "Considering that `S` had to be a subtype of `Subject`, as per its `type S <: Subject` definition, and given the self-type annotation `self: S =>` in the body of the `Subject` trait established that the subtypes of `Subject` (in this case, `Sensor`) have to also be subtypes of `S`, then:\n", + "\n", + "```scala\n", + "Sensor <: Subject && Sensor <: S\n", + "```\n", + "\n", + "Because of this double constraint, `Sensor` can pass itself as a subtype of `Subject` instead of as an instance of `Subject` when `publish(this)` is called.\n", + "\n", + "The implication of safety being, possibly, that [through subtyping](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#subtyping), the instance of `Sensor` can be used where an extended implementation of `Subject` is expected, but insofar as it is also the type assigned to `S`." + ] + }, + { + "cell_type": "markdown", + "id": "990c0e8a-6495-4561-8833-8e426d7af7e7", + "metadata": {}, + "source": [ + "## Invoking the `SensorReader` logic" + ] + }, + { + "cell_type": "markdown", + "id": "1a527837-272e-424f-9594-31d43bb1d4d9", + "metadata": {}, + "source": [ + "In the example below, the output is two times an identical message printed for the same change in the `currentValue` of `sensor1`, since it has two different observers subscribed (`d1` and `d2`), and a single message printed for the change to the value of `s2` from its one observer `d1`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a62d18c3-cc2e-4368-b1a4-159e3c464ec7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sensor1 has value 2.0\n", + "sensor1 has value 2.0\n", + "sensor2 has value 3.0\n" + ] + }, + { + "data": { + "text/plain": [ + "\u001b[32mimport \u001b[39m\u001b[36mSensorReader.*\n", + "\n", + "// setting up a network\n", + "\u001b[39m\n", + "\u001b[36ms1\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@2a8e138f\n", + "\u001b[36ms2\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@10fc52e9\n", + "\u001b[36md1\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@38234ace\n", + "\u001b[36md2\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@182f632a" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import SensorReader.*\n", + "\n", + "// setting up a network\n", + "val s1 = Sensor(\"sensor1\")\n", + "val s2 = Sensor(\"sensor2\")\n", + "val d1 = SensorReader.Display()\n", + "val d2 = SensorReader.Display()\n", + "s1.subscribe(d1)\n", + "s1.subscribe(d2)\n", + "s2.subscribe(d1)\n", + "\n", + "// propagating updates through the network\n", + "s1.changeValue(2)\n", + "s2.changeValue(3)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Scala", + "language": "scala", + "name": "scala" + }, + "language_info": { + "codemirror_mode": "text/x-scala", + "file_extension": ".sc", + "mimetype": "text/x-scala", + "name": "scala", + "nbconvert_exporter": "script", + "version": "2.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/service-oriented-design/service-oriented-design.md b/notebooks/service-oriented-design/service-oriented-design.md new file mode 100644 index 0000000..cfa8527 --- /dev/null +++ b/notebooks/service-oriented-design/service-oriented-design.md @@ -0,0 +1,257 @@ +# Notes on the "Service-oriented design" OOP domain modelling example + +This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation. + +The example is in turn based on the paper by Martin Odersky and Matthias Zenger, ["Scalable Component Abstractions"](https://doi.org/10.1145/1094811.1094815). + +> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers) + +## The `SubjectObserver` trait + + +```scala +trait SubjectObserver: + type S <: Subject + type O <: Observer + + trait Subject: + self: S => + private var observers: List[O] = List() + def subscribe(obs: O): Unit = + observers = obs :: observers + def publish() = + for obs <- observers do obs.notify(this) + + trait Observer: + def notify(sub: S): Unit +``` + + + + + defined trait SubjectObserver + + + +### Top-level definitions + +In the `SubjectObserver` trait's top level, four members are defined: + +Two abstract types: +- `S` (implying subject) +- `O` (implying observer) + +Two other traits: +- `Subject` +- `Observer` + +### Type definitions + +The syntax `type S <: Subject`, called an "**upper bound**", is a declaration for an abstract type `S` which must be a subtype of `Subject`. + +Meaning that: +> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`. + +Similarly, a type `O` is also defined, which must be a subtype of `Observer`. + +### Trait definitions + +Each minor trait has the following members: + +```scala +trait Subject: + private var observers: List[O] = List() + def subscribe(obs: O): Unit = // ... + def publish() = // ... +trait Observer: + def notify(sub: S): Unit +``` + +All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract. + +#### Subject + +The `Subject` trait defines three concrete members: +- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()` +- Two public, concrete methods whose behavior depend on this field: + - `subscribe(obs: O): Unit` + - `publish(): Unit` + +These have concrete bodies defined as such: + +```scala +def subscribe(obs: O): Unit = observers = obs :: observers +def publish() = for obs <- observers do obs.notify(this) +``` + +The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field. + +The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument. + +Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation: + +> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`. + +#### Observer + +The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`. + +My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something. + +## A `SubjectObserver` implementation + + +```scala +object SensorReader extends SubjectObserver: + type S = Sensor + type O = Display + + class Sensor(val label: String) extends Subject: + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = + currentValue = v + publish() + + class Display extends Observer: + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") +``` + + + + + defined object SensorReader + + + +Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait. + +To satisfy its contract, it defines: + +```scala +type S = Sensor +type O = Display +``` + +- A type `S` — referring to the trait's "subject" concept — assigned the class `Sensor`, this class defined in the body of the object +- A type `O` — referring to the trait's "observer" concept — which is assigned to the class `Display`, also defined in the body of the object + +It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types: + +```scala +class Sensor(val label: String) extends Subject: + // .... + +class Display extends Observer: + def notify(sub: Sensor) = + // ... +``` + +- The class `Sensor` takes a string `label` value for its constructor and extends `Subject` +- The class `Display` takes no arguments for its constructor and extends `Observer` + +The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods. + +However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0: + +```scala +class Sensor(val label: String) extends Subject: + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = + currentValue = v + publish() +``` + +This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers. + +The `Display` class must yet implement a `notify(sub: S)` method that handles a notification event. + +It does so by printing to STDOUT the interpolated string `"${sub.label} has value ${sub.value}"`: + +```scala +class Display extends Observer: + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") +``` + +The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait. + +The documentation emphasizes how this design demonstrates an object-oriented paradigm: + +> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming: + +> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`. +> - The implementation of `changeValue` uses the method `publish` defined in the extended trait. +> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`. + +It also makes the following observation: + +> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`. + +It is alluding to the contract established in the nested `Observer` trait: + +```scala +trait SubjectObserver: + type S <: Subject + // ... + + trait Subject: + self: S => + // ... + +trait Observer: + def notify(sub: S): Unit +``` + +Considering that `S` had to be a subtype of `Subject`, as per its `type S <: Subject` definition, and given the self-type annotation `self: S =>` in the body of the `Subject` trait established that the subtypes of `Subject` (in this case, `Sensor`) have to also be subtypes of `S`, then: + +```scala +Sensor <: Subject && Sensor <: S +``` + +Because of this double constraint, `Sensor` can pass itself as a subtype of `Subject` instead of as an instance of `Subject` when `publish(this)` is called. + +The implication of safety being, possibly, that [through subtyping](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#subtyping), the instance of `Sensor` can be used where an extended implementation of `Subject` is expected, but insofar as it is also the type assigned to `S`. + +## Invoking the `SensorReader` logic + +In the example below, the output is two times an identical message printed for the same change in the `currentValue` of `sensor1`, since it has two different observers subscribed (`d1` and `d2`), and a single message printed for the change to the value of `s2` from its one observer `d1`. + + +```scala +import SensorReader.* + +// setting up a network +val s1 = Sensor("sensor1") +val s2 = Sensor("sensor2") +val d1 = SensorReader.Display() +val d2 = SensorReader.Display() +s1.subscribe(d1) +s1.subscribe(d2) +s2.subscribe(d1) + +// propagating updates through the network +s1.changeValue(2) +s2.changeValue(3) +``` + + sensor1 has value 2.0 + sensor1 has value 2.0 + sensor2 has value 3.0 + + + + + + import SensorReader.* + + // setting up a network +  + s1: Sensor = ammonite.$sess.cell2$Helper$SensorReader$Sensor@2a8e138f + s2: Sensor = ammonite.$sess.cell2$Helper$SensorReader$Sensor@10fc52e9 + d1: Display = ammonite.$sess.cell2$Helper$SensorReader$Display@38234ace + d2: Display = ammonite.$sess.cell2$Helper$SensorReader$Display@182f632a + + diff --git a/notebooks/service-oriented-design/service-oriented-design.pdf b/notebooks/service-oriented-design/service-oriented-design.pdf new file mode 100644 index 0000000..f0f5531 Binary files /dev/null and b/notebooks/service-oriented-design/service-oriented-design.pdf differ