Scala impliziert, dass sogar Java-Shops verstehen können

Nachdem ich die Ankündigung von Implicit auf ScalaMatsuri 2018 gesehen hatte, verstand ich sie endlich und werde aus der Sicht der Java-Benutzer über Implicit schreiben. Da Scala wie ein Amateur ist, besteht eine große Wahrscheinlichkeit, dass Artikel geändert oder gelöscht werden.

Problem, dass die Anzahl der Methoden zu stark zunimmt, wenn die Schnittstelle implementiert wird

Deklarieren Sie in Java, wie das Objekt mit Interface behandelt werden soll, und implementieren Sie die Methode. Wenn beispielsweise die Instanzen x1 und x2 einer Klasse X vergleichbar sind, implementieren Sie die Schnittstelle Comparable in X. Und da dieses vergleichbare Objekt sortiert werden kann, kann es an Collection.sort () usw. übergeben werden.


public class X implements Comparable<X> {
    public int i;

    public X(int i) {
        this.i = i;
    }

    @Override
    public int compareTo( X x) {
        return this.i - x.i;
    }

    @Test
    public void testSort(){
        List<X> list = new ArrayList<X>();
        list.add(new X(3));
        list.add(new X(1));
        list.add(new X(0));
        list.add(new X(2));
        Collections.sort(list);
    }
}

Da Interface eine Erklärung zum Umgang damit ist, kann es so weit implementiert werden, wie es wie A oder B behandelt werden kann. Bei unachtsamer Implementierung wird jedoch auch eine große Anzahl von Schnittstellenmethoden implementiert.


public interface A {
    void a1();
    void a2();
}

public class X implements Comparable<X>, A, B, C {
    public int i;

    public X(int i) {
        this.i = i;
    }

    @Override
    public int compareTo( X x) {
        return this.i - x.i;
    }

    @Override
    public void a1() {
    }

    @Override
    public void a2() {
    }

    @Override
    public void b1() {
    }

    @Override
    public void b2() {
    }

    @Override
    public void c1() {
    }

    @Override
    public void c2() {
    }
}

Das ist nicht gut. Wenn Sie einfach zu viele Methoden haben, kann dies Ihre Aussichten verschlechtern, und Prüfer werden möglicherweise wütend, wenn die Anzahl der Methoden pro Klasse durch Konventionen begrenzt ist. Tatsächlich sind diese Methoden nicht sehr relevant, sodass es keinen großen Vorteil hat, sie an derselben Stelle zu schreiben. Implementieren Vergleichbar für das Sortieren bedeutet in erster Linie, dass es ohne das Sortieren nicht implementiert werden würde, was bedeutet, dass es nichts mit der Essenz des Objekts zu tun hat. Die Klasse ist "was es ist", die Schnittstelle (Typ) ist "was es behandelt werden kann", und die Deklaration ist leichter zu erkennen, wenn sie an derselben Stelle geschrieben wird, aber die Implementierung hat nicht viel Wert zu mischen.

Teilen Sie die Klasse

Betrachten wir also die Aufteilung der Klasse. Für Methoden wie Comparable, die eine Schnittstelle erfüllen, reicht es häufig aus, eine Klasse zu erstellen, die ein Wrapper für das ursprüngliche Objekt ist. Nehmen wir an, dass X, das dieses Comparable implementiert, eine ComparableX-Klasse ist, sodass toComparableX () von X aus ausgeführt werden kann.


public class ComparableX implements Comparable<ComparableX> {
	public X x;

	public ComparableX(X x) {
		this.x = x;
	}

	@Override
	public int compareTo(ComparableX comparableX) {
		return this.x.i - comparableX.x.i;
	}
}

public class X {
	public int i;

	public X(int i) {
		this.i = i;
	}

	public ComparableX toComparableX() {
		return new ComparableX(this);
	}

	public A toA() {
		return new XinA(this);//Angenommen, Sie erstellen einen Wrapper namens Klasse XinA implementiert A.
	}

	public B toB() {
		return new XinB(this);
	}

