Files
PIC-Simu/paul.qmd
2025-05-31 14:46:46 +02:00

317 lines
14 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
lang: de
format:
pdf:
toc: true
number-sections: true
colorlinks: true
crossref:
custom:
- kind: float
reference-prefix: UML-Diagramm
key: uml
latex-env: uml
latex-list-of-description: UML-Diagramme
---
{{< pagebreak >}}
# Entwurfsmuster
Zusätzlich zu den beiden im Folgenden dargestellten Entwursmustern benutzen wir einen [Service Locator](https://en.wikipedia.org/wiki/Service_locator_pattern), der zu den [Architectural Patterns](https://en.wikipedia.org/wiki/Architectural_pattern) zählt. Dieses Pattern nutzt ein `HashMap`, in der verschiedene Komponenten des Programms gespeichert werden, um das Komponenten-Management zu vereinfachen und explizite Abhängigkeiten der Komponenten untereinander zu vermeiden, die das Initialisieren der einzelnen Komponenten erschweren könnten.
In unserem Projekt fungiert [`PICComponentLocator`](https://git.paulmartin.cloud/paul/PIC-Simu/src/commit/c38835fd7b47c662a344c9ab2c41e0527760bd61/src/main/java/fabrik/simulator/pic16f84/PICComponentLocator.java) als dieser Locator. Er besitzt einen `componentCatalogue` als Member, der eine `Map<Class<? extends PICComponentInterface>, PICComponentInterface>` ist. Das bedeutet, dass alle Klassen, die durch den Locator gemanaget werden sollen, das `PICComponentInterface` implementieren müssen.
Um nicht bei allen Zugriffen die `PICComponentLocator.getComponent()` ausführen zu müssen, wurde zusätzlich die `abstract` Klasse `PICComponent` [eingeführt]. Sie besitzt als Member alle implementierten Komponenten, also sollten neue Komponenten ebenfalls das Interface implementieren und als Member in `PICComponent` angelegt werden. Alle Komponenten können diese Klasse `extend`en und dadurch auch alle weiteren Komponenten als Member haben, dies ist auch in @uml-observer dargestellt [^1]. Durch die `initialize`-Funktion des `PICComponent` werden durch einen Aufruf der `PICComponentLocator.initAll()` alle Member vom Locator geholt.
## Marker-Entwurfsmuster
[Marker-Patterns](https://en.wikipedia.org/wiki/Marker_interface_pattern) werden allgemein genutzt, um Klassen Metadaten zuzuordnen. In unserem Projekt stellen die `FrontendSpecific`-Interfaces Marker dar, die genutzt werden um zu kommunizieren, dass Klassen andere Klassen benötigen, die Frontend-spezifisch sind und somit besonders beachtet werden müssen. So können alle [`Interface`s](LINK ZU ORDNER) definiert werden ohne direkt von fremdem Code abhängig zu sein. Bei möglichen anderen Frontend-Implementierungen müssten entsprechend nur die passenden Klassen die jeweiligen Interfaces implementieren und nichts an den Interfaces ändern.
Eingeführt wurden die `FrontendSpecific`s in [diesem Commit](https://git.paulmartin.cloud/paul/PIC-Simu/commit/06e934801645e32dea5415ccb4f38368a1667df6) ([hier](https://git.paulmartin.cloud/paul/PIC-Simu/commit/ef3b0fce5f9b6cce06494ff6ce59f5534064e7d2) verfollständigt). Es wurde zunächst ein `FrontendSpecificObject`-Interface angelegt, das alle Frontend-spezifischen Klassen beschreibt, die von Methoden anderer Klassen genutzt werden (sprich, die in den [`Interface`s](https://git.paulmartin.cloud/paul/PIC-Simu/src/commit/f28603843d7ef6cbf4666ab2b2ceda02ca411eb7/src/main/java/fabrik/simulator/pic16f84/interfaces) vorkommen). Es ist - entsprechend des Marker-Patterns - komplett leer [definiert](https://git.paulmartin.cloud/paul/PIC-Simu/src/commit/f28603843d7ef6cbf4666ab2b2ceda02ca411eb7/src/main/java/fabrik/simulator/pic16f84/frontendspecifics/FrontendSpecificObject.java):
```java
public interface FrontendSpecificObject {
}
```
Zusätzlich gibt es für spezifische Frontend-Klassen auch `FrontendSpecific`-Interfaces, sodass nach wie vor nur bestimmte Klassen über- bzw. zurückgegeben werden können. Diese spezifischen Interfaces sind ebenfalls leer, nur nutzen sie `extends FrontendSpecificObject` um zu verdeutlichen, dass sie zu den allgemeinen `FrontendSpecificObject`s gehören. Hier beispielsweise [`FrontendSpecificCircle`](https://git.paulmartin.cloud/paul/PIC-Simu/src/commit/ef3b0fce5f9b6cce06494ff6ce59f5534064e7d2/src/main/java/fabrik/simulator/pic16f84/frontendspecifics/FrontendSpecificCircle.java):
```java
public interface FrontendSpecificCircle extends FrontendSpecificObject {
}
```
Tatsächlich genutzt werden die `FrontendSpecific`-Interfaces von [Circle], [ToggleButtonGroup] und [Vbox]. Sie `extenden` ihr jeweiliges `JavaFX`-Pendant und `implementen` ihr jeweiliges `FrontendSpecific`-Interface. Darüber hinaus implementieren sie nur die nötigen (also im Code tatsächlich genutzten) Konstruktoren, die wiederum nur `super()` aufrufen, hier bspw. [`ToggleButtonGroup`]:
```java
public class ToggleButtonGroup extends
com.gluonhq.charm.glisten.control.ToggleButtonGroup
implements FrontendSpecificToggleButtonGroup {
public ToggleButtonGroup(){
super();
}
public ToggleButtonGroup(ToggleButton... toggles) {
super(toggles);
}
}
```
Alle für das Marker-Pattern eingeführten Klassen sind in @uml-marker erkennbar [^1]:
::: {#uml-marker}
```{mermaid}
%%| fig-width: 6.5
classDiagram
direction TB
class FrontendSpecificObject {
<<Interface>>
}
class FrontendSpecificToggleButtonGroup {
<<Interface>>
}
class ToggleButtonGroup {
public ToggleButtonGroup()
public ToggleButtonGroup(ToggleButton... toggles)
}
class `com.gluonhq.charm.glisten.control.ToggleButtonGroup` {
...
....()
}
FrontendSpecificObject <|-- FrontendSpecificToggleButtonGroup : << extends >>
FrontendSpecificToggleButtonGroup <|-- ToggleButtonGroup : << implements >>
`com.gluonhq.charm.glisten.control.ToggleButtonGroup` <|-- ToggleButtonGroup : << extends >>
class FrontendSpecificVBox{
<<Interface>>
}
class VBox {
public VBox()
}
class `javafx.scene.layout.VBox` {
...
....()
}
FrontendSpecificObject <|-- FrontendSpecificVBox : << extends >>
FrontendSpecificVBox <|-- VBox : << implements >>
`javafx.scene.layout.VBox` <|-- VBox : << extends >>
class FrontendSpecificCircle {
<<Interface>>
}
class Circle {
public Circle()
}
class `javafx.scene.shape.Circle` {
...
....()
}
FrontendSpecificObject <|-- FrontendSpecificCircle : << extends >>
FrontendSpecificCircle <|-- Circle : << implements >>
`javafx.scene.shape.Circle` <|-- Circle : << extends >>
class PICComponentInterface{
<<Interface>>
+ initialize(PICComponentLocator picComponents)
}
class WindowManagement{
<<Interface>>
refreshTable()$
startFromMain(String[] args)$
}
FrontendSpecificObject <|-- WindowManagement : << extends >>
PICComponentInterface <|-- WindowManagement : << extends >>
```
Das genutzte Marker-Pattern und alle seine Verwendungen.
:::
## Beobachter- (/Observer-) Entwurfsmuster
[Beobachter-Entwurfsmuster](https://en.wikipedia.org/wiki/Observer_pattern) werden genutzt, damit ein Subjekt mehrere Beobachter über eine Zustandsänderung informieren kann. In unserem Projekt passiert das bei einer Änderung der `totalExecutionTime`. Das Subjekt `ExecutionTimeSubject` führt hierbei ein `Set` an Beobachtern, die bei uns durch das Interface `ExecutionTimeObserver` repräsentiert werden, welches durch die `registerObserver`- und `unregisterObserver`-Funktionen verwaltet werden kann. Bei einer Zustandsänderung muss die `notifyObservers`-Funktion aufgerufen werden, welche für alle Observer die im Interface spezifizierte `executionTimeChanged`-Funktion aufruft. Die Implementierung wurde [hier](https://git.paulmartin.cloud/paul/PIC-Simu/commit/85bc6e9ebae4655ba3ad7ee360332010edc910dd) begonnen, [hier](https://git.paulmartin.cloud/paul/PIC-Simu/commit/cf6bcd8498cd2d03e85b0c5f6faaaed935d3a155) vereinfacht um das Threading des Frontends zu respektieren und [hier](https://git.paulmartin.cloud/paul/PIC-Simu/commit/52d63523341179c3c49e0ac31a60a8d7c11cdddc) in die letzten Tests eingefügt.
In der aktuellen Umsetzung übernimmt die [`Commands`](https://git.paulmartin.cloud/paul/PIC-Simu/src/commit/ef3b0fce5f9b6cce06494ff6ce59f5534064e7d2/src/main/java/fabrik/simulator/pic16f84/Commands.java)-Klasse gleichzeitig die Rolle des `ExecutionTimeSubject`s und `CommandsInterface`s. Deshalb [wird in der `Main`-Klasse](https://git.paulmartin.cloud/paul/PIC-Simu/src/commit/85bc6e9ebae4655ba3ad7ee360332010edc910dd/src/main/java/fabrik/simulator/pic16f84/Main.java) das `Commands`-Objekt dem `ComponentLocator` sowohl für die `ExecutionTimeSubject.class` als auch für die `CommandInterface.class` hinzugefügt. `ExecutionTimeObserver` sind die Klassen, die vorher `commands.getTotalExecutionTime()` gepollt haben. Die Implementierung dieses Entwursmusters ist in @uml-observer erkennbar [^1]:
:::{#uml-observer}
```{mermaid}
%%| fig-width: 6.5
classDiagram
direction TB
class PICComponentInterface{
<<Interface>>
+ initialize(PICComponentLocator picComponents)
}
class CommandInterface {
<<Interface>>
+ CALL(int isr);
+ get_wRegister();
+ decode(int i);
}
class PICComponent{
<<Abstract>>
# DataRegisterInterface dataRegister
...
+ initialize(PICComponentLocator locator)
}
class ExecutionTimeSubject{
<<Abstract>>
- Set~ExecutionTimeObserver~ observers
+ registerObserver(ExecutionTimeObserver observer)
+ unregisterObserver(ExecutionTimeObserver observer)
+ getTotalExecutionTime()
+ addExecutionTime(int i)
+ getExecutionTimeMultiplier()
# notifyObservers()
....()
}
class Commands{
- wRegister
- totalExecutionTime
- executionTimeMultiplier
....()
}
class FrontendSpecificObject {
<<Interface>>
}
class FrontendControllerInterface{
<<Interface>>
+ getPORTbuttons() FrontendSpecificToggleButtonGroup[]
+ getTRISbuttons() FrontendSpecificToggleButtonGroup[]
+ stopRunFromBackend(String watchdogTimer)
}
class Controller_Frontend {
...
....()
}
class TimerInterface {
<<Interface>>
+ cycles(int i)
+ incrementFromPin(int directRegister)
+ increment(boolean manual)
}
class Timer {
...
....()
}
class EEPROMInterface {
<<Interface>>
registerTime(double executionTime, boolean b)
parse(int i, int content, int i1)
read(int address) long
write(int address, long data)
}
class EEPROM {
...
....()
}
class ExecutionTimeObserver {
<<Interface>>
+ executionTimeChanged()
}
ExecutionTimeObserver <|-- Timer : << implements >>
TimerInterface <|-- Timer : << implements >>
PICComponent <|-- Timer : << extends >>
PICComponentInterface <|-- TimerInterface : << extends >>
PICComponentInterface <|-- EEPROMInterface : << extends >>
ExecutionTimeObserver <|-- EEPROM : << implements >>
EEPROMInterface <|-- EEPROM : << implements >>
PICComponent <|-- EEPROM : << extends >>
ExecutionTimeObserver <|-- Controller_Frontend : << implements >>
PICComponent <|-- Controller_Frontend : << extends >>
PICComponentInterface <|-- PICComponent : << implements >>
PICComponentInterface <|-- FrontendControllerInterface : << extends >>
FrontendControllerInterface <|-- Controller_Frontend : << implements >>
FrontendSpecificObject <|-- FrontendControllerInterface : << extends >>
CommandInterface <|-- Commands : << implements >>
ExecutionTimeSubject <|-- Commands : << extends >>
PICComponent <|-- ExecutionTimeSubject : << extends >>
PICComponentInterface <|-- CommandInterface : << extends >>
```
Das genutzte Observer-Pattern und seine Verwendungen im (Nicht-Test-) Code.
:::
[^1]: Für das Pattern unwichtige Funktionen und Variablen wurden ausgelassen
# Clean Architecture
Die Clean Architecture ist darauf ausgelegt, Softwareprojekte langfristig betreibbar, flexibel und wartbar zu halten. Dazu wird das Projekt in **konzentrische Schichten** unterteilt, in denen die **Abhängigkeitsrichtung stets von außen nach innen** verläuft die sogenannte *Dependency Rule*. Der Kern der Anwendung bleibt dabei vollständig unabhängig von technischen Details wie Benutzeroberflächen, Datenbanken oder Netzwerken.
In unserem Projekt haben wir diese Schichtarchitektur wie folgt umgesetzt:
## 1. Interface/Adapter-Schicht
Diese Schicht enthält alle Klassen, die als Schnittstelle zwischen der Anwendung und der Benutzeroberfläche dienen.
Dazu zählen alle Klassen im Package, `fabrik.simulator.pic16f84.frontendspecifics` insbesondere:
- `Controller_Frontend`
- `CreateWindow`
- `IOPorts`
- `ToggleButtonGroupExt`
Diese Klassen erben vom `FrontendSpecificObject` und sind spezifisch für die grafische Oberfläche. Sie implementieren die Schnittstellen der inneren Schichten und leiten Benutzerinteraktionen weiter.
## 2. Application Code
Die nächstinnere Schicht beinhaltet die anwendungsspezifische Logik unsere *Use Cases*. Dazu zählen sämtliche Klassen im Package `fabrik.simulator.pic16f84`, **sofern sie nicht vom `FrontendSpecificObject` erben**. Diese Schicht ist weitgehend unabhängig von der GUI und bleibt stabil, selbst wenn sich die Darstellung oder Eingabeform ändert.
Diese Klassen gehören zur Anwendungslogik:
- `Timer`
- `PreScaler`
- `WatchdogTimer`
- `ProgrammStack`
## Main-Klasse als Plugin
Die `Main`-Klasse bildet den äußeren Rahmen (Plugin-Schicht) und initialisiert die gesamte Anwendung:
- Es werden alle Objekte erzeugt und dem `Locator` zugewiesen.
- Das `WindowManagement` startet das Frontend.
Ein besonders interessanter Aspekt: Wenn man sämtliche Referenzen auf das Frontend (z.B. in Zeile 19, 20, 23) entfernt, lässt sich die App **trotzdem erfolgreich starten und nutzen**. Das zeigt, dass die Schichten entkoppelt sind ein zentrales Ziel der Clean Architecture.
\newpage
## Visualisierung
```{mermaid}
flowchart TB
subgraph Interface/Adapter-Schicht
A1[Controller_Frontend, CreateWindow, IOPorts, ToggleButtonGroupExt]
end
subgraph Application Code
A2[Use Cases, WatchdogTimer, PreScaler, ProgrammStack, Timer]
end
subgraph Plugin-Schicht
A3[Main.java]
end
A3 --> A1
A1 --> A2
```