[JAVA] A nice workaround when "having an instance of either of the two classes"

TL;TR

The story that it is convenient to make such a thing

Overview version.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());
		}
	}
}

preamble

There are times when you want to create one of two different class instances from an input. (For example, when you want to read a log file or form in which various records are aggregated and create another instance for each content.) You may also want to change those different instances to a single class instance for the final aggregation. (For example, when you want to convert the contents of an instance to SolrInputDocument in order to plunge it into NoSQL.)

For example, if you want to create an instance of SecondObjectA class (access log) and SecondObjectB class (purchase log) from FirstObject class (each line of log file) and convert it to FinalObject class (SolrInputDocument), the code will be like this.

FirstObject firstObject = getFirstObject();

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

Assumption: Let's review the class design first

The best solution is to change the class design so that SecondObjectA and SecondObjectB have a common parent class (or interface) SecondObject and SecondObject FirstObject.transform ().

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

However, this is often not the case in the world. There are many cases where the class design becomes complicated, the problem of double inheritance prevents inheritance, and the class has to be used. This article discusses possible workarounds in such cases.

problem

Let's look at the problem when we go back to the first code.

Repost.java


FirstObject firstObject = getFirstObject();

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

--The description is quick --transformA and transformB are functionally parallel but far away --It's hard to keep track of the code when the inside of the if, else block gets bigger.

Occasionally seen solution: make a bean

As a solution when SecondObjectA and SecondObjectB do not have a common interface or parent class, you may find a way to create a bean with both instances.

public class MyBean {
	private SecondObjectA secondObjectA;
	private SecondObjectB secondObjectB;
//Getter below/lined up with setter
}

This way, the calling part can be written as if we had introduced a common parent class. But

――NPE usually occurs from such a place (although it can be dealt with by making it Immutable, hiding the constructor so that it has a unique value, etc.) --It's easy to create bad code like if (myBean.secondObjectA! = Null) {hogeuhga ~} else {} at the conversion method. --A large number of bean classes are created when there are multiple pairs that "have one of the two instances".

And it becomes a hotbed that causes a bad phenomenon.

solution

If you make something like this

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

You can write like this.

Main part.java


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

Alternatively, the first object may be passed in a stream. In such a case

Main part.java


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

Like this.

Conclusion: Not so much used

If you introduce the OneOf class like this, it may be neat and simple. Since it can be used without worrying about the relationship between classes, it can also be used for existing classes, which is often convenient. However, if you need "one of the two homebrew classes", be aware that the class design may not be good. (If you use it effectively even in your own class, it will be effective. Poor vocabulary)

Recommended Posts

A nice workaround when "having an instance of either of the two classes"
[Java] When writing the source ... A memorandum of understanding ①
Check the operation of two roles with a chat application
Java learning_Behavior when there is a method with the same name as a field with the same name in two classes that have an inheritance relationship
A memo of the update command when npm install causes an error in GitHub Action etc.
About an instance of java
The basics of the process of making a call with an Android app
Get a proxy instance of the component itself in Spring Boot
[Ruby] Relationship between parent class and child class. The relationship between a class and an instance.
I took a look at the resources of Azure Container Instance