-
Notifications
You must be signed in to change notification settings - Fork 0
Programming Principles
Nach dem Single Responsibility Principle soll eine Klasse nicht zu viele Verantwortlichkeiten aufweisen.
In unserem Programmentwurf wurde das Single Responsibility Principle in Commit Configuration Commit in der Klasse Configuration umgesetzt.
Jede Klasse der Configuration Architektur hat eine oder höchstens 2 Funktionen.
Das Klassenkonstrukt um Configuration ist in mehrere Subklassen aufgebaut, die alle einzelne Aufgaben übernehmen.
Die Klasse Configuration hat die Aufgabe die Konfiguration bereitzustellen, sie implementiert die Schnittstelle zu anderen Programmteilen an über die Plugin Schnittstelle des Kerns. Dies macht sie indem sie das Interface "IConfiguration" implementiert.
Die Aufgabenteilung durch das Single Responsibility Prinzip wird dadurch erreicht, dass Funktionalitäten wie das Speichern der Configuration auf dem Dateisystem und das Laden der jeweiligen Konfiguration in einzelne Klassen ausgelagert wurden. Ebenso wurde die Datenhaltung der Konfiguration in einem Baum in einer extra Klassenstruktur ausgelagert.
class Configuration{}
classDiagram
Configuration --> "1" ConfigMap
Configuration --> "1" ConfigurationSerializer
Configuration --> "1" ConfigurationDeserializer
Configuration ..> IConfiguration : implements
Configuration ..> IPlugin : implements
ConfigMap --> "0..n" ConfigKey
ConfigMap --|> ConfigTree
ConfigKey --|> ConfigTree
class ConfigTree{
+member
}
class Configuration{
+configPath:File
}
class IConfiguration{
<<interface>>
+getConfiguration()
+setConfigKey(key:String, value:String)
+getConfigKey()
}
class ConfigMap{
+member
}
class ConfigTree{
+member
}
Durch das 'IPlugin' Interface im Kernteil unserer Anwendung wird das Open-Closed Principle umgesetzt. Da das 'IPlugin' Interface die load Methode bereitstellt, die von jedem Kernplugin implementiert wird. Durch die Implementierung der Load Methode in jedem Kernplugin, wird sichergestellt dass jedes Plugin seine eigene Initialisierung durchführen kann, ohne dass bestehender Code der 'Loader' oder 'Core' Klasse verändert wird.
package pluginmanager;
public interface IPlugin {
void load(ICore core);
void unload();
void receiveNotification(String message);
String getName();
}Wird die Load Methode aufgerufen initialisiert sich jedes Kernplugin selbst mit einer angepassten Loadmethode.
In der ConfigurationKlassenhierachie, die bereits in Klasse Configuration beschrieben wurde wird das Liskovo Substituion Principle umgesetzt. Der Commit in der das Liskov Substitution Principle umgesetzt wurde ist der gleiche wie der Commit in dem das Single
Die Klassen ConfigMap und ConfigKey erben von der Klasse ConfigTree. Die einzelnen Klassen haben dabei folgende Funktion.
-
Basisklasse ConfigTree: Die abstrakte Klasse ConfigTree stellt die Basisklasse dar. Sie definiert eine gemeinsame Schnittstelle für verschiedene Arten von Konfigurationsknoten.
-
Abgeleitete Klasse ConfigMap: Die Klasse ConfigMap erweitert die Basisklasse ConfigTree. Sie fügt eine spezifische Implementierung hinzu, um eine Konfigurationsstruktur als Map von Schlüssel-Wert-Paaren darzustellen. Sie stellt die Methode getConfigSubmap bereit, um eine Teilmenge der Konfigurationsmap als neue ConfigMap abzurufen, und die Methode getConfigKey, um einen spezifischen Konfigurationsschlüssel als ConfigKey abzurufen.
-
Abgeleitete Klasse ConfigKey: Die Klasse ConfigKey erweitert ebenfalls die Basisklasse ConfigTree. Sie repräsentiert einen einzelnen Konfigurationsschlüssel mit einem Wert.
Zusammenfassend lässt sich sagen, dass das gezeigte Codebeispiel das Liskov-Substitutionsprinzip einhält, da die abgeleiteten Klassen die Verträge der Basisklasse einhalten und deren Funktionen erweitern, ohne das erwartete Verhalten zu ändern.
Die im Abschnitt "[Dekorierer Pattern](#Dekorierer Pattern)" beschriebenen Klassen SadQueueingBlockUpdater und SadChunkedBlockUpdater implementieren beide das BlockUpdater Interface. Beide besitzen jedoch noch eine weitere Schnittstelle, da auf Minecraft-Ticks beziehungsweise Chunk Load Events reagiert werden muss. Wären die entsprechenden Methoden im BlockUpdater Interface deklariert, würde das Dekorierer Pattern so nicht mehr funktionieren, da jede Implementierungen auf Ticks und Chunk Loads reagieren müsste. Stattdessen sind diese Methoden daher in eigenen Interfaces namens RedstoneTickListener und ChunkLoadListener deklariert. So kann der aufrufende Code (SadMinecraftEventUnpacker) mit dem jeweils benötigten Interface arbeiten und hat keine unnötig starke Kopplung an andere involiverte Mechaniken wie BlockUpdates.
Das Dependency Inversion Principle wird im Kernteil des Porgammes durch die Verwendung von Interfaces durchgesetzt.
Desweiteren wird das Inversion of Control (IoC) Muster verwendet.
Anstatt Abhängigkeiten zu Implementierungen einer Klasse zu haben, hat die Klasse Core nur Abhängigkeiten zu abstrakten Schnittstellen um das Dependency Inversion Principle umzusetzen.
Die Klasse Core hat eine Abhängigkeit von der abstrakten Schnittstelle IConfiguration. Statt direkt eine konkrete Implementierung von IConfiguration zu instanziieren, wird die konkrete Implementierung über den Konstruktor als Abhängigkeit übergeben. Dadurch hängt Core nicht von einer spezifischen Implementierung ab, sondern von der abstrakten Schnittstelle. Die Klasse Core hat auch eine Abhängigkeit von der abstrakten Schnittstelle IPluginFactory. Auch hier wird die konkrete Implementierung über den Konstruktor als Abhängigkeit übergeben, anstatt direkt eine spezifische Implementierung zu instanziieren. Die Klasse Core hat ebenfalls Abhängigkeiten von den abstrakten Schnittstellen IPublisher und IReceiver. Auch hier werden die konkreten Implementierungen über den Konstruktor als Abhängigkeiten übergeben.
classDiagram
class Core {
+ IConfiguration configuration
+ List<IPlugin> loadedPlugins
+ IPublisher publisher
+ IReceiver receiver
+ IPluginFactory pluginFactory
+ ApplicationState appState
--
+ Core(plugins, pluginFactory, configuration, initialState, publisher, receiver)
+ getConfiguration()
+ run()
+ getCore()
+ setState(state)
+ getState()
+ getPublisher()
}
interface IConfiguration
interface IPlugin
interface IPluginFactory
interface IPublisher
interface IReceiver
Core --|> IConfiguration
Core --|> IPlugin
Core --|> IPluginFactory
Core --|> IPublisher
Core --|> IReceiver
Die Klasse Core nimmt die abhängigen Objekte
pluginspluginFactoryconfigurationpublisherreceiver
im Konstruktor entgegen. Dadurch wird die Kontrolle über die Erstellung und Bereitstellung dieser Objekte an den aufrufenden Code (den sogenannten "Inversion of Control Container" oder "IoC Container") übergeben.
Durch die Verwendung eines IoC Containers wird die Erstellung der abhängigen Objekte zentralisiert und die Abhängigkeiten zwischen den Komponenten des Systems werden entkoppelt. Dadurch wird die Flexibilität, Erweiterbarkeit und Austauschbarkeit des Systems verbessert.
Der Inversion of Control Container ist im Falle des Kernteils des Smarthome Bridge Projektes die Loader Klasse. Sie stellt die Objekte bereit, die in der Core Klasse verwendet werden.
Im aktuellen Zustand des Projektes werden Basisfunktionalitäten wie Nachrichtenaustausch und Configuration direkt per Objekt übergeben und von der Loader Klasse instanziiert. In den nächsten Schritten sollen nur noch die Plugins übergeben werden, die von der Loader Klasse initialisiert werden. Es werden jedoch nur Interfaces übergeben um eine lose Kopplung zu gewährleisten und nicht von konkreten Implementierung beispielsweise der PluginFactory abhängig zu sein.