Skip to content

Commit

Permalink
Add Service-oriented design notebook
Browse files Browse the repository at this point in the history
  • Loading branch information
jultty committed Nov 26, 2023
1 parent 027dfa7 commit b56356c
Show file tree
Hide file tree
Showing 8 changed files with 9,492 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
375 changes: 375 additions & 0 deletions notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 9 additions & 0 deletions notebooks/README.md
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit b56356c

Please sign in to comment.