	public C toC() {
		return new XinC(this);
	}
}

Wenn Sie sortieren möchten, können Sie eine Liste dieses vergleichbaren X erstellen, sortieren und das darin enthaltene X extrahieren. Ebenso können Sie Klassen mit anderen Schnittstellen definieren. Jetzt sind die Aussichten besser. Eigentlich sollten Sie den Konstruktor und die Factory auf der Wrapper-Seite schreiben, anstatt in A () oder toB () zu schreiben.


class A {
    public A(X x){}
    public static A getInstance(X x){}
}

Ein Conversion-Code erhöht die Anzahl der Conversion-Ziele. Es ist jedoch nicht das Problem von X, dass das Konvertierungsziel zunimmt, sondern das Problem eines anderen Klassencodes, der das Konvertierungsziel anfordert. Es ist nicht zumutbar, X jedes Mal zu ändern, wenn die Anzahl der Konvertierungsziele mit A und B zunimmt.

Wenn Sie es teilen, werden Sie nicht in der Lage sein, damit umzugehen, wie es ist

Es gibt jedoch ein Problem, dass X nicht so behandelt werden kann, wie es durch Sandwiching-Methoden, Konstruktoren und Fabriken ist.


A xA= x.toA()
aFnc(xA)
aFnc(new A(x))
aFnc(A.getInstance(x))
//aFnc(X)Ich wünschte ich könnte es direkt schreiben ...

Wenn es mehrere Methoden gibt, die Objekte desselben Typs zurückgeben, können Sie möglicherweise nicht bestimmen, welche verwendet werden sollen.


class X{
//Welches soll ich verwenden! ??
    public A toWonderfulA(){}
    public A toGratestA(){}
}

Im Gegenteil, es besteht die Möglichkeit, dass die Namen kollidieren, und um ehrlich zu sein, möchte ich für jede Schnittstelle separat schreiben. Als eine Täuschung, die mit Java nicht realisiert werden kann, stellen wir uns eine Sprache vor, in der dieselbe Klasse für jede implementierende Schnittstelle separat geschrieben werden kann.


public class X {
	public int i;

	public X(int i) {
		this.i = i;
	}
}

public class X implements Comparable<X> {
	public int compareTo(X x) {
		return this.i - x.i;
	}
}

public class X implements A {
	public void a1() {
	}

	public void a2() {
	}

}

Ich finde die Aussichten sehr gut. Natürlich kann dieser Code nicht kompiliert werden. Ich möchte, dass der Compiler die Tatsache ausnutzt, dass sie sich in derselben Klasse befinden, und sie zusammenführt.

Scala kann es schaffen

Aber Scala kann ähnlichen Code schreiben. Ja, implizit.


case class X(i: Int) {
}

//So etwas wie Schnittstelle
trait A[T] {
    def a1():Int
    def a2():Int
}

object X {

//Sie können den Code "Wenn ein anderer Typ für X angefordert wird" in Implicit schreiben. Dies gibt einfach einen Wert zurück, der von X abgerufen werden kann.
  implicit def xToInt(x:X): Int = x.i

// class X { toOrderingX(){return new Ordering()}}Gleich wie.Vergleicher statt vergleichbar, da X nicht direkt gebunden ist. Diese Bestellung ist sortiert.compare()Sortieren Sie, indem Sie zwei Elemente X der Liste übergeben und vergleichen
  implicit val toOrderingX: Ordering[X] = new Ordering[X] {
    override def compare(x: X, y: X): Int = x.i - y.i
  }

//Für normalen Code sieht es so aus, als würde eine Klasse X die Bestellung implizieren oder ein X mit einem Ordnungskomparator
  val xList = List(new X(3), new X(1), new X(2), new X(0))
  val sortedList = xList.sorted

// class X { toA(){return new A(this)}}Gleich wie. Sie können das Argument x direkt binden, aber ich habe es nicht als Beispielcode gesehen, da es nicht wie eine Typklasse aussieht.
  implicit def xToA(x:X): A[X] = new A[X] {
    def a1()=x.i
    def a2(){}
  }

//Sieht aus wie Klasse X A implementiert ... aber nur neues A.(new X(1))Ist es einfacher zu glauben, dass es so funktioniert?
  aFnc(new X(1))

}


