Dynamische Methodik und nicht-spezifische Rückgabe werte von Methoden

Haltet ihr so etwas für Sinnvoll?


  • Anzahl der Umfrageteilnehmer
    2

StormCraftable

Grünschnabel
Halli Hallo!
Meine Frage ist sehr speziell und ich weiß nicht, ob ich hier richtig bin, aber ich beschreibe mal, was ich versuche zu erreichen.
Ich möchte etwas ähnliches wie Spring umsetzen, das schon mal vor weg. Nur soll dieses wesentlich mehr an Dependencie Injection für "Modulare Programmierung" orientiert sein. Dazu folgende Idee:
Ich möchte eine "DataOutputPipe" haben, die eine statische, innere "HashMap" beinhaltet, in welcher sich (Key : Value) [String (Der Name) : Das_Object] befindet. Des weiteren soll man Objekte hinzufügen, sowie bekommen können, also 2 Methoden die im Prinzip "addData(Das_Object object)" und "getData(String key)" heißen.
Mein Problem liegt dabei in der Methode getData(). Welche Objekte man der "DataOutputPipe" gibt soll nicht vor-definiert sein. Das heißt, füge ich bei einem mal die Klasse A der DataOutputPipe hinzu, soll man diese bekommen und nutzen können, fügt man beim nächsten mal die Klasse B der DataOutputPipe hinzu, soll man auch dieses bekommen und nutzen können. Diesen dynamischen Rückgabe wert konnte ich bis heute nicht erfolgreich umsetzen. Das mag daran liegen, dass ich Jahrelang in php entwickelt habe und jetzt ein bisschen über die Typsicherheit von Java stolpere.. auch wenn das eigentlich besser ist.
Vorteile davon wären:
- Objekte müssen nicht instantiiert werden. Man bekommt von der DataOutputPipe (ähnlich wie bei einem Factory-Patern) fertige Objekte.
- Man bekommt (wenn man will) immer die gleichen Objekte, wie bei einem Singlton-Patern.
- Unabhängig von der Instantiierung der DataOutputPipe kann jedes beliebige Objekt die gleiche Instanz eines beliebigen Objektes bekommen.

Das Problem, auf welches ich bisher gestoßen bin, ist, dass ich sehr eingeschränkt bin, wenn ich abstract classes, bzw interfaces für die einzelnen Daten nutze. Das würde heißen, dass jede Klasse in der DataOutputPipe nur eine bestimmte Menge an Methoden haben könnte. Das soll so nicht sein.

Meine Motivation ist, dass ich diese DataOutputPipe in ein, vor längerer Zeit von mir geschriebenem RegisterHandler-Framework einpflegen möchte. Dieses RegisterHandler-Framework sorgt für Kommunikation zwischen Objekten, ohne Objekte und / oder primitive Datentypen übergeben muss.

Ferner wäre es unglaublich cool, wenn man der DataOutputPipe sagen werden soll, "füge automatisch alle Klassen mit der custom Annotation @bla hinzu", nur dazu kenne ich mich noch nicht gut genug mit custom Annotations aus (sowas bekommt man im Studium nicht erklärt :D )

Meine konkrete Frage: Ist so etwas möglich? Und wen ja, wie würde man so etwas am besten umsetzen.

Hier etwas Pseudocode, wie ich es mir vorstelle:

Überall wo DataComponent steht, habe ich keine Ahnung, was da hin muss, bzw. wie es anders aufgebaut besser wäre.

Die DataOutputPipe
Java:
package pipe;

public class DataOutputPipe {

    private static DataContainerList<DataComponent> dataComponentContainer; public DataOutputPipe() {
        DataOutputPipe.dataComponentContainer = new DataContainerList<>();
    }

    public DataOutputPipe(DataContainerList<DataComponent> dataComponentContainer) {

    }

    public static DataComponent getData(String name) {
        return DataOutputPipe.dataComponentContainer.get(name);
    }

    public static void addData(DataComponent component) {
        DataOutputPipe.dataComponentContainer.add(component.getDataName(), component);
    }
}

Und hier die DataContainerList, welche später die Daten beinhalten soll.

Java:
package pipe;

import java.util.HashMap;
import java.util.Iterator;
/**
* Eine DataContainerList wird genutzt um DataComponent's zu speichern
* @param <DataComponent>
*/

class DataContainerList<DataComponent> {

    private static HashMap<String, DataComponent> dataComponentList;DataContainerList() {
        createDataComponentList();
    }

    public void add(String assertetName, DataComponent component) {
        dataComponentList.put(assertetName,component);}

        public void add(DataComponent component)
        dataComponentList.put(component.getDataName(),component);
    }

    public DataComponent get(String objectName) {
        return dataComponentList.get(objectName);
    }


    private void createDataComponentList() {
        if(dataComponentList == null) {
            dataComponentList = new HashMap<>();
        }
    }


    private boolean testForDuplicate(Object component) {
        Iterator dataComponentListIterator = dataComponentList.entrySet().iterator();
        while (dataComponentListIterator.hasNext()) {
            if(dataComponentListIterator.next().equals(component)) {
                return true;
            }
        }
        return false;
    }
}

Ich entschuldige mich für den langen Post und hoffe ich habe etwas Struktur beibehalten, Ich bin noch nicht so perdu mit dem Forum! Sollten euch Rechtschreibfehler, oder Formatierungs-Fehler auffallen, tut es mir leid.
Vielen Dank schon mal im Voraus für eure Zeit!
 
Zuletzt bearbeitet:
Hi,

also ich gebe zu wahrscheinlich auf Anhieb nicht alles korrekt verstanden zu haben, aber zu deiner Problematik mit dynamischen Rückgabewerten:

