Implicite de Scala que même les boutiques Java peuvent comprendre

Après avoir vu l'annonce d'Implicit à ScalaMatsuri 2018, j'ai finalement compris, donc j'écrirai sur Implicit du point de vue des utilisateurs de Java. Puisque Scala est comme un amateur, il y a une grande possibilité que les articles soient modifiés ou supprimés.

Problème que le nombre de méthodes augmente trop lorsque l'interface est implémentée

En java, déclarez comment gérer l'objet avec Interface et implémentez la méthode de celui-ci. Par exemple, si les instances x1 et x2 d'une classe X sont comparables, implémentez l'interface Comparable dans X. Et puisque cet objet comparable peut être trié, il peut être passé à Collection.sort () etc.


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);
    }
}

Depuis Interface est une déclaration sur la façon de le gérer, il peut être implémenté autant qu'il peut être traité comme A ou B. Cependant, s'il est implémenté sans précaution, il implémentera également un grand nombre de méthodes d'interface.


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() {
    }
}

Ce n'est pas bien. Le simple fait d'avoir trop de méthodes peut aggraver vos perspectives et les critiques peuvent se fâcher si le nombre de méthodes par classe est limité par convention. Et en fait, ces méthodes ne sont pas très pertinentes, il n'y a donc pas beaucoup d'avantages à les écrire au même endroit. Implémenter Comparable pour le tri en premier lieu signifie qu'il ne serait pas implémenté sans le besoin de tri, ce qui signifie qu'il n'a rien à voir avec l'essence de l'objet. La classe est "ce qu'elle est", l'interface (type) est "ce qu'elle peut être traitée comme", et la déclaration est plus facile à voir en l'écrivant au même endroit, mais l'implémentation n'a pas beaucoup de mérite à mélanger.

Divisez la classe

Pensons donc à diviser la classe. Pour les méthodes telles que Comparable qui satisfont une interface, il suffit souvent de créer une classe qui est un wrapper pour l'objet d'origine. Supposons que X qui implémente ce Comparable est une classe ComparableX afin que toComparableX () puisse être exécuté à partir de X.


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);//Supposons que vous créez un wrapper appelé classe XinA implémente A.
	}

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

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

Si vous souhaitez trier, vous pouvez faire une liste de ce X comparable, le trier et extraire le X à l'intérieur. De même, vous pouvez définir des classes avec d'autres interfaces. Maintenant, les perspectives sont meilleures. En fait, vous devriez écrire le constructeur et la fabrique du côté wrapper plutôt que d'écrire toA () ou toB ().


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

Un code de conversion est celui qui augmente le nombre de destinations de conversion. Cependant, ce n'est pas le problème de X que la destination de conversion augmente, mais le problème d'un autre code de classe qui demande la destination de conversion. Il n'est pas raisonnable de changer X chaque fois que le nombre de destinations de conversion augmente avec A et B.

Si vous le divisez, vous ne pourrez pas le gérer tel quel

Cependant, il existe un problème en ce que X ne peut pas être traité tel quel en intercalant des méthodes, des constructeurs et des usines.


A xA= x.toA()
aFnc(xA)
aFnc(new A(x))
aFnc(A.getInstance(x))
//aFnc(X)J'aimerais pouvoir l'écrire directement ...

En outre, s'il existe plusieurs méthodes qui renvoient des objets du même type, vous ne pourrez peut-être pas déterminer laquelle utiliser.


class X{
//Lequel devrais-je utiliser! ??
    public A toWonderfulA(){}
    public A toGratestA(){}
}

Au contraire, il y a une possibilité que les noms entrent en collision, et pour être honnête, j'aimerais écrire séparément pour chaque interface. Donc, comme une illusion qui ne peut pas être réalisée avec Java, pensons à un langage dans lequel la même classe peut être écrite séparément pour chaque interface d'implémentation.


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() {
	}

}

Je pense que les perspectives sont très bonnes. Bien entendu, ce code ne peut pas être compilé. J'aimerais que le compilateur tire parti du fait qu'ils sont dans la même classe et les fusionne.

