554 lines
22 KiB
Java
554 lines
22 KiB
Java
package de.wwwu.awolf.presenter.evaluation;
|
|
|
|
import de.wwwu.awolf.model.Line;
|
|
import de.wwwu.awolf.model.LineModel;
|
|
import de.wwwu.awolf.model.Point;
|
|
import de.wwwu.awolf.model.communication.Data;
|
|
import de.wwwu.awolf.model.communication.EvaluationData;
|
|
import de.wwwu.awolf.model.communication.SubscriberType;
|
|
import de.wwwu.awolf.presenter.Presenter;
|
|
import de.wwwu.awolf.presenter.algorithms.Algorithm;
|
|
import de.wwwu.awolf.presenter.algorithms.advanced.LeastMedianOfSquaresEstimator;
|
|
import de.wwwu.awolf.presenter.algorithms.advanced.RepeatedMedianEstimator;
|
|
import de.wwwu.awolf.presenter.algorithms.advanced.TheilSenEstimator;
|
|
import de.wwwu.awolf.presenter.algorithms.naiv.NaivLeastMedianOfSquaresEstimator;
|
|
import de.wwwu.awolf.presenter.algorithms.naiv.NaivRepeatedMedianEstimator;
|
|
import de.wwwu.awolf.presenter.algorithms.naiv.NaivTheilSenEstimator;
|
|
import de.wwwu.awolf.presenter.generator.DatasetGenerator;
|
|
import de.wwwu.awolf.presenter.io.DataImporter;
|
|
import de.wwwu.awolf.presenter.util.IntersectionComputer;
|
|
import de.wwwu.awolf.presenter.util.Logging;
|
|
|
|
import java.io.File;
|
|
import java.util.*;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.Flow;
|
|
|
|
/**
|
|
* Implementierung verschiedener Algorithmen zur Berechnung von Ausgleichsgeraden.
|
|
*
|
|
* @Author: Armin Wolf
|
|
* @Email: a_wolf28@uni-muenster.de
|
|
* @Date: 01.08.2017.
|
|
*/
|
|
public class EvaluateAlgorithms implements Flow.Publisher<Data> {
|
|
|
|
private LineModel arrangement;
|
|
|
|
private List<Line> lmsL;
|
|
private List<Line> rmL;
|
|
private List<Line> tsL;
|
|
|
|
private List<Point> lmsP;
|
|
private List<Point> tsP;
|
|
|
|
private Thread lmsThread;
|
|
private Thread rmThread;
|
|
private Thread tsThread;
|
|
|
|
private DatasetGenerator generator;
|
|
|
|
private String[][] names = {{"MSE", "RMSE", "MAE", "MDAE", "Steigung", "y-Achsenabschnitt", "S-MSE", "S-RMSE", "S-MAE", "S-MDAE", "Brute-force Steigung", "Brute-force y-Achsenabschnitt"}, {"MAPE", "MDAPE", "RMSPE", "RMDSPE", "Steigung", "y-Achsenabschnitt"}};
|
|
|
|
//übergebene Parameter
|
|
private int type;
|
|
private int iterations;
|
|
private int alg;
|
|
private Flow.Subscriber<? super Data> subscriber;
|
|
|
|
/**
|
|
* Konstruktor zur evaluation
|
|
*
|
|
* @param type Typ der evaluation
|
|
* @param n Größe des Datensatzes
|
|
* @param alg 0 = lms,
|
|
* 1 = rm,
|
|
* 2 = ts,
|
|
* 3 = lms, rm,
|
|
* 4 = lms, ts,
|
|
* 5 = rm, ts,
|
|
* 6 = lms, rm, ts,
|
|
* @param datasettyp typ der zu generierenden Datensatz
|
|
*/
|
|
public EvaluateAlgorithms(int type, int n, int alg, String datasettyp, Presenter presenter) {
|
|
subscribe(presenter);
|
|
this.arrangement = new LineModel();
|
|
generator = new DatasetGenerator(presenter);
|
|
switch (datasettyp) {
|
|
case "Punktwolke":
|
|
arrangement.setLines(generator.generateDataCloud(n));
|
|
break;
|
|
case "Gerade":
|
|
arrangement.setLines(generator.generateDataLines(n));
|
|
break;
|
|
case "Kreis und Gerade":
|
|
arrangement.setLines(generator.generateCircle(n));
|
|
break;
|
|
}
|
|
this.type = type;
|
|
this.iterations = n;
|
|
this.alg = alg;
|
|
|
|
IntersectionComputer computer = new IntersectionComputer(arrangement.getLines());
|
|
arrangement.setNodes(computer.compute());
|
|
|
|
lmsL = new LinkedList<>(arrangement.getLines());
|
|
rmL = new LinkedList<>(arrangement.getLines());
|
|
tsL = new LinkedList<>(arrangement.getLines());
|
|
|
|
lmsP = new ArrayList<>(arrangement.getNodes());
|
|
tsP = new ArrayList<>(arrangement.getNodes());
|
|
}
|
|
|
|
/**
|
|
* Konstruktor zur evaluation
|
|
*
|
|
* @param type Typ der evaluation
|
|
* @param alg 0 = lms,
|
|
* 1 = rm,
|
|
* 2 = ts,
|
|
* 3 = lms, rm,
|
|
* 4 = lms, ts,
|
|
* 5 = rm, ts,
|
|
* 6 = lms, rm, ts,
|
|
* @param file Datei die importiert werden soll
|
|
*/
|
|
public EvaluateAlgorithms(int type, int alg, File file) {
|
|
this.arrangement = new LineModel();
|
|
|
|
DataImporter importer = new DataImporter(file, this.subscriber);
|
|
|
|
List<Line> importedLines = importer.run();
|
|
if (importedLines != null)
|
|
arrangement.setLines(importedLines);
|
|
|
|
this.type = type;
|
|
this.alg = alg;
|
|
|
|
IntersectionComputer computer = new IntersectionComputer(arrangement.getLines());
|
|
arrangement.setNodes(computer.compute());
|
|
|
|
|
|
lmsL = new LinkedList<>(arrangement.getLines());
|
|
rmL = new LinkedList<>(arrangement.getLines());
|
|
tsL = new LinkedList<>(arrangement.getLines());
|
|
|
|
lmsP = new ArrayList<>(arrangement.getNodes());
|
|
tsP = new ArrayList<>(arrangement.getNodes());
|
|
}
|
|
|
|
/**
|
|
* Startet die Evaluation zu den passenden Typ. Bei beendigung wird der Beobachter informiert.
|
|
*
|
|
* @throws InterruptedException
|
|
*/
|
|
public void run() {
|
|
|
|
List<String> result;
|
|
List<List<String>> multipleResults = new ArrayList<>();
|
|
ExecutorService executorService = Executors.newCachedThreadPool();
|
|
|
|
|
|
switch (type) {
|
|
case 0:
|
|
//der alg der gewählt wurde
|
|
if (alg == 0) {
|
|
final Line naivEstimator = new Line(0,0);
|
|
final Line advancedEstimator = new Line(0,0);
|
|
executorService.submit(() -> {
|
|
NaivLeastMedianOfSquaresEstimator l = new NaivLeastMedianOfSquaresEstimator(arrangement.getLines());
|
|
l.run();
|
|
naivEstimator.setM(l.getM());
|
|
naivEstimator.setB(l.getB());
|
|
});
|
|
executorService.submit(() -> {
|
|
LeastMedianOfSquaresEstimator lmsAlg = new LeastMedianOfSquaresEstimator(lmsL, lmsP);
|
|
lmsAlg.run();
|
|
lmsAlg.pepareResult();
|
|
advancedEstimator.setM(lmsAlg.getSlope());
|
|
advancedEstimator.setB(lmsAlg.getyInterception());
|
|
});
|
|
|
|
result = getScaleDependentMeasure(arrangement.getLines(), advancedEstimator.getM(), advancedEstimator.getB());
|
|
result.addAll(getScaledErrorBasedMeasure(arrangement.getLines(), advancedEstimator.getM(), advancedEstimator.getB(), naivEstimator.getM(), naivEstimator.getB()));
|
|
Double[] tmp = {advancedEstimator.getM(), advancedEstimator.getB(), naivEstimator.getM(), naivEstimator.getB()};
|
|
sendPlotLineResults(Arrays.asList(tmp), Algorithm.Type.LMS);
|
|
} else if (alg == 1) {
|
|
final double[] m = new double[1];
|
|
final double[] b = new double[1];
|
|
Thread t = new Thread(() -> {
|
|
NaivRepeatedMedianEstimator r = new NaivRepeatedMedianEstimator(arrangement.getLines());
|
|
r.run();
|
|
m[0] = r.getM();
|
|
b[0] = r.getB();
|
|
});
|
|
t.start();
|
|
try {
|
|
startRM();
|
|
} catch (InterruptedException e) {
|
|
Logging.logError(e.getMessage(), e);
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
result = getScaleDependentMeasure(arrangement.getLines(), Double.valueOf(rmRes[0]), Double.valueOf(rmRes[1]));
|
|
result.addAll(getScaledErrorBasedMeasure(arrangement.getLines(), Double.valueOf(rmRes[0]), Double.valueOf(rmRes[1]), m[0], b[0]));
|
|
Double[] tmp = {Double.valueOf(rmRes[0]), Double.valueOf(rmRes[1]), m[0], b[0]};
|
|
sendPlotLineResults(Arrays.asList(tmp), Algorithm.Type.NAIV_RM);
|
|
} else {
|
|
final double[] m = new double[1];
|
|
final double[] b = new double[1];
|
|
Thread t = new Thread(() -> {
|
|
NaivTheilSenEstimator ts = new NaivTheilSenEstimator(arrangement.getLines());
|
|
ts.run();
|
|
m[0] = ts.getM();
|
|
b[0] = ts.getB();
|
|
});
|
|
t.start();
|
|
try {
|
|
startTS();
|
|
} catch (InterruptedException e) {
|
|
Logging.logError(e.getMessage(), e);
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
result = getScaleDependentMeasure(arrangement.getLines(), Double.valueOf(tsRes[0]), Double.valueOf(tsRes[1]));
|
|
result.addAll(getScaledErrorBasedMeasure(arrangement.getLines(), Double.valueOf(tsRes[0]), Double.valueOf(tsRes[1]), m[0], b[0]));
|
|
Double[] tmp = { Double.valueOf(tsRes[0]), Double.valueOf(tsRes[1]), m[0], b[0]};
|
|
sendPlotLineResults(Arrays.asList(tmp), Algorithm.Type.NAIV_TS);
|
|
}
|
|
sendTableApproximationTypes();
|
|
sendTableApproximationData(result, alg);
|
|
break;
|
|
case 1:
|
|
List<List<String>> lineRes;
|
|
switch (alg) {
|
|
case 3:
|
|
try {
|
|
startLMS();
|
|
startRM();
|
|
} catch (InterruptedException e) {
|
|
Logging.logError(e.getMessage(), e);
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1]);
|
|
multipleResults.add(result);
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), rmRes[0], rmRes[1]);
|
|
multipleResults.add(result);
|
|
result = fillPseudoResults();
|
|
multipleResults.add(result);
|
|
|
|
lineRes = new ArrayList<>();
|
|
lineRes.add(Arrays.asList(lmsRes));
|
|
lineRes.add(Arrays.asList(rmRes));
|
|
sendPloteLineResults(lineRes, Arrays.asList(Algorithm.Type.LMS, Algorithm.Type.RM));
|
|
|
|
break;
|
|
case 4:
|
|
try {
|
|
startLMS();
|
|
startTS();
|
|
} catch (InterruptedException e) {
|
|
Logging.logError(e.getMessage(), e);
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1]);
|
|
multipleResults.add(result);
|
|
result = fillPseudoResults();
|
|
multipleResults.add(result);
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), tsRes[0], tsRes[1]);
|
|
multipleResults.add(result);
|
|
|
|
lineRes = new ArrayList<>();
|
|
lineRes.add(Arrays.asList(lmsRes));
|
|
lineRes.add(Arrays.asList(tsRes));
|
|
sendPloteLineResults(lineRes, Arrays.asList(Algorithm.Type.LMS, Algorithm.Type.TS));
|
|
break;
|
|
case 5:
|
|
try {
|
|
startRM();
|
|
startTS();
|
|
} catch (InterruptedException e) {
|
|
Logging.logError(e.getMessage(), e);
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
|
|
result = fillPseudoResults();
|
|
multipleResults.add(result);
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), rmRes[0], rmRes[1]);
|
|
multipleResults.add(result);
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), tsRes[0], tsRes[1]);
|
|
multipleResults.add(result);
|
|
|
|
lineRes = new ArrayList<>();
|
|
lineRes.add(Arrays.asList(rmRes));
|
|
lineRes.add(Arrays.asList(tsRes));
|
|
sendPloteLineResults(lineRes, Arrays.asList(Algorithm.Type.RM, Algorithm.Type.TS));
|
|
break;
|
|
case 6:
|
|
try {
|
|
startLMS();
|
|
startRM();
|
|
startTS();
|
|
} catch (InterruptedException e) {
|
|
Logging.logError(e.getMessage(), e);
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1]);
|
|
multipleResults.add(result);
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), rmRes[0], rmRes[1]);
|
|
multipleResults.add(result);
|
|
result = getPercentigeErrorBasedMeasure(arrangement.getLines(), tsRes[0], tsRes[1]);
|
|
multipleResults.add(result);
|
|
|
|
lineRes = new ArrayList<>();
|
|
lineRes.add(Arrays.asList(lmsRes));
|
|
lineRes.add(Arrays.asList(rmRes));
|
|
lineRes.add(Arrays.asList(tsRes));
|
|
sendPloteLineResults(lineRes, Arrays.asList(Algorithm.Type.LMS, Algorithm.Type.RM, Algorithm.Type.TS));
|
|
break;
|
|
}
|
|
|
|
sendTableApproximationData(multipleResults);
|
|
break;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
/**
|
|
* Die berechneten Ergebnisse werden an den Beobachter übermittelt um dann visualisiert zu werden.
|
|
*
|
|
* @param result Ergebnisse
|
|
* @param col Spalte
|
|
*/
|
|
public void sendTableApproximationData(List<String> result, int col) {
|
|
List<String> tableInput = new ArrayList<>();
|
|
for (int i = 0; i < names[type].length; i++) {
|
|
tableInput.add(result.get(i));
|
|
}
|
|
tableInput.add("");
|
|
EvaluationData data = new EvaluationData();
|
|
data.setColumn(col);
|
|
data.setLabels(tableInput);
|
|
tableInput.clear();
|
|
}
|
|
|
|
/**
|
|
* Die berechneten Ergebnisse werden an den Beobachter übermittelt um dann visualisiert zu werden.
|
|
*
|
|
* @param result Ergebnisse
|
|
*/
|
|
public void sendTableApproximationData(List<List<String>> result) {
|
|
List<String> tableInput = new ArrayList<>();
|
|
|
|
//TODO Hääää? xD
|
|
//iteration über die ApproximationsGüten -- Zeilen
|
|
for (int j = 0; j <= result.get(0).size(); j++) {
|
|
tableInput.add("eval-ds");
|
|
if (j != result.get(0).size()) {
|
|
|
|
tableInput.add(names[type][j]);
|
|
//iteration über die alg. -- Spalten
|
|
for (int i = 0; i < 3; i++) {
|
|
tableInput.add(result.get(i).get(j));
|
|
}
|
|
} else {
|
|
tableInput.add("");
|
|
tableInput.add("");
|
|
tableInput.add("");
|
|
tableInput.add("");
|
|
}
|
|
|
|
EvaluationData data = new EvaluationData();
|
|
data.setType(SubscriberType.EVAL_DS);
|
|
data.setMultipleColumnResult(result);
|
|
this.subscriber.onNext(data);
|
|
|
|
tableInput.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Die Art der Ergebnisse (MSE, RMSE,...) wird an der Beobachter übermittelt.
|
|
*/
|
|
public void sendTableApproximationTypes() {
|
|
EvaluationData data = new EvaluationData();
|
|
data.setType(SubscriberType.EVAL_T);
|
|
data.setLabels(Arrays.asList(names[type]));
|
|
this.subscriber.onNext(data);
|
|
}
|
|
|
|
/**
|
|
* Zur visualisierung der berechneten Geraden wird die Steigung und der y-Achsenabschnitt an den
|
|
* Beobachter übermittelt.
|
|
*
|
|
* @param res Feld mit den Werten für die Steigung und dern y-Achsenabschnitt
|
|
* @param alg code für welchen Algorithmus sich die Werte beziehen
|
|
*/
|
|
public void sendPlotLineResults(List<Double> res, Algorithm.Type alg) {
|
|
EvaluationData data = new EvaluationData();
|
|
data.setType(SubscriberType.LINES_RES);
|
|
data.setAlgorithmtypes(Collections.singletonList(alg));
|
|
data.setOneColumnresult(res);
|
|
this.subscriber.onNext(data);
|
|
}
|
|
|
|
/**
|
|
* Zur visualisierung der berechneten Geraden wird die Steigung und der y-Achsenabschnitt an den
|
|
* Beobachter übermittelt.
|
|
*
|
|
* @param res Feld mit den Werten für die Steigung und dern y-Achsenabschnitt (alle)
|
|
* @param algs codes für welchen Algorithmus sich die Werte beziehen (alle)
|
|
*/
|
|
public void sendPloteLineResults(List<List<String>> res, List<Algorithm.Type> algs) {
|
|
EvaluationData data = new EvaluationData();
|
|
data.setType(SubscriberType.LINES_RES_MULT);
|
|
data.setAlgorithmtypes(algs);
|
|
data.setMultipleColumnResult(res);
|
|
this.subscriber.onNext(data);
|
|
}
|
|
|
|
/**
|
|
* Startet die Berechnung des Alg. zum LMS-Schätzer
|
|
*
|
|
* @throws InterruptedException
|
|
*/
|
|
public void startLMS() throws InterruptedException {
|
|
lmsThread = new Thread(() -> {
|
|
LeastMedianOfSquaresEstimator lmsAlg = new LeastMedianOfSquaresEstimator(lmsL, lmsP);
|
|
lmsAlg.run();
|
|
lmsAlg.pepareResult();
|
|
lmsRes[0] = lmsAlg.getSlope();
|
|
lmsRes[1] = lmsAlg.getyInterception();
|
|
|
|
});
|
|
lmsThread.start();
|
|
lmsThread.join();
|
|
}
|
|
|
|
/**
|
|
* Startet die Berechnung des Alg. zum RM-Schätzer
|
|
*
|
|
* @throws InterruptedException
|
|
*/
|
|
public void startRM() throws InterruptedException {
|
|
rmThread = new Thread(() -> {
|
|
RepeatedMedianEstimator rmAlg = new RepeatedMedianEstimator(rmL);
|
|
rmAlg.run();
|
|
rmAlg.pepareResult();
|
|
rmRes[0] = rmAlg.getSlope();
|
|
rmRes[1] = rmAlg.getyInterception();
|
|
});
|
|
rmThread.start();
|
|
rmThread.join();
|
|
}
|
|
|
|
/**
|
|
* Startet die Berechnung des Alg. zum TS-Schätzer
|
|
*
|
|
* @throws InterruptedException
|
|
*/
|
|
public void startTS() throws InterruptedException {
|
|
tsThread = new Thread(() -> {
|
|
TheilSenEstimator tsAlg = new TheilSenEstimator(tsL, tsP);
|
|
tsAlg.run();
|
|
tsAlg.pepareResult();
|
|
tsRes[0] = tsAlg.getSlope();
|
|
tsRes[1] = tsAlg.getyInterception();
|
|
});
|
|
tsThread.start();
|
|
tsThread.join();
|
|
}
|
|
|
|
/**
|
|
* Startet die Berechnung der skalierungsabbhängigen Maße.
|
|
*
|
|
* @param lines Liste der Geraden
|
|
* @param m Steigung
|
|
* @param b y-Achsenabschnitt
|
|
* @return Liste mit den Ergebnissen, bereit zum visualisieren
|
|
*/
|
|
public List<String> getScaleDependentMeasure(final List<Line> lines, final Double m, final Double b) {
|
|
ScaleDependentMeasure scaleDependentMeasure = new ScaleDependentMeasure(lines, m, b);
|
|
List<String> ret = new ArrayList<>();
|
|
ret.add(scaleDependentMeasure.mse().toString());
|
|
ret.add(scaleDependentMeasure.rmse().toString());
|
|
ret.add(scaleDependentMeasure.mae().toString());
|
|
ret.add(scaleDependentMeasure.mdae().toString());
|
|
ret.add(m.toString());
|
|
ret.add(b.toString());
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Startet die Berechnung der Maße die auf dem prozentualen Fehler basieren.
|
|
*
|
|
* @param lines Liste der Geraden
|
|
* @param m Steigung
|
|
* @param b y-Achsenabschnitt
|
|
* @return Liste mit den Ergebnissen, bereit zum visualisieren
|
|
*/
|
|
public List<String> getPercentigeErrorBasedMeasure(final List<Line> lines, final Double m, final Double b) {
|
|
PercentageErrorBasedMeasure percentageErrorBasedMeasure = new PercentageErrorBasedMeasure(lines, m, b);
|
|
ArrayList<String> ret = new ArrayList<>();
|
|
ret.add(percentageErrorBasedMeasure.mape().toString());
|
|
ret.add(percentageErrorBasedMeasure.mdape().toString());
|
|
ret.add(percentageErrorBasedMeasure.rmspe().toString());
|
|
ret.add(percentageErrorBasedMeasure.rmdspe().toString());
|
|
ret.add(m.toString());
|
|
ret.add(b.toString());
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Startet die Berechnung der skalierungsunabbhängigen Maße.
|
|
*
|
|
* @param lines Liste der Geraden
|
|
* @param m Steigung
|
|
* @param b y-Achsenabschnitt
|
|
* @return Liste mit den Ergebnissen, bereit zum visualisieren
|
|
*/
|
|
public List<String> getScaledErrorBasedMeasure(final List<Line> lines, final Double m, final Double b, final Double nM, final Double nB) {
|
|
ScaledErrorBasedMeasure scaledErrorBasedMeasure = new ScaledErrorBasedMeasure(lines, m, b, nM, nB);
|
|
List<String> ret = new ArrayList<>();
|
|
ret.add(scaledErrorBasedMeasure.mse().toString());
|
|
ret.add(scaledErrorBasedMeasure.rmse().toString());
|
|
ret.add(scaledErrorBasedMeasure.mae().toString());
|
|
ret.add(scaledErrorBasedMeasure.mdae().toString());
|
|
ret.add(nM.toString());
|
|
ret.add(nB.toString());
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Damit es bei der Visualisierung trennende Zeilen gibt.
|
|
*
|
|
* @return
|
|
*/
|
|
private List<String> fillPseudoResults() {
|
|
List<String> result = new ArrayList<>();
|
|
result.add(" ");
|
|
result.add(" ");
|
|
result.add(" ");
|
|
result.add(" ");
|
|
result.add(" ");
|
|
result.add(" ");
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @return Liste der Geraden auf der die Berechnungen ausgeführt wurden
|
|
*/
|
|
public List<Line> getData() {
|
|
return arrangement.getLines();
|
|
}
|
|
|
|
@Override
|
|
public void subscribe(Flow.Subscriber<? super Data> subscriber) {
|
|
this.subscriber = subscriber;
|
|
}
|
|
}
|