Viele Leute lernen Java, wissen aber nicht, wie sie die Benutzeroberfläche verwenden sollen. Tatsächlich funktioniert das Programm ohne Schnittstelle, und ich denke, es ist einfacher, es einfach auszuführen. In diesem Artikel werde ich über eine scheinbar unnötige Schnittstelle schreiben.
Unten werden abwechselnd normaler Java-Code und Spring-Code angezeigt. Spring ist ein sehr guter Rahmen, um die Benutzeroberfläche zu verstehen, deshalb habe ich es gewagt, sie hier zu schreiben. Die Ausführungsergebnisse sind für beide gleich. Sehen Sie sich also das an, das Ihnen gefällt.
gradle macht viele gute dinge. Wenn Sie es nicht wirklich tun, können Sie hier überspringen.
$ git clone https://github.com/reta-ygs/interface-demo.git
$ cd interface-demo
Führen Sie wie normales Java aus
$ ./gradlew pojo
Führen Sie den Frühling aus
$ ./gradlew spring
Definieren Sie "welche Verarbeitung erforderlich ist". Dieses Mal habe ich eine Today-Methode definiert, um den heutigen Tag abzurufen, und eine dayOffset-Methode, um einen Tag abzurufen, z. B. 3 Tage später.
DayOfWeekCalculator.java
public interface DayOfWeekCalculator {
DayOfWeek today();
DayOfWeek dayOffset(int dayOffset);
}
Die Schnittstelle alleine macht nichts. Bereiten Sie eine Klasse vor, die die definierte Schnittstelle implementiert, und schreiben Sie dort den Inhalt der eigentlichen Verarbeitung.
CalculatorImpl.java
public class CalculatorImpl implements DayOfWeekCalculator {
final Clock clock;
public CalculatorImpl(Clock clock) {
this.clock = clock;
}
@Override
public DayOfWeek today() {
return LocalDate.now(clock)
.getDayOfWeek();
}
@Override
public DayOfWeek dayOffset(int dayOffset) {
return LocalDate.now(clock)
.plusDays(dayOffset)
.getDayOfWeek();
}
}
Die Uhr wird später verwendet, dies ist jedoch auch eine Art der Abhängigkeitsinjektion.
Dies ist hier noch nicht wichtig, aber ich werde einer Variablen vom Typ DayOfWeekCalculator eine Instanz von CalculatorImpl zuweisen.
Main.java
DayOfWeekCalculator calculator = new CalculatorImpl(Clock.systemDefaultZone());
System.out.println(calculator.today());
System.out.println(calculator.dayOffset(7));
Mit Spring können Sie aus dem Quellcode entfernen, dass der Inhalt der Schnittstelle die CalculatorImpl-Klasse ist. Definieren Sie zuerst die Bean, diesmal schreiben Sie sie in XML.
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="calculator" class="com.example.CalculatorImpl">
<constructor-arg ref="clock"/>
</bean>
<bean id="clock" class="java.time.Clock" factory-method="systemDefaultZone"/>
</beans>
Verwenden Sie im Code die ApplicationContext-Klasse, damit Spring die Beans für die DayOfWeekCalculator-Klasse vorbereitet.
SpringMain.java
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
DayOfWeekCalculator calculator = context.getBean(DayOfWeekCalculator.class);
System.out.println(calculator.today());
System.out.println(calculator.dayOffset(7));
Sie können das CalculatorImpl im ausgeführten Code nicht sehen. Ich konnte verbergen, dass der Inhalt des DayOfWeekCalculator ein CalculatorImpl ist.
Die gleiche Verarbeitung kann nur mit der CalculatorImpl-Klasse durchgeführt werden, ohne dass eine Schnittstelle vorbereitet werden muss. Auch für die CalcuratorImpl-Klasse verwendet die LocalDate.now-Methode Clock. SystemDefaultZone ohne Argumente. Es gibt zwei Hauptvorteile, warum wir die Schnittstelle vorbereitet haben und warum wir die Uhr von außen erhalten haben.
In diesem Fall sind zwei Arten von Mock-Tests möglich.
Fügen wir eine Methode hinzu, um festzustellen, ob heute ein Feiertag ist.
DayOfWeekCalculator.java
public interface DayOfWeekCalculator {
DayOfWeek today();
DayOfWeek dayOffset(int dayOffset);
boolean isHolidayToday();
}
Nehmen Sie die DayOfWeekCalculator-Klasse als Argument und fügen Sie eine Methode hinzu, um die Zeichenfolge entsprechend dem Beurteilungsergebnis des Urlaubs abzurufen.
Main.java
static String holiday(DayOfWeekCalculator calculator) {
if (calculator.isHolidayToday()) {
return "Holiday!";
} else {
return "Arbeit";
}
}
Wenn Sie diese Feiertagsmethode testen möchten, müssen Sie normalerweise warten, bis die Implementierungsklasse CalculatorImpl abgeschlossen ist. Da das Argument jedoch der DayOfWeekCalculator der Schnittstelle ist, ist es möglich, durch vorübergehende Vorbereitung der Scheinklasse zu testen.
MainTest.java
@Test
void holiday() {
MockCalculator mockCalculator = new MockCalculator(true);
assertEquals(Main.holiday(mockCalculator), "Holiday!");
}
@Test
void notHoliday() {
MockCalculator mockCalculator = new MockCalculator(false);
assertEquals(Main.holiday(mockCalculator), "Arbeit");
}
class MockCalculator implements DayOfWeekCalculator {
final boolean holiday;
MockCalculator(boolean holiday) {
this.holiday = holiday;
}
@Override
public boolean isHolidayToday() {
return holiday;
}
}
Als Ergebnis der Verspottung konnten wir testen, ohne auf die Verarbeitung der zur Schnittstelle hinzugefügten Methode zu warten.
Datumsbezogene Tests sind langwierig. Selbst wenn Sie an einem bestimmten Datum testen möchten, können Sie die Systemzeit wahrscheinlich nicht ändern. Die Standard-API Clock ist sehr nützlich für Datumsänderungstests. Der Grund, warum ich Clock im Konstruktor von CalculatorImpl nicht als Argument verwenden musste, ist, das Testen zu vereinfachen.
CalculatorImplTest.java
//Scheinzeit
Clock mock = Clock.fixed(
LocalDate.of(2020, 7, 7) // 2020/7/Fest auf 7
.atStartOfDay(ZoneId.systemDefault()).toInstant(),
ZoneId.systemDefault());
CalculatorImpl calculator = new CalculatorImpl(mock);
@Test
void today() {
assertEquals(calculator.today(), DayOfWeek.TUESDAY);
}
@Test
void dayOffset() {
assertEquals(calculator.dayOffset(7), DayOfWeek.TUESDAY);
}
Ich konnte testen, ob die Klasse funktioniert, unabhängig vom heutigen Datum.
Zum Beispiel habe ich mich aufgrund von Umständen für Erwachsene entschieden, die CalculatorImpl-Klasse nicht zu verwenden, was nicht zumutbar ist. Es kann nicht geholfen werden, also verwenden wir stattdessen die vorbereitete CalcImplSecond-Klasse.
CalcImplSecond.java
public class CalcImplSecond implements DayOfWeekCalculator {
//Implementierungsdetails weggelassen
}
Ändern Sie einfach die Klasse, um sie der Variablen vom Typ DayOfWeekCalculator zuzuweisen.
Main.java
// DayOfWeekCalculator calculator = new CalculatorImpl(Clock.systemDefaultZone());
DayOfWeekCalculator calculator = new CalcImplSecond();
System.out.println(calculator.today());
System.out.println(calculator.dayOffset(7));
Beide implementieren die DayOfWeekCalculator-Schnittstelle, sodass Sie sie Variablen vom Typ DayOfWeekCalculator zuweisen können. Die Tatsache, dass die Schnittstelle gemeinsam ist, bedeutet auch, dass die Methoden, die aufgerufen werden können, identisch sind und es grundsätzlich nicht erforderlich ist, den Teil zu ändern, der die Zuweisungszielvariable verwendet.
Sie müssen lediglich die Bean-Definition ändern. Es ist keine Änderung des Java-Codes erforderlich.
spring.xml
<bean id="calculator" class="com.example.CalcImplSecond"/>
Ich habe den Code nicht geändert, aber der der Variablen zugewiesene Typ wurde in den CalcImplSecond-Typ geändert.
SpringMain.java
DayOfWeekCalculator calculator = context.getBean(DayOfWeekCalculator.class);
System.out.println(calculator.getClass()); // class com.example.CalcImplSecond
Hier ist ein kleines Gespräch darüber, was eine Schnittstelle ist.
In diesem Beispielprojekt fungiert die Schnittstelle als Puffer zwischen dem Prozessbenutzer und dem tatsächlich ausgeführten Prozess. Lassen Sie uns in jeder Position denken.
Sie müssen nicht wissen, was Sie tatsächlich tun, um den Prozess zu verwenden. [^ 1] Am problematischsten ist, dass sich die aufrufende Methode ändert, z. B. das Argument und der Rückgabewert. Die Schnittstelle ist ein Versprechen an den Implementierer. [^ 2]
Der Wechsel von CalculatorImpl zu CalcImplSecond erleichtert das Wechseln zwischen Klassenentitäten. Dieses Mal ist es direkt neu, aber wenn Sie die Factory-Methode usw. verwenden, werden Sie den Anrufer nicht darüber informieren, dass der Inhalt der Klasse zum Zeitpunkt des Versions-Upgrades usw. gewechselt wurde. Wenn Sie eine Klasse ablehnen müssen, können Sie den Fix minimieren.
Um ehrlich zu sein, sind die Vorteile der Verwendung der Benutzeroberfläche mit diesem Beispielcode nicht so groß. Abschließend möchte ich vorstellen, wo und wie es tatsächlich verwendet wird.
Es wird verwendet, um auf die Datenbank zuzugreifen. Wenn Sie sich jedoch den Inhalt des Pakets ansehen, sind die meisten Klassen Schnittstellen. Verbindung, PreparedStatement / 8 / docs / api / java / sql / PreparedStatement.html), ResultSet usw. Alle Klassen, die ich oft benutze, sind Schnittstellen. Die Verbindungsmethode mit DB wird von der Schnittstelle definiert, und der Verbindungsprozess zu jedem RDBMS wie MySQL und Postgres wird als JDBC-Treiber verteilt. Dank der Schnittstelle kann der Code angewendet werden, ohne zu wissen, mit welcher Datenbank Sie eine Verbindung herstellen. [^ 3]
Servlet Die meisten Klassen wie Request, Response und Session sind ebenfalls Schnittstellen. Die Implementierungsklasse wird von einem Anwendungsserver wie Tomcat bereitgestellt. Außerdem ist die HttpServlet-Klasse, die wir normalerweise schreiben, eine abstrakte Klasse. Überschreiben Sie die erforderliche http-Methode, schreiben Sie die URL-Zuordnung in die XML-Datei und die Anmerkungen, und der AP-Server übernimmt alle Einstellungen und Aufräumarbeiten für Sie.
[^ 1]: Aus Leistungsgründen sind wir uns möglicherweise der internen Implementierung bewusst, aber im Idealfall sollten wir den Anrufer nicht darauf aufmerksam machen. [^ 2]: Tatsächlich ist die "Anfrage" an den Implementierer in Bezug auf den Ausdruck genauer, und darüber hinaus ist die Geschichte der Verantwortung involviert. [^ 3]: Da es abhängig von RDBMS Dialekte gibt, werden Sie beim Schreiben einer Abfrage häufig den Unterschied im Verbindungsziel bemerken. Im Gegenteil, ich denke, dass es fast keine Kenntnis des Verbindungsziels außer der Abfrage gibt.
Recommended Posts