Skip to content

Software Design Pattern

Maximilian Burr edited this page May 31, 2023 · 15 revisions

Software Design Pattern

In der Anwendung werden mehrere Design Pattern umgesetzt.

Factory Pattern

%%{init: {'theme':'forest'}}%%
classDiagram

PluginFactory --> IPlugin
PluginFactory ..|> IPluginFactory : implements
Core --> "0..n" IPlugin

Loader --> "1" IPluginFactory
Loader --> "1" Core

Core --> "1" IPluginFactory





class Loader{
    +loadPlugins()
    +init()
}

class IPluginFactory{
    <<interface>>
    +createPlugin(String className)
}

class Core{
    + List<IPlugin> loadedPlugins
    + IPluginFactory pluginFactory
    + ...
    + Core(plugins, pluginFactory, configuration, initialState, publisher, receiver)
    + ...()
    }


class IPlugin{
    <<interface>>
    +load()
    +unload()
}
Loading

Im Kern Programmteil wurde eine Factory in der Klasse PluginFactory umgesetzt. Die Loader Klasse verwendet eine Factory Klasse, um die IPlugin Objekte zu erstellen und an den Kern zu übergeben. Die Verwendung der Factory ermöglicht es, zur Laufzeit Plugins zu laden und zu erstellen. Damit müssen die neuen Plugins nicht im Kern instanziiert werden, sondern die Aufgabe kann durch die Factory durchgeführt werden. Dazu wird über das IPluginFactory eine Instanz der PluginFactory an die Core Klasse übergeben.

Im aktuellen Zustand gibt es nur eine Art von Plugin, später sollen noch mit einer Vererbungsstruktur feinere Unterteilungen von Plugin Arten durchgeführt werden; beispielsweise in Plugins für Frontends, SmartHome Systemen oder Funktionalitätserweiterungen des Kerns. Dazu würden dann weitere "SubTypePluginFactories" angelegt werden, die ebenso das IPluginFactory implementieren.

Im aktuellen Zustand kann die Loader Klasse, die die Klassendateien der Plugins sucht, nur Plugin.class Dateien laden, die im Java Classpath an der richtigen Stelle liegen. Um die Erweiterung durch unterschiedliche Plugins zu ermöglichen, müsste diese Klasse ebenso umgebaut werden.

PluginFactory:

package pluginmanager;


import java.lang.reflect.InvocationTargetException;

public class PluginFactory implements IPluginFactory {
    public IPlugin createPlugin(String className) throws IllegalAccessException,

     InstantiationException, NoSuchMethodException,
     
      InvocationTargetException, ClassNotFoundException {
        Class<?> clazz = Class.forName(className);
        if (IPlugin.class.isAssignableFrom(clazz)) {
            return (IPlugin) clazz.getDeclaredConstructor().newInstance();
        }
        return null;
    }
}

Dekorierer Pattern

%%{init: {'theme':'forest'}}%%
classDiagram

QueueingBlockUpdater --> "1" BlockUpdater
QueueingBlockUpdater ..|> BlockUpdater : implements
SadBlockUpdater ..|> BlockUpdater : implements
ChunkedBlockUpdater ..|> BlockUpdater : implements
ChunkedBlockUpdater --> "1" BlockUpdater

class BlockUpdater{
    <<interface>>
    +doUpdate(BlockUpdate update)
}

class SadBlockUpdater{
    +SadBlockUpdater()
}

class ChunkedBlockUpdater{
    -BlockUpdater updater
    +ChunckedBlockUpdater(BlockUpdater updater)
    +onChunkload()
}

class QueueingBlockUpdater{
    -BlockUpdater updater
    +QueueingBlockUpdater(BlockUpdater updater)
    +onRedstoneTick()
}
Loading

Im Minecraft Smarthome werden Zustände von Minecraft-Blöcken geändert, um Outputs zu visualisieren. Daher gibt es im Interface BlockUpdater eine Methode namens doUpdate, die eine BlockUpdate-Instanz mit Informationen wie dem betreffenden Block oder dem neuen Zustand erwartet. Eine Implementierung dieses Interfaces namens SadBlockUpdater implementiert diese Methode und führt beim Aufruf das Update durch. Da Minecraft aber Minecraft ist und Minecraft-Dinge macht, reicht das so nicht aus.

Ein Block-Update wird in der Regel über die Smarthome Bridge ausgelöst. Der entsprechende Aufruf kommt daher aus dem Teil des Minecraft Smarthomes, der für die Netzwerkkommunikation zuständig ist. Es ist sehr wahrscheinlich, dass diese Aufrufe in einem separaten Thread stattfinden und damit nicht synchron zu den sogenannten Ticks des Minecraft Servers sind.

Daher gibt es eine weitere Implementierung von BlockUpdater namens SadQueueingBlockUpdater. Ein Aufruf der doUpdate Methode fügt das Update darin nur einer Warteschlange hinzu. Bei jedem Minecraft Tick wird eine weitere Methode namens doPendingUpdates aufgerufen, die die gesammelten Updates ausführt und die Warteschlange leert. Zum Ausführen der Updates erwartet der Konstruktor der Klasse mittels Dependency Injection eine weitere Instanz eines BlockUpdaters, die als Attribut gespeichert und beim Abarbeiten der Schlange entsprechend aufgerufen wird.

Neben fehlender Synchronisation muss in Minecraft die Aufteilung der Welt in Chunks (Bereiche) bedacht werden. Nur Chunks im Umkreis von angemeldeten Spielern oder dem Einstiegspunkt der Welt sind geladen. Und nur in geladenen Chunks kann auf Blöcke zugegriffen werden.

Die BlockUpdater-Implementierung namens SadChunkedBlockUpdater prüft dazu beim Aufruf von doUpdate zuerst (mittels eines Objekts, das das kann und per Same Day Delivery Dependency Injection erhalten wurde), ob der betreffende Chunk geladen ist. Ist er das, wird der "Über-Kopf-Pfeil" des Dekorators wieder verwendet und das Update wird ausgeführt. Ist der Chunk nicht geladen, wird es in eine Warteschlange gespeichert, die auf chunkweisen Zugriff ausgelegt wurde. Sobald ein Chunk geladen wird (das wird an anderer Stelle durch einen Observer festgestellt), wird die doChunkUpdates Methode mit dem betreffenden Chunk als Parameter aufgerufen und die, für dieses Chunk in der Warteschlange gespeicherten, Updates werden ausgeführt.

Der Einsatz des Dekorierer Patterns an dieser Stelle macht die "Workarounds" um diese Eigenheiten von Minecraft sowohl für die aufrufende Seite als auch die aufgerufene Seite (SadBlockUpdater) transparent. So kann dort der Fokus auf den eigentlichen Aufgaben, nämlich dem Auslösen von Updates beziehungsweise dem Durchführen der Updates an konkreten Blöcken liegen.

Clone this wiki locally