Einfachste Art und Weise wäre, deine Methode getData gibt einfach Object zurück...dann kannst du immer alles zurückgeben, aber du weißt halt entweder nicht, was es wirklich für ein Objekt ist oder musst dann erst noch casten...

Besser: generischer Rückgabewert!

Java:
public <T> T getData(String name, Class<T> type) {
    return type.cast(map.get(name));
}

Natürlich fehlt da noch Error-Handling, was ist, wenns das nicht gibt usw.
Die Elemente werden dann richtig gecastet:

Java:
MyClass obj = dataOutputPipe.getData(name, MyClass.class);

Jetzt ist aber nur das Problem gelöst, dass du nicht mehr casten musst. Wenn du nicht weißt, was für ein Klasse sich hinter dem Namen verbirgt bist du aufgeschmissen. Aber sowas kann man auch lösen:

Java:
public Class<?> getType(String name) {
    return map.get(name).getClass();
}

Verbunden mit der anderen Methode, kriegst du dann deine Objekte immer richtig gecastet und dynamisch raus. Einziger Nachteil: Du musst den Namen 2 mal übergeben, die Methode getType irgendwie in den Hintergrund innerhalb von getData zu packen bringt nichts, da die Methode getData ja ihren dynamischen Returntype kennen muss. Und den musst du immer von außerhalb angeben. Der ganze Aufruf, um ein Objekt zu holen, wäre dann quasi:

Java:
MyClass obj = dataOutputPipe.getData(name, dataOutputPipe.getType(name));


Ich hoffe das ist so ungefähr was du suchst.


Viele Grüße

Daniel
 
Zuletzt bearbeitet:
Super! Vielen Dank! Das klappt schon ganz gut!
Das einzige Problem, das ich momentan noch habe, ist das die Klasseninstanzen in diesem Fall abhängig sind von der DataOutputPipe.
Hier mal das Beispiel:
Die DataOutputPipe:
Java:
package pipe;

import java.util.ArrayList;
public class DataOutputPipe {

    private static ArrayList<String> dataKeyList = new ArrayList<>();
    private static DataContainerList<String, Object> dataComponentList = new DataContainerList<>();
    public void add(String name, Object component) {
        dataComponentList.add(name, component);
        dataKeyList.add(name);
    }

    public <T> T getData(String name, Class<T> type) {
        return type.cast(dataComponentList.get(name));
    }

    public <T> T getData(String name) {
        Object o = dataComponentList.get(name);
        return (T) o.getClass().cast(dataComponentList.get(name));
    }

    public Class<?> getType(String name) {
        return dataComponentList.get(name).getClass();
    }

    public ArrayList<String> getAllKeys() {
        return dataKeyList;
    }

    public boolean keyInDataOutputPipe(String key) {
        return dataKeyList.contains(key);
    }

}

Hier ist die DataContainerList
Java:
package pipe;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

class DataContainerList<String, Object> {

    private Map<String, Object> dataComponentHashMap;

    DataContainerList() {
        dataComponentHashMap = Collections.synchronizedMap(new HashMap<String,Object>());
    }

    public void add(String key, Object data) {
        dataComponentHashMap.put(key, data);
    }

    public Object get(String key) {
        return dataComponentHashMap.get(key);
    }

    public void updateData(String key, Object data) {
        dataComponentHashMap.put(key, data);
    }

    public void remove(String key) {
       dataComponentHashMap.remove(key);
   }
}

Ich habe hier mal ein Beispiel gemacht.
Dafür habe ich 3 Klassen, A, B und C. Alle haben als Atribut einen Namen und als Methoden eine setName()-Methode, die den Namen setzt (duh) und eine whoAmI()-Methode die den Namen ausgibt.

Java:
package main;

import main.test.A;
import pipe.DataOutputPipe;

public class Main {
    public static void main(String[] args) {
        DataOutputPipe dataOutputPipe = new DataOutputPipe();
        dataOutputPipe.add(A.class.getName(),new A());
       
        A a = dataOutputPipe.getData(A.class.getName(), A.class);
        a.whoAmI();
        a.setName("nichtA");
        a.whoAmI();
        A a2 = dataOutputPipe.getData(A.class.getName(), A.class);
        a2.whoAmI();
    }
}

Diese Main-Methode gibt das folgende aus:

Mein Name ist "A"
Mein Name ist "nichtA"
Mein Name ist "nichtA"

Die DataOutputPipe sollte aber immer das gleiche Objekt zurück geben. In php würde ich für eine Klasse eine Datei erstellen und diese einbinden, wenn ich sie bräuchte. Dann würde ich bei den Atributen, die ich ändern wollte mit Session-Variablen arbeiten und den Rest von der Instanz des Objektes abhängig machen, so dass die Ausgabe so aussähe:

Mein Name ist "A"
Mein Name ist "nichtA"
Mein Name ist "A"


Nur wenn ich sage:
Java:
dataContainerList.update(key, a)
soll das nächste Objekt, das ich anfrage eben jenes sein, welches ich hier speichere.
Dafür müsste ich aber Klassen und nicht Objekte speichern, oder bin ich da auf dem falschen Dampfer? Und dann bei
Java:
dataOutputPipe.getData(A.class.getName(), A.class)
müsste es dann heißen
Java:
return new dataContainerList.get(key);
?
Nur dann wäre das Problem, dass man nicht updaten kann, weil man Objekte nicht zu Klassen casten kann, oder?
Uah, ich bin verwirrt :D
 
Zuletzt bearbeitet:
Okay, also, nach längerem Studium der Java Reflections API habe ich eine Lösung gefunden.
Vorab der Quellcode, dann die Erklärung.
Java:
package pipe;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

public class DataOutputPipe {