Scala peut le faire

Mais Scala peut écrire du code similaire. Oui, implicite.


case class X(i: Int) {
}

//Quelque chose comme Interface
trait A[T] {
    def a1():Int
    def a2():Int
}

object X {

//Vous pouvez écrire le code "lorsqu'un autre type est demandé pour X" en implicite. Cela renvoie simplement une valeur qui peut être récupérée à partir de X.
  implicit def xToInt(x:X): Int = x.i

// class X { toOrderingX(){return new Ordering()}}Pareil que.Comparateur plutôt que comparable car X n'est pas directement lié. Cette commande est triée.compare()Trier en passant deux éléments X de la liste et en comparant
  implicit val toOrderingX: Ordering[X] = new Ordering[X] {
    override def compare(x: X, y: X): Int = x.i - y.i
  }

//Pour le code normal, cela ressemble à une classe X implémente la commande, ou à un X avec un comparateur de commande
  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)}}Pareil que. Vous pouvez lier l'argument x directement, mais je ne l'ai pas vu comme exemple de code car il ne ressemble pas à une classe de type.
  implicit def xToA(x:X): A[X] = new A[X] {
    def a1()=x.i
    def a2(){}
  }

//On dirait que la classe X implémente A ... mais juste un nouveau A(new X(1))Est-il plus facile de penser que cela fonctionne comme?
  aFnc(new X(1))

}


L'implicite est appelée conversion de type implicite, mais du point de vue de la boutique Java, c'est comme une interface de mise à niveau. Les classes et les interfaces peuvent être traitées comme des types de la même manière, mais leur nature est un peu différente. De cette façon, il est possible de séparer ce qu'est la classe et comment la gérer de l'extérieur, ce qui est utile pour écrire un modèle plus simple.

Implicite existe entre une classe et un type

Une méthode d'une classe est un bloc de code qui opère sur une instance de cette classe. Alors, n'est-il pas plus facile de gérer le code qui cible la même instance s'il est écrit ensemble dans cette classe? C'est l'une des idées de base de l'orientation objet.


//Les fonctions peuvent être placées n'importe où, elles sont donc gratuites mais difficiles à gérer
def f(x : X) : Y = ...
def g(x : X) : Y = ...

//Une méthode est un bloc de code associé à une instance de classe. Se déplace lorsque X est passé comme ceci
class X {
    def f() = ...
    def g() = ...
}

Implicite est un bloc de code lié à une «relation où une classe requiert un autre type». Il semble que le code que j'ai écrit comme une illusion en Java plus tôt fonctionne réellement.


//Je veux que vous vous connectiez à "quand X doit être comparé" (ne compile pas)
public class X implements Comparable<X> {
	public int compareTo(X x) {
		return this.i - x.i;
	}
}

//Je veux que vous vous connectiez à "lorsque X est demandé type A" (ne compile pas
public class X implements A {
	public void a1() {
	}

	public void a2() {
	}

}


//Conduit implicitement à "quand X est demandé de type A" (compile à travers)
  implicit def xToA(x:X): A[X] = new A[X] {
    def a1()=...
    def a2(){}
  }

Relation avec DDD

Le modèle de domaine doit être indépendant de tout autre élément que le domaine. La mise en œuvre de base de ceci est de le rendre indépendant des autres couches, mais à l'inverse, je pense qu'il est également nécessaire d'éviter autant que possible le code qui dépend d'autres couches. Par exemple, si vous avez un modèle de domaine qui n'assume pas le tri, devez-vous implémenter Compareable car il est pratique de pouvoir trier lors de son affichage à l'écran? Si vous ajoutez du code au modèle uniquement par commodité, le modèle perd sa pureté. Vous pourrez peut-être écrire du code plus axé sur le domaine en utilisant Implicit.

