[JAVA] Une belle solution de contournement lorsque "avoir une instance de l'une des deux classes"

TL;TR

L'histoire qu'il est pratique de faire une telle chose

Version de présentation.java


public class OneOf<A, B> {
	final Optional<A> a;
	final Optional<B> b;

	public <C> C apply(Function<A, C> AtoC, Function<B, C> BtoC) {
		if (a.isPresent()) {
			return AtoC.apply(a.get());
		} else {
			return BtoC.apply(b.get());
		}
	}
}

préambule

Il y a des moments où vous souhaitez créer l'une des deux instances de classe différentes à partir d'une entrée. (Par exemple, lorsque vous souhaitez lire un fichier journal ou un formulaire dans lequel divers enregistrements sont agrégés et créer une instance distincte pour chaque contenu.) Vous pouvez également changer ces différentes instances en instances de classe unique pour l'agrégation finale. (Par exemple, lorsque vous souhaitez convertir le contenu d'une instance en SolrInputDocument afin de le plonger dans NoSQL.)

Par exemple, si vous souhaitez créer des instances de la classe SecondObjectA (journal d'accès) et de la classe SecondObjectB (journal des achats) à partir de la classe FirstObject (chaque ligne du fichier journal) et les convertir en classe FinalObject (SolrInputDocument), le code sera le suivant.

FirstObject firstObject = getFirstObject();

FinalObject finalObject;
if(firstObject.isTypeA()){
	SecondObjectA secondObjectA = transformA(first);
	finalObject = finalTransformA(secocndObjectA);
}else{
	SecondObjectB secondObjectB = transformB(first);
	finalObject = finalTransformB(secocndObjectB);
}

Hypothèse: examinons d'abord la conception de la classe

La meilleure solution consiste à modifier la conception de la classe afin que SecondObjectA et SecondObjectB aient une classe parent (ou une interface) commune SecondObject et SecondObject FirstObject.transform ().

FirstObject firstObject = getFirstObject();
SecondObject secondObject = firstObject.transform(); 
FinalObject finalObject = finalTransform(secondObject);

Cependant, ce n'est souvent pas le cas dans le monde. Dans de nombreux cas, la conception de la classe devient compliquée, elle ne peut pas être héritée en raison du problème du double héritage et la classe doit être utilisée. Cet article décrit les solutions de contournement possibles dans de tels cas.

problème

Regardons le problème lorsque nous revenons au premier code.

Republier.java


FirstObject firstObject = getFirstObject();

FinalObject finalObject;
if(firstObject.isTypeA()){
	SecondObjectA secondObjectA = transformA(first);
	finalObject = finalTransformA(secocndObjectA);
}else{
	SecondObjectB secondObjectB = transformB(first);
	finalObject = finalTransformB(secocndObjectB);
}

Solution parfois vue: faire un haricot

Comme solution lorsque SecondObjectA et SecondObjectB n'ont pas d'interface commune ou de classe parent, vous pouvez trouver un moyen de créer un bean avec les deux instances.

public class MyBean {
	private SecondObjectA secondObjectA;
	private SecondObjectB secondObjectB;
//Ci-dessous getter/aligné avec setter
}

En faisant cela, la partie appelante peut être écrite comme si nous introduisions une classe parent commune. Mais

――NPE provient généralement d'un tel endroit (bien qu'il puisse être traité en le rendant immuable, en masquant le constructeur pour qu'il ait une valeur unique, etc.)

Et cela devient un foyer qui provoque un mauvais phénomène.

Solution

Si tu fais quelque chose comme ça

OneOf.java


public class OneOf<A, B> {
	private final Optional<A> a;
	private final Optional<B> b;

	private OneOf(A a, B b) {
		this.a = Optional.ofNullable(a);
		this.b = Optional.ofNullable(b);
	}

	public static <A, B> OneOf<A, B> OneOfA(A a) {
		return new OneOf<A, B>(a, null);
	}

	public static <A, B> OneOf<A, B> OneOfB(B b) {
		return new OneOf<A, B>(null, b);
	}

	public Optional<A> getA() {
		return a;
	}

	public Optional<B> getB() {
		return b;
	}

	public boolean isA() {
		return a.isPresent();
	}

	public boolean isB() {
		return !isA();
	}

	public <C> C apply(Function<A, C> AtoC, Function<B, C> BtoC) {
		if (isA()) {
			return AtoC.apply(a.get());
		} else {
			return BtoC.apply(b.get());
		}
	}

	public static <A, B, C> Function<OneOf<A, B>, C> totalize(Function<A, C> AtoC, Function<B, C> BtoC) {
		return (one) -> one.apply(AtoC, BtoC);
	}
}

Vous pouvez écrire comme ça.

Partie principale.java


FirstObject firstObject = getFirstObject();
OneOf<SecondObjectA,SecondObjectB> secondObject = transform(first);
FinalObject finalObject = secondObject.apply(this::finalTransformA,this::finalTransformB);

Alternativement, le premier objet peut être passé dans un flux. Dans ce cas

Partie principale.java


Stream<FirstObject> firstObject = getFirstObjectStream();
Stream<FinalObject> firstObject.map(this::transform).map(OneOf.totalize(this::finalTransformA,this::finalTransformB));

Comme ça.

Conclusion: pas tellement utilisé

Si vous introduisez la classe OneOf comme ceci, cela peut être simple et net. Puisqu'il peut être utilisé sans se soucier de la relation entre les classes, il peut également être utilisé pour les classes existantes, ce qui est souvent pratique. Cependant, si vous avez besoin de "l'une des deux classes homebrew", sachez que la conception de la classe peut ne pas être bonne. (Si vous l'utilisez efficacement même dans votre propre classe, ce sera efficace. Vocabulaire médiocre)

Recommended Posts

Une belle solution de contournement lorsque "avoir une instance de l'une des deux classes"
[Java] Lors de l'écriture du source ... Mémorandum ①
Vérifier le fonctionnement de deux rôles avec une application de chat
Java learning_Behavior lorsqu'il y a un champ avec le même nom et une méthode avec le même nom dans deux classes dans une relation d'héritage
À propos des instances Java
Obtenez une instance proxy du composant lui-même dans Spring Boot
[Ruby] Relation entre la classe parent et la classe enfant. Relation entre classe et instance.
J'ai jeté un coup d'œil aux ressources d'Azure Container Instance