    private static ArrayList<String> dataKeyList = new ArrayList<>(); 
    private static ObjectedModuleContainerList<String, Object> moduleContainerList = new ObjectedModuleContainerList<>(); 
    private static ObjectedModuleContainerList<String, Object> objectedModuleContainerList = new ObjectedModuleContainerList<>(); 
    public void add(String name, Object component) {
        if(moduleContainerList.contains(name)) {
            // TODO Exceptionhandling
            return;
        }
        DataOutputPipe.moduleContainerList.addObjectedModule(name, component);
        DataOutputPipe.dataKeyList.add(name);
    }

    public <T> T getModule(String name, Class<T> type) {
        T toreturn = null;
        Class<?> classToInstance = moduleContainerList.getObjectedModule(name).getClass();
        String classNameToInstance = classToInstance.getName();
    
        // TODO Exceptionhandling
        try {
            Class<?> clazz = Class.forName(classNameToInstance);
            Constructor<?> ctor = clazz.getConstructor();
            Object object = ctor.newInstance();
            toreturn = type.cast(object);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return toreturn;
    }

    public <T> T getModule(String name) {
        Object o = DataOutputPipe.moduleContainerList.getObjectedModule(name);
        Class<T> type = (Class<T>) o.getClass(); 
        return getModule(name, type);
    }

    public <T> T getObjectedModule(String key) {
        Object o = objectedModuleContainerList.getObjectedModule(key); 
        return (T) o.getClass().cast(objectedModuleContainerList.getObjectedModule(key));
    }

    public void updateObjectedModule(String key, Object instanceToSafe) {
        objectedModuleContainerList.addObjectedModule(key, instanceToSafe);
    }

    public Class<?> getType(String name) {
        return DataOutputPipe.moduleContainerList.getObjectedModule(name).getClass();
    }

    public ArrayList<String> getAllKeys() {
        return DataOutputPipe.dataKeyList;
    }

    public boolean keyInDataOutputPipe(String key) {
        return DataOutputPipe.dataKeyList.contains(key);
    }

}

Hier fehlt noch ne Menge, aka custom-exceptions usw. aber so etwas ist ein funktionierender Prototyp.
Also, was ist das hier.

Die anderen Dateien wurden nicht groß verändert.
Zum bessern Verständnis hier mal mit Beispiel.

Java:
package main;

import main.test.A;
import main.test.Tester;
import pipe.DataOutputPipe;

public class Main {
    public static void main(String[] args) {
        DataOutputPipe dataOutputPipe = new DataOutputPipe();
        dataOutputPipe.add(A.class.getName(), new A());

        A a = dataOutputPipe.getModule(A.class.getName());
        a.whoAmI();
        a.setName("nichtA");
        a.whoAmI();

        dataOutputPipe.updateObjectedModule(A.class.getName(), a);

        A a2 = dataOutputPipe.getModule(A.class.getName());
        a2.whoAmI();

        A a3 = dataOutputPipe.getObjectedModule(A.class.getName());
        a3.whoAmI();

    }
}
Ausgabe:
Mein Name ist "A"
Mein Name ist "nichtA"
Mein Name ist "A"
Mein Name ist "nichtA"

Also, der Ablauf.
Als erstes instantiieren wir eine DataOutputPipe. Dies könnte man auch static machen, oder als Singleton, ist hier aber unwesentlich.
Wir fügen dann der DataOutputPipe ein Modul hinzu, die Klasse A. Die Klasse A hat wie vorher auch, ein Atribut name und eine Methode whoAmI und setName.
Hier wäre es cool, wenn das mit annotations klappen könnte
In der DataOutputPipe wird nun in einer static instanz der DataContainerList, die in meinem vorangegangenen Post steht, von der nur der Name geändert wurde.
Dort wird das Instantiierte Objekt gespeichert.
Wenn wir in der nächsten Zeile dann sagen
Java:
        A a = dataOutputPipe.getModule(A.class.getName());
returnt die DataOutputPipe eine neue Instanz, des gespeicherten Objektes. So ist a unabhängig von der DataOutputPipe.
mit whoAmI wird dann ausgegeben ' Mein Name ist "A" '. Wir ändern daraufhin den Namen mit setName und rufen erneut whoAmI auf. Wie erwartet ist die Ausgabe nun ' Mein Name ist "nichtA" '.
Als nächstes speichern wir uns mit:
Java:
       dataOutputPipe.updateObjectedModule(A.class.getName(), a);
die aktuelle Instanz von a mit dem neuen Namen "nichtA". Und speichern uns dann in a2 eine weitere "Basis-Instanz" von A, nur um sicher zu gehen, dass wir diese noch genau so bekommen können, nachdem wir eine neue gespeichert haben, was wir mit a2.whoAmI() bestätigen.
Interessanter wird es mit a3. In a3 speichern wir uns mit:
Java:
        A a3 = dataOutputPipe.getObjectedModule(A.class.getName());
eine Instanz von der geänderten a-Klasse. Und auch das bestätigen wir durch whoAmI, welches "nichtA" ausgibt.
Ist die DataOutputPipe verständlich, oder soll ich die auch noch einmal erklären?
Und kennt sich jemand mit Custom-Annotations aus, so dass man sagen kann
Java:
@Module
public class A {
....
Wird die Klasse A automatisch geladen, so dass der code-Teil
Java:
        dataOutputPipe.add(A.class.getName(), new A());
nicht mehr notwendig wird? Oder ist so etwas utopisch, allein und "quick and dirty" zu entwickeln?

Mit den besten Grüßen!
 
Ich hoffe ich gehe hier keinen auf die Nerven... Nur muss ich das mitteilen...
Es klappt auch. Der Prototyp, nur ohne tatsächliche Register ist fertig.
Ich teile hier mal meinen Quellcode. Nur, da dies nicht die tatsächliche Lösung ist, soll ich das Thema schon als gelöst markieren? Oder erst, wenn ich es auch mit Registern zum Laufen gebracht habe?
Nun aber mal zu dem coolen Teil: Ich haue erst einmal etwas Quellcode raus und versuche das dann zu erklären. Natürlich ist es nicht möglich, das komplett zu erklären, das würde zu lange dauern zu schreiben/lesen und ein tl;dr will keiner.
Zu erst eine custom Annotation. Wir wollen später alle Klassen, mit dieser Annotation automatisch einbinden in die DataOutputPipe.
Java:
package annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RegisterModul {
    boolean integratedInRegisterHandler() default true;
}

In diesem Beispiel nutze ich abermals die Klasse A als positives Beispiel. Ich habe noch kein negatives Beispiel (aka eine Klasse, die nicht in der DataOutputPipe ist, aber trotzdem angefordert wird)
Java:
package main.test;

import annotations.RegisterModul;


@RegisterModul
public class A {