Implizit wird als implizite Typkonvertierung bezeichnet, ist jedoch aus Sicht des Java-Shops wie eine Nachrüstschnittstelle. Klassen und Schnittstellen können auf die gleiche Weise als Typen behandelt werden, unterscheiden sich jedoch geringfügig. Auf diese Weise ist es möglich, die Klasse zu trennen und von außen damit umzugehen, was zum Schreiben eines einfacheren Modells hilfreich ist.

Implizit besteht zwischen einer Klasse und einem Typ

Eine Methode einer Klasse ist ein Codeblock, der mit einer Instanz dieser Klasse arbeitet. Ist es dann nicht einfacher, den Code zu verwalten, der auf dieselbe Instanz abzielt, wenn er zusammen in dieser Klasse geschrieben wird? Das ist eine der Grundideen der Objektorientierung.


//Funktionen können überall platziert werden, sind also kostenlos, aber schwierig zu verwalten
def f(x : X) : Y = ...
def g(x : X) : Y = ...

//Eine Methode ist ein Codeblock, der einer Klasseninstanz zugeordnet ist. Bewegt sich, wenn X als solches übergeben wird
class X {
    def f() = ...
    def g() = ...
}

Implizit ist ein Codeblock, der an eine "Beziehung gebunden ist, in der eine Klasse einen anderen Typ benötigt". Es scheint, dass der Code, den ich früher als Täuschung in Java geschrieben habe, tatsächlich funktioniert.


//Ich möchte, dass Sie eine Verbindung zu "Wenn X zum Vergleichen aufgefordert wird" herstellen (wird nicht kompiliert)
public class X implements Comparable<X> {
	public int compareTo(X x) {
		return this.i - x.i;
	}
}

//Ich möchte, dass Sie eine Verbindung zu "Wenn X für Typ A angefordert wird" herstellen (wird nicht kompiliert)
public class X implements A {
	public void a1() {
	}

	public void a2() {
	}

}


//Implizit führt zu "wenn X angefordert wird, Typ A" (kompiliert durch)
  implicit def xToA(x:X): A[X] = new A[X] {
    def a1()=...
    def a2(){}
  }

Beziehung zu DDD

Das Domänenmodell sollte von allem anderen als der Domäne unabhängig sein. Die grundlegende Implementierung besteht darin, es unabhängig von anderen Ebenen zu machen, aber umgekehrt denke ich, dass es auch notwendig ist, Code zu vermeiden, der so weit wie möglich von anderen Ebenen abhängt. Wenn Sie beispielsweise ein Domänenmodell haben, das keine Sortierung voraussetzt, sollten Sie Compareable implementieren, da es praktisch ist, sortieren zu können, wenn es auf dem Bildschirm angezeigt wird? Wenn Sie dem Modell nur zur Vereinfachung Code hinzufügen, verliert das Modell seine Reinheit. Möglicherweise können Sie mithilfe von Implicit mehr domänenorientierten Code schreiben.

Wir möchten das Domänenmodell auch so weit wie möglich vereinheitlichen, aber in verschiedenen Kontexten kann es als anderes Modell entworfen werden. Angenommen, Sie haben ein Objekt, das sich auf den Verkauf bezieht. Dieses Objekt ist ein Produkt, wenn es verkauft wird, aber es ist nur eine Fracht, wenn es zu einem Geschäft transportiert wird, und der Käufer wird es verwenden, wie er oder sie es möchte, unabhängig von der Bequemlichkeit des Verkäufers. Mit anderen Worten, das Objekt ist je nach Kontext unterschiedlich. In Büchern usw. wird geschrieben, um einen Konverter zu schreiben, um das Domänenmodell zu konvertieren, aber es ist nicht ungewöhnlich, dass ein Konverter eine instabile Standposition hat und mehrere ähnliche erstellt werden, und "Konverter ist ein Modell. Es kann unfruchtbare Kontroversen wie "Ist es ein Teil von?" Implizit kann verwendet werden, um den Konverter darauf aufmerksam zu machen und ihn zu verwalten. (Derzeit scheint dies nicht die empfohlene Verwendung zu sein.)