Aussi, nous voulons unifier le modèle de domaine autant que possible, mais dans différents contextes, il peut être conçu comme un autre modèle. Par exemple, supposons que vous ayez un objet lié aux ventes. Cet objet est un produit lorsqu'il est vendu, mais ce n'est qu'un fret lorsqu'il est transporté dans un magasin, et l'acheteur l'utilisera comme il le souhaite, quelle que soit la commodité du vendeur. En d'autres termes, l'objet est différent selon le contexte. Dans les livres, etc., il est écrit pour écrire un convertisseur pour convertir le modèle de domaine, mais il n'est pas rare qu'un convertisseur ait une position debout instable et que plusieurs similaires soient créés, et «le convertisseur est un modèle. Il peut y avoir des controverses stériles telles que "Cela fait-il partie de?" Implicite peut être utilisé pour rendre le convertisseur conscient et le gérer. (Actuellement, cela ne semble pas être l'utilisation recommandée.)

Problèmes implicites

Il est difficile de comprendre où il est écrit ou où il doit être écrit

La méthode est une fonction étroitement liée à la classe et au moins physiquement co-localisée avec X. Les interfaces sont également déclarées à côté des classes dans la plupart des langages, de sorte que les déclarations sont au même endroit. Implicite le détruit. C'est juste que ce n'est pas toujours écrit au même endroit que la classe car il est déclaré en dehors de la classe. Le code lorsque X est demandé pour le type A doit-il être au même emplacement que X? Doit-il être au même endroit que A? Où écrire X et A quand ils ne peuvent pas être écrits au même endroit dans la classe existante? C'est un problème difficile et grave. Parce que la classe est une description de «ce que c'est», mais il y a une tradition / ligne directrice pour écrire «ce qu'elle est», mais il n'y a pas de ligne directrice pour écrire «ce que c'est quand elle est reconnue comme cela». est.

La relation doit être explicite, mais on ne peut pas déterminer si elle va de soi

Par exemple, si Int doit se comporter comme un Float, il va de soi que l'Int peut être un Float. D'un autre côté, il n'est pas évident que Float puisse être Int. De nombreuses langues tronquent, mais il existe d'autres méthodes d'arrondi telles que l'arrondi. Lors de la conversion d'une donnée en un autre type de données, il n'est pas rare d'effectuer des conversions inattendues car il n'est pas courant de se demander si c'est pratique ou non, mais ce n'est pas si évident.

Nom incorrect pour la conversion de type implicite

Si vous traduisez implicitement littéralement, ce sera implicite, mais Ce que je voulais faire était d'informer le compilateur de la conversion de type, c'est-à-dire que c'était une déclaration, donc j'aurais dû la nommer conversion de type déclarative ou quelque chose comme ça. La conversion de type est également un peu douteuse. Est-ce que "quelque chose qui comble le vide entre la classe et le type requis" est appelé conversion de type? Cependant, je ne connais pas le Scala ou l'anglais, ce n'est donc qu'une excuse.

Impressions

Implicite a l'impression que le concept est correct et donne au code la liberté, mais il est facile d'en abuser et il n'y a pas assez de directives pour l'écrire correctement.

Recommended Posts

Implicite de Scala que même les boutiques Java peuvent comprendre
Les bases de jQuery que même les freeters peuvent comprendre
"Héritage" que même les débutants en orientation objet peuvent comprendre
Polymorphisme que même les débutants en orientation objet peuvent comprendre
Construction d'un environnement Android que même les singes peuvent comprendre [React Native]
Expliquer «orienté objet» que même les élèves du primaire peuvent comprendre au niveau 5
Construction d'environnement de développement JSP + Eclipse + Jetty que même les débutants en Java peuvent faire
Comprendre le constructeur java
[JavaScript] Programmeurs Java! Si c'est le cas, la fermeture JavaScript est super facile à comprendre!
Ecrire une classe qui peut être ordonnée en Java Un petit mémo standard
Jouez avec les nœuds de fonction Java qui peuvent utiliser Java avec Node-RED
[JQuery] Un gars que même les débutants pourraient bien comprendre