    private String name;

    public A () {
        name = "A";
    }

    public String getDataName() { return this.name; }

    public void setName(String name) { this.name = name; }

    public void whoAmI() {
        System.out.println("Mein Name ist \""+getDataName()+"\"");
    }
}

Nun "die Magie", der RegisterHandler.
Java:
package handler;

import handler.scanner.AnnotationScanner;
import pipe.DataOutputPipe;

import java.util.List;

public class RegisterHandler {

    private static List<Class<?>> allModules;
    private static DataOutputPipe dataOutputPipe;
    private static RegisterHandler instance;

    public synchronized static RegisterHandler getInstance() {
        if(instance == null) {
            instance = new RegisterHandler();
        }
        return instance;
    }

    private RegisterHandler() {
        if(RegisterHandler.allModules == null) {
            RegisterHandler.allModules = AnnotationScanner.getInstance().getAllAnnotadedClasses();
            RegisterHandler.dataOutputPipe = DataOutputPipe.getInstance();
            for(Class<?> c : RegisterHandler.allModules) {
                try {
                    RegisterHandler.dataOutputPipe.add(c.getName(), c.newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public <T> T getClass(String key) {
        return dataOutputPipe.getModule(key);
    }
}
Der RegisterHandler soll später noch die Möglichkeit bieten, neue Register zu erstellen, Register auf Namen zu binden, damit auch andere / mehrere Prozesse ohne eine Instanz-Übergabe und ohne Observable-Pattern immer die selbe Instanz eines Objektes haben.
Der RegisterHandler nutzt eine weiter Klasse, neben der DataOutputPipe, den AnnotationScanner.
Diese Klasse sieht so aus:
Java:
package handler.scanner;

import annotations.RegisterModul;

import java.util.List;

public class AnnotationScanner {

    private static List<Class<?>> annotadedClasses;


    private static PathScanner pathScanner;

    private static String rootPath;

    private static AnnotationScanner ourInstance;
    private static List<Class<?>> classesWithAnnotatedType;


    public static AnnotationScanner getInstance() {
        if(ourInstance == null) {
            ourInstance = new AnnotationScanner();
        }
        return ourInstance;
    }

    private AnnotationScanner() {
        System.out.println("$ Starting up PathScanner ... ");
        pathScanner = PathScanner.getInstance();
        System.out.println("$ PathScanner started successfully!");
    }

    public List<Class<?>> getAllAnnotadedClasses() {
        if(rootPath == null) {
            rootPath = "main";
        }
        return getClassesWithAnnotatedType();
    }

    private List<Class<?>> getClassesWithAnnotatedType() {
        if(AnnotationScanner.classesWithAnnotatedType != null) {
            return AnnotationScanner.classesWithAnnotatedType;
        }
        System.out.println("$ Loading annotaded classes for the first Time ... ");
        List<Class<?>> allClasses = pathScanner.find(rootPath);
        System.out.println("found: " + allClasses.size() + " classes");
        int numberClasses = allClasses.size();
        for(int i = 0 ; i < numberClasses ; i++) {
            System.out.print("prozessing class " + allClasses.get(i).getName() + " ... ");
            if(allClasses.get(i).isAnnotationPresent(RegisterModul.class)) {
                System.out.print(" this class is annotated to be auto-implemented ... ");
                allClasses.add(allClasses.get(i));
            }
            System.out.println("done");
        }
        AnnotationScanner.classesWithAnnotatedType = allClasses;
        return allClasses;
    }

}
Kurz gesagt, lädt diese Klasse alle Klassen mit der custom Annotation mit Hilfe der folgenden Klasse:
Java:
package handler.scanner;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class PathScanner {

    private static PathScanner ourInstance;

    public static PathScanner getInstance() {
        if(ourInstance == null) {
            System.out.print("$ Initialing PathScanner for the first time... ");
            ourInstance = new PathScanner();
            System.out.println("done");
        }
        return ourInstance;
    }

    private PathScanner() {

    }

    private static final char PKG_SEPARATOR = '.';

    private static final char DIR_SEPARATOR = '/';

    private static final String CLASS_FILE_SUFFIX = ".class";

    private static final String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the package '%s' exists?";

    public List<Class<?>> find(String scannedPackage) {
        String scannedPath = scannedPackage.replace(PKG_SEPARATOR, DIR_SEPARATOR);
        URL scannedUrl = Thread.currentThread().getContextClassLoader().getResource(scannedPath);
        if (scannedUrl == null) {
            throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage));
        }
        File scannedDir = new File(scannedUrl.getFile());
        List<Class<?>> classes = new ArrayList<Class<?>>();
        for (File file : scannedDir.listFiles()) {
            classes.addAll(find(file, scannedPackage));
        }
        return classes;
    }

    private List<Class<?>> find(File file, String scannedPackage) {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        String resource = scannedPackage + PKG_SEPARATOR + file.getName();
        if (file.isDirectory()) {
            for (File child : file.listFiles()) {
                classes.addAll(find(child, resource));
            }
        } else if (resource.endsWith(CLASS_FILE_SUFFIX)) {
            int endIndex = resource.length() - CLASS_FILE_SUFFIX.length();
            String className = resource.substring(0, endIndex);
            try {
                classes.add(Class.forName(className));
            } catch (ClassNotFoundException ignore) {
            }
        }
        return classes;
    }
}
Diese Klasse habe ich aus Artefakten aus dem Internet kreiert. Sie durchsucht quasi ein "root"-verzeichniss, welches aktuell noch durch ein Atribut bestimmt wird (hier empfiehlt sich eine Konfigurationsdatei o.ä). Ich habe überlegt hier eine fertige API zu nutzen, nur wollte ich dies nach Möglichkeit ohne Abhängigkeiten von irgendwas erstellen, was auch geklappt hat!
Zu guter letzt noch die Main-Klasse, zum testen:
Java:
package main;

import handler.RegisterHandler;
import main.test.A;


public class Main {
    public static void main(String[] args) {

        RegisterHandler d = RegisterHandler.getInstance();
        A a = d.getClass(A.class.getName());
        a.whoAmI();

    }
}
Die Ausgabe ist (mit allen Prototyp-Ausgaben in den anderen Klassen kombiniert):

$ Starting up PathScanner ...
$ Initialing PathScanner for the first time... done
$ PathScanner started successfully!
$ Loading annotaded classes for the first Time ...
found: 5 classes
prozessing class main.Main ... done
prozessing class main.test.Tester ... done
prozessing class main.test.C ... done
prozessing class main.test.A ... this class is annotated to be auto-implemented ... done
prozessing class main.test.B ... done

Mein Name ist "A"


Es funktioniert. Wenn auch noch nicht perfekt, wie es am ende sein soll, aber es funktioniert.
der Methodenaufruf
Java:
A a = d.getClass(A.class.getName());
soll später ersetzt werden durch das öffnen eines neuen Registers, bzw. dem joinen eines bereits bestehenden Registers , in welchen man auf die Objekte zugreifen kann.
Ich hoffe ich habe nichts vergessen.. Ist das alles so weit verständlich? Soll ich etwas mehr erklären? Oder erst einmal den Prototyp ein einen Entwurfstypen überführen?
Oder soll ich ganz aufhören, hier zu schreiben?

Mit den besten Grüßen!
 
Zuletzt bearbeitet:
Hi

Bitte nicht aufhören :)

Wollte eigentlich auch schon eine Antwort schreiben (zum Thema, und länger als die hier) und bin nur noch nicht dazu gekommen. Tut mir leid, wenn das zurzeit etwas nach Alleinunterhaltung ausschaut. Es liegt sicher nicht daran, dass deine Posts irgendwie schlecht oder fehl am Platz wären, ganz im Gegenteil!

Ich hoffe, ich komme morgen dazu, auch was themenbezogenes beizutragen...
 
Okay, dann kommt jetzt gleich mal der 2. Prototyp :D
Ich habe die binären Register fertig. Die können 3 elementare Operationen. Pull, fetch und push. Alle die schon mal mit Version-Controll gearbeitet haben, wird das etwas sagen. Auch hier mal wieder ein paar Code-Schnipsel. Sehr viel bestehendes hat sich nicht geändert. Lediglich der RegisterHandler-, sowie die Main-Klasse. In anderen Klassen wurden nur Ausgaben entfernt.

Der RegisterHandler sieht nun wie folgt aus:
Java:
package handler;

import handler.register.Register;
import handler.register.RegisterID;
import handler.scanner.AnnotationScanner;
import pipe.DataOutputPipe;

import java.util.HashMap;
import java.util.List;

public class RegisterHandler {

    private static List<Class<?>> allModules;
    private static DataOutputPipe dataOutputPipe;
    private static RegisterHandler instance;

    // Eine statische Liste alle Register, damit kein Name 2 mal vor kommt
    private static HashMap<String, Register> registerList;

    public synchronized static RegisterHandler getInstance() {
        if(instance == null) {
            instance = new RegisterHandler();
        }
        return instance;
    }

    private RegisterHandler() {
        System.out.println("$ Initialing RegisterHandler for the first time ...");
        if(RegisterHandler.allModules == null) {
            RegisterHandler.allModules = AnnotationScanner.getInstance().getAllAnnotadedClasses();
            RegisterHandler.dataOutputPipe = DataOutputPipe.getInstance();
            int numberAllModules = allModules.size();

            /* Warum nicht foreach?
             *
             * Es gibt mehrere Stellen, an denen die Listen iterriert werden. Manchmal 2 oder mehr gleichzeitig.
             * Wenn an jeder Stelle eine foreach schleife verwendet werden würde, würder der Listeninterne Interator ziemlich durcheinander kommen.
             * Deswegen nutzen wir zum Iterieren der Listen besser normale for-schleifen.
             */
            for(int i = 0 ; i < numberAllModules ; i++) {
                try {
                    RegisterHandler.dataOutputPipe.add(allModules.get(i).getName(), allModules.get(i).newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        if(registerList == null) {
            registerList = new HashMap<>();
        }

    }

    public <T> T getModule(String key) {
        return dataOutputPipe.getModule(key);
    }

    public RegisterID pullNewRegister() {
        Register newRegister = new Register();
        registerList.put(newRegister.getRegisterId().toString(), newRegister);
        return newRegister.getRegisterId();
    }

    public <T> T getModuleFromRegister(RegisterID registerID, String className) {
        return registerList.get(registerID.toString()).pullModule(className);
    }

    public Register getRegisterForId(RegisterID registerID) {
        return registerList.get(registerID.toString());
    }

}
Der RegisterHandler besitzt zwar noch die Methode
Java:
  public <T> T getModule(String key)
, welche in dem aktuellen Kontext redundant ist, aber sie kann zum testen genutzt werden.
In Prinzip fehlt hier Abermals das Exception-Handling... :( Ich bitte das zu entschuldigen...
Die Idee:
- Pull ein register (ein neues Register erstellen) -> pullModuleFromPipe (fügt ein Modul von der DataOutputPipe dem Register hinzu) -> Arbeite mit diesem.
Dafür gibt es 2 neue Datentypen:
1. Das Register:
Java:
package handler.register;

import pipe.DataOutputPipe;

import java.util.HashMap;

public class Register {

    // Die ID des Registers, eindeutig identifizierend
    private RegisterID registerId;
    // Die Instanz der DataOutputPipe
    private DataOutputPipe dataOutputPipe;

    // Klassen, zugeschnitten auf die Instanz eines Registers
    private HashMap<String, Object> moduleContainerList;

    public Register() {
        moduleContainerList = new HashMap<>();
        registerId = new RegisterID();
        dataOutputPipe = DataOutputPipe.getInstance();
    }

    public RegisterID getRegisterId() {
        return registerId;
    }

    public void fetchModuleFromPipe(String className) {
        if(!moduleContainerList.containsKey(className)) {
            moduleContainerList.put(className, dataOutputPipe.getModule(className));
        }
    }

    public void pullModuleFromPipe(String className) {
        moduleContainerList.put(className, dataOutputPipe.getModule(className));
    }

    public <T> T fetchAndGetModuleFromPipe(String className) {
        fetchModuleFromPipe(className);
        return (T) moduleContainerList.get(className);
    }

    public <T> T pullAndGetModuleFromPipe(String className) {
        pullModuleFromPipe(className);
        return (T) moduleContainerList.get(className);
    }

    public <T> T pullModule(String className) {
        return (T) moduleContainerList.get(className);
    }

    //TODO !
    public void pushClassToRegister(Class<?> clazz) {

    }
}
Das Register hat folgende 3 elementare Aktionen:
- Pull. Hole auf jeden Fall ein Modul aus der DataOutputPipe und füge es dem Register hinzu. Wenn es schon existiert, überschreibe es.
- Fetch. Hole ein Modul aus der DataOutputPipe und füge es dem Register hinzu, außer es existiert schon.
- Push. Schreibe ein Klasse in ein Register.

und 2. Die RegisterID. Diese ist ein ziemlich einfacher Datentyp, aber ich mag den Stil.
Java:
package handler.register;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class RegisterID {
    private static List<String> allRegisterIDs;
    private String currentRegisterID;

    public RegisterID() {
        allRegisterIDs = new ArrayList<>();
        randomID();
    }

    public String toString() {
        return currentRegisterID;
    }

    public void randomID() {
        String toTest = UUID.randomUUID().toString();
        while(allRegisterIDs.contains(toTest)) {
            toTest = UUID.randomUUID().toString();
        }
        RegisterID.allRegisterIDs.add(toTest);
        currentRegisterID = toTest;
    }
}
Das ist ziemlich einfach. Auch wenn ich hier UUID benutze, gehe ich in der Methode randomID() sicher, dass die ID noch nicht existiert. Und das habe ich mir einfach gemacht, mit einer static List für alle vergebenen RegisterID's.

Nun hier mal die übliche Main-Methode, mit der ich teste. Ich mache es hier mal einfach mit der getRegisterForId-Methode. Das verletzt etwas das Gesetz von Demeter, aber naja.. wird schon passen.
Java:
package main;

import handler.RegisterHandler;
import main.test.Tester;


public class Main {
    public static void main(String[] args) {

     
        RegisterHandler registerHandler = RegisterHandler.getInstance();
        RegisterID registerID = registerHandler.pullNewRegister();
        A a = registerHandler.getRegisterForId(registerID).pullAndGetModuleFromPipe(A.class.getName());
        A a2 = registerHandler.getRegisterForId(registerID).pullModule(A.class.getName());
        a.whoAmI();
        a2.whoAmI();

    }
}
Ausgabe:

$ Initialing RegisterHandler for the first time ...
$ Initialing AnnotationScanner for the first time ...
$ Initialing PathScanner for the first time...
$ Loading annotaded-classes for the first Time ...
Mein Name ist "A"
Mein Name ist "A"


Was machen wir hier: Die oberen Ausgaben, die mit einem $ anfangen, werden bei den Singleton-Aufrufen, welche das erste mal eine Instanz erstellen, ausgegeben. Diese kann man ignorieren.
Im Code erstellen wir zu erst einmal eine Instanz des RegisterHandler's. Dabei werden alle Klassen, des Root-Verzeichnisses rekursiv gescant usw. wie bereits in einen der Vorangegangen Post gezeigt.
Dann pullen wir ein neues Register mit
Java:
pullNewRegister()
Dabei wird eine neue Instanz eines Register in der Liste in dem RegisterHandler erstellt. Im Anschluss könne wir mit der unschönen
Java:
registerHandler.getRegisterForId(registerID).pullAndGetModuleFromPipe(A.class.getName());
methode das Register ansprechen. Mit der Methode
Java:
pullAndGetModuleFromPipe(SomeClassName);
machen wir das folgende: Wir holen uns die Klasse die SomeClassName heißt aus der DataOutputPipe, speichern diese im Register und zugleich gibt diese Methode die Klasse zurück. Schluss endlich mit
Java:
a.whoAmI();
verifizieren wir, dass das auch wirklich a ist.

Ich bin grade etwas zu sehr im Uni-Stress, als das ich einen vollständigen Feature-Test schreiben könnte.. Das hole ich nach, sobald ich etwas mehr Zeit habe.
Außerdem würde ich gerne für euch ein Github repository bereitstellen. Das habe ich aber noch nie gemacht und jedes mal wenn ich das pushe und pulle, kann man es nicht mehr kompilieren.. Ich arbeite mit IntelliJ ultimate, das wäre kein Problem, es gibt auch eine Integration für eben jenes, nur habe ich Github noch nie genutzt.

Ich möchte hier noch einmal betonen, dass ich keiner fertigen Frameworks nutze, da ich den RegisterHander so unabhängig wie möglich haben möchte!

Das Fehlt noch:
- Register auf einen bestimmten Namen binden, dass auch Prozesse, die sich auf einen Namen geeinigt haben, dieses Register nutzen können.
- Doku (wie immer ... ).
- Register-Templates. Das man sagen kann, erstelle ein neues register und pull gleich dieses und dieses Modul aus der Pipe.
- Überdenken, ob der Klassenname das beste ist, ob Klassen in der OutputPipe zu identifizieren.
- Konfiguration einlesen und nutzen.
- Die Instantiierung der Klassen nicht von dem Constructor abhängig machen.

Fällt euch noch was auf, was fehlt? Was ihr euch wünschen würdet? Oder wird das :offtopic:?

Mit den besten Grüßen!
 
Zuletzt bearbeitet:
Ich wollte grade 2 mal schreiben, dass ich einen Fehler gefunden habe, aber mir ist beides mal aufgefallen, dass der Fehler nicht im Quellcode liegt, sondern in den Aufrufen :D
Das ganze scheint doch noch komplexer zu sein, als es mir scheint. Da kam mir die Frage auf, ist das so verständlich? Soll ich das ganze etwas ausführlicher erklären? Oder gar von Grund auf neu erklären?
 
Ich nerve euch mal wieder. Dieses mal mit einem praktischen Beispiel!
Doch vorab ein bisschen mehr code.
Der RegisterHandler besitzt nun die Möglichkeit, Register aus eigene, bzw. "legere" ID's zu speichern. Intern werden damit RegisterID's durch legere ID's eindeutig identifiziert.
Java:
package handler;

import handler.register.Register;
import handler.register.RegisterID;
import handler.scanner.AnnotationScanner;
import pipe.DataOutputPipe;

import java.util.HashMap;
import java.util.List;

public class RegisterHandler {

    private static List<Class<?>> allModules;
    private static DataOutputPipe dataOutputPipe;
    private static RegisterHandler instance;

    // Eine statische Liste alle Register, damit kein Name 2 mal vor kommt
    private static HashMap<RegisterID, Register> registerList;
    private static HashMap<String, RegisterID> boundRegisters;

    public synchronized static RegisterHandler getInstance() {
        if(instance == null) {
            instance = new RegisterHandler();
        }
        return instance;
    }

    private RegisterHandler() {
        System.out.println("$ Initialing RegisterHandler for the first time ...");
        if(RegisterHandler.allModules == null) {
            RegisterHandler.allModules = AnnotationScanner.getInstance().getAllAnnotadedClasses();
            RegisterHandler.dataOutputPipe = DataOutputPipe.getInstance();
            int numberAllModules = allModules.size();

            /* Warum nicht foreach?
             *
             * Es gibt mehrere Stellen, an denen die Listen iterriert werden. Manchmal 2 oder mehr gleichzeitig.
             * Wenn an jeder Stelle eine foreach schleife verwendet werden würde, würder der Listeninterne Interator ziemlich durcheinander kommen.
             * Deswegen nutzen wir zum Iterieren der Listen besser normale for-schleifen.
             */
            for(int i = 0 ; i < numberAllModules ; i++) {
                try {
                    RegisterHandler.dataOutputPipe.add(allModules.get(i).getName(), allModules.get(i).newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        if(registerList == null) {
            registerList = new HashMap<>();
        }
        if(boundRegisters == null) {
            boundRegisters = new HashMap<>();
        }

    }

    public boolean registerIDTaken(RegisterID id) {
        return registerList.containsKey(id);
    }

    public boolean registerBound(String legereID) {
        return boundRegisters.containsKey(legereID);
    }

    public RegisterID pullNewRegister() {
        Register newRegister = new Register();
        registerList.put(newRegister.getRegisterId(), newRegister);
        return newRegister.getRegisterId();
    }

    public <T> T getModuleFromRegister(RegisterID registerID, String className) {
        return registerList.get(registerID.toString()).pullModule(className);
    }

    public Register getRegisterForId(RegisterID registerID) {
        return registerList.get(registerID);
    }

    public Register getRegisterForId(String legereId) {
        return getRegisterForId(boundRegisters.get(legereId));
    }

    public void bindRegister(String legereID, RegisterID id) {
        boundRegisters.put(legereID, id);
    }

    public RegisterID getRegisterID(String legereID) {
        return boundRegisters.get(legereID);
    }

}
Die Register haben auch ein kleines Update bekommen. Nicht groß, nur ein paar Kleinigkeiten. Das will ich euch aber nicht vorenthalten.
Java:
package handler.register;

import pipe.DataOutputPipe;

import java.util.HashMap;

public class Register {

    /**
     * The identifier of this register
     */
    private RegisterID registerId;
    /**
     *
     */
    // Die Instanz der DataOutputPipe
    private DataOutputPipe dataOutputPipe;

    // Klassen, zugeschnitten auf die Instanz eines Registers
    private HashMap<String, Object> moduleContainerList;

    public Register() {
        moduleContainerList = new HashMap<>();
        registerId = new RegisterID();
        dataOutputPipe = DataOutputPipe.getInstance();
    }

    public void fetchModuleFromPipe(String className) {
        if(!moduleContainerList.containsKey(className)) {
            moduleContainerList.put(className, dataOutputPipe.getModule(className));
        }
    }

    public void pullModuleFromPipe(String className) {
        moduleContainerList.put(className, dataOutputPipe.getModule(className));
    }

    public <T> T fetchAndGetModuleFromPipe(String className) {
        fetchModuleFromPipe(className);
        return (T) moduleContainerList.get(className);
    }

    public <T> T pullAndGetModuleFromPipe(String className) {
        pullModuleFromPipe(className);
        return (T) moduleContainerList.get(className);
    }

    public <T> T pullModule(String className) {
        return (T) moduleContainerList.get(className);
    }

    public void pushClassToRegister(Class<?> clazz) {
        pushClassToRegister(clazz.getName(), clazz);
    }

    public void pushClassToRegister(String className, Object object) {
        moduleContainerList.put(className, object);
    }

    public RegisterID getRegisterId() {
        return registerId;
    }
}

Soooo und jetzt zum proof of concept! Ich mache es hier mit 2 Top-Level Instanzen. Stellt euch vor, es wären hier mehrere Top- bis Medium-Level Instanzen und anstatt nur 2, relativ synchronen Methoden-Aufrufe, mehrere asynchrone Aufrufe, vielleicht sogar Threads.

Java:
package main;

import handler.RegisterHandler;
import handler.register.RegisterID;
import main.test.Tester;
import main.test.Tester2;


public class Main {
    public static void main(String[] args) {

        RegisterHandler registerHandler = RegisterHandler.getInstance();

        RegisterID id = registerHandler.pullNewRegister();

        RegisterHandler.getInstance().bindRegister("1" , id);

        Tester tester = new Tester();
        Tester2 tester2 = new Tester2();

        int longer = 0;

        while(longer < 4) {

            tester.runner();
            tester2.runner();

            longer++;
        }


    }
}

Die Main Klasse erstellt zu erst eine Instanz des RegisterHandler's. "Zieht" dann ein neues Register und weist ihm den legeren Namen "1" (ja, ich weiß, ich habe auch schon legerere Namen gesehen..).
Dann erstellen wir uns Tester und Tester2 (die beiden kommen gleich). Dann führen wir 4 mal hintereinander die runner Methode der beiden aus.
Jetzt zu den Testern! Ich zeige hier nur eine Klasse, aber keine Sorge, die Klasse Tester2 ist 1:1 die selbe Klasse, nur heißt sie Tester2.
Java:
package main.test;

import handler.RegisterHandler;
import handler.register.Register;

public class Tester {

    private Register register;

    public Tester() {
        this.register = RegisterHandler.getInstance().getRegisterForId("1");
    }

    public void run() {
        C c = register.fetchAndGetModuleFromPipe(C.class.getName());
        c.howMuch();
        c.higher();
        register.pushClassToRegister(C.class.getName() , c);
    }

}
Und hier noch eine Klasse C, die wir brauchen:
Java:
package main.test;

import annotations.RegisterModul;

@RegisterModul
public class C {

    private int counter;

    public C() {
        counter = 1;
    }

    public void higher() {
        counter++;
    }

    public void howMuch() {
        System.out.println("Ich zähle " + counter);
    }

}
Also, was machen wir hier? Im Konstruktor Holen wir uns ein Register aus dem RegisterHandler. Nach der legeren ID "1", in der Wir vorhin das andere Register gespeichert haben (C kommt gleich noch).
In der Methode Run haben wir einen sehr einfachen Algorithmus:
Wir holen (fetchen, also überschreiben das gespeicherte Objekt nicht) uns die Klasse C aus dem Register -> Wir fragen C wie hoch sein Counter ist -> Wir erhöhen C -> Wir speichern (pushen) C in das Register unter dem Namen von C
Normal müsste die Ausgabe jetzt so aussehen:

Ich zähle 1
Ich zähle 1
Ich zähle 2
Ich zähle 2
Ich zähle 3
Ich zähle 3
Ich zähle 4
Ich zähle 4

Aber! Weil wir das Objekt immer aus dem Register Pulen und wieder rein Pushen, weiß Tester2 was Tester gemacht hat und anders rum.
Ohne dass die Klassen Instanzen von C übergeben, haben sie die aktuelle Instanz von C, obwohl C keine einzige / s static Methode / Atribut besitzt.
Deswegen sieht die Ausgabe so aus:

Ich zähle 1
Ich zähle 2
Ich zähle 3
Ich zähle 4
Ich zähle 5
Ich zähle 6
Ich zähle 7
Ich zähle 8


etwas feedback wäre cool :)

Als nächste Erweiterung kommt ExceptionHandling, sowie abfragen, ob RegisterID's bereits existieren usw. Ohne diese Abfragen bekommt man nullpointer exceptions. Nicht immer schön...

Das Fehlt noch:
- (done) Register auf einen bestimmten Namen binden, dass auch Prozesse, die sich auf einen Namen geeinigt haben, dieses Register nutzen können. (done)
- Doku (wie immer ... ).
- Register-Templates. Das man sagen kann, erstelle ein neues register und pull gleich dieses und dieses Modul aus der Pipe.
- Überdenken, ob der Klassenname das beste ist, ob Klassen in der OutputPipe zu identifizieren.
- Konfiguration einlesen und nutzen.
- Die Instantiierung der Klassen nicht von dem Constructor abhängig machen.

Anmerkungen? Erweiterungswünsche? Fragen?

Wie immer: Mit besten Wünschen!
 
Zuletzt bearbeitet:
Zurück