Implizite Probleme

Es ist schwer zu verstehen, wo es geschrieben ist oder wo es geschrieben werden sollte

Die Methode ist eine Funktion, die eng mit der Klasse verbunden ist und sich zumindest physisch zusammen mit X befindet. Schnittstellen werden in den meisten Sprachen auch neben Klassen deklariert, sodass sich die Deklarationen an derselben Stelle befinden. Implizit zerstört es. Es ist nur so, dass es nicht immer an derselben Stelle wie die Klasse geschrieben wird, weil es außerhalb der Klasse deklariert ist. Sollte sich der Code, wenn X für Typ A angefordert wird, an derselben Stelle wie X befinden? Sollte es an der gleichen Stelle wie A sein? Wo sollen X und A geschrieben werden, wenn sie in der vorhandenen Klasse nicht an derselben Stelle geschrieben werden können? Dies ist ein schwieriges und ernstes Problem. Weil die Klasse eine Beschreibung von "was es ist" ist, aber es gibt eine Tradition / Richtlinie, um "was es ist" zu schreiben, aber es gibt keine Richtlinie, um zu schreiben "was es ist, wenn es als das erkannt wird". ist.

Die Beziehung muss selbsterklärend sein, aber es kann nicht festgestellt werden, ob sie selbstverständlich ist

Wenn sich Int beispielsweise als Float verhalten soll, ist es selbstverständlich, dass Int ein Float sein kann. Andererseits ist es nicht offensichtlich, ob Float Int sein kann. Viele Sprachen werden abgeschnitten, aber es gibt andere Rundungsmethoden wie das Runden. Bei der Konvertierung von Daten in einen anderen Datentyp sind unerwartete Konvertierungen nicht ungewöhnlich, da nicht häufig darüber nachgedacht wird, ob dies zweckmäßig ist oder nicht, sondern ob dies selbstverständlich ist.

Schlechter Name für implizite Typkonvertierung

Wenn Sie Implizit wörtlich übersetzen, wird es implizit sein, aber Ich wollte den Compiler über die Typkonvertierung informieren, das heißt, es war eine Deklaration, also hätte ich sie deklarative Typkonvertierung oder ähnliches nennen sollen. Die Typkonvertierung ist ebenfalls etwas zweifelhaft. Wird "etwas, das die Lücke zwischen der Klasse und dem erforderlichen Typ füllt" als Typkonvertierung bezeichnet? Ich bin jedoch nicht mit Scala oder Englisch vertraut, daher ist dies nur eine Ausrede.

Impressionen

Implizit hat den Eindruck, dass das Konzept korrekt ist und dem Code Freiheit gibt, aber es ist leicht zu missbrauchen und es gibt nicht genügend Richtlinien, um es richtig zu schreiben.

Recommended Posts

Scala impliziert, dass sogar Java-Shops verstehen können
Grundlagen von jQuery, die selbst Freeter verstehen können
"Vererbung", die auch Anfänger der Objektorientierung verstehen können
Polymorphismus, den auch Anfänger der Objektorientierung verstehen können
Aufbau einer Android-Umgebung, die selbst Affen verstehen können [React Native]
Erklären Sie "objektorientiert", das selbst Grundschüler auf Stufe 5 verstehen können
Aufbau einer JSP + Eclipse + Jetty-Entwicklungsumgebung, die selbst Java-Anfänger ausführen können
Verstehen Sie den Java-Konstruktor
[JavaScript] Java-Programmierer! Sie können das Schließen von JavaScript sehr leicht verstehen!
Schreiben einer Klasse, die in Java bestellt werden kann Ein kleines Standard-Memo
Spielen Sie mit Java-Funktionsknoten, die Java mit Node-RED verwenden können
[JQuery] Ein Typ, den selbst Anfänger gut verstehen konnten