Visitor - aegisql/conveyor GitHub Wiki

Table Of Contents

Visitor

Primary goal of the Reactive Builder project was to provide, well, Asynchronous Builder for some complex data structures. In this article we'll show that Reactive builder is actually closely related to the Visitor pattern.

Visitor pattern is often used when you need to provide different actions on some complex data structure. Developer of the structure does not know what kind of actions will be required.

Let's extend already familiar User and UserBuilder, and show how we can dynamically provide different processing for same data.

Imagine that we have two services that need the same User information. However, first server needs First name first, and Last name second, all in lower case, while the second one needs Last name first, and all in upper case. Our datasources keep user info as is.

Creating abstract builder

/* 
 * COPYRIGHT (C) AEGIS DATA SOLUTIONS, LLC, 2015
 */
package com.aegisql.conveyor.user;

import java.util.function.Supplier;

public abstract class AbstractSmartUserBuilder implements Supplier<User> {
	String first;
	String last;
	Integer yearOfBirth;
	private boolean ready = false;

	public Integer getYearOfBirth() {
		return yearOfBirth;
	}
	public static void setYearOfBirth(AbstractSmartUserBuilder builder, Integer yob) {
		builder.yearOfBirth = yob;
	}
	public String getFirst() {
		return first;
	}
	public abstract void acceptFirst(String first);
	public static void setFirst(AbstractSmartUserBuilder builder, String first) {
		builder.acceptFirst(first);
	}
	public String getLast() {
		return last;
	}
	public abstract void acceptLast(String last);
	public static void setLast(AbstractSmartUserBuilder builder, String last) {
		builder.acceptLast(last);
	}
	public boolean ready() {
		return ready;
	}
	public void setReady(boolean ready) {
		this.ready = ready;
	}
}

Creating smart labels working with this abstract class methods

/* 
 * COPYRIGHT (C) AEGIS DATA SOLUTIONS, LLC, 2015
 */
package com.aegisql.conveyor.user;

import java.util.function.BiConsumer;
import com.aegisql.conveyor.SmartLabel;

public enum BuilderEvents implements SmartLabel<AbstractSmartUserBuilder> {
	
	SET_FIRST(AbstractSmartUserBuilder::setFirst),
	SET_LAST(AbstractSmartUserBuilder::setLast),
	SET_YEAR(AbstractSmartUserBuilder::setYearOfBirth)
	;
	BiConsumer<AbstractSmartUserBuilder, Object> setter;
	<T> AbstractBuilderEvents(BiConsumer<AbstractSmartUserBuilder,T> setter) {
		this.setter = (BiConsumer<AbstractSmartUserBuilder, Object>) setter;
	}
	@Override
	public BiConsumer<AbstractSmartUserBuilder, Object> get() {
		return setter;
	}
}

Create concrete implementations for two different builders.

package com.aegisql.conveyor.user;

public class LowerCaseUserBuilder extends AbstractSmartUserBuilder {
	@Override
	public User get() {
		return new FirstLastUser(first, last, yearOfBirth);
	}
	@Override
	public void acceptFirst(String first) {
		this.first = first.toLowerCase();
	}
	@Override
	public void acceptLast(String last) {
		this.last = last.toLowerCase();
	}
}
public class UpperCaseUserBuilder extends AbstractSmartUserBuilder {
	@Override
	public User get() {
		return new LastFirstUser(first, last, yearOfBirth);
	}
	@Override
	public void acceptFirst(String first) {
		this.first = first.toUpperCase();
	}
	@Override
	public void acceptLast(String last) {
		this.last = last.toUpperCase();
	}
}

Creating concrete implementations for User class

public class FirstLastUser extends User {
	public FirstLastUser (String first, String last, int yob) {
		super(first, last, yob);
	}

	@Override
	public String toString() {
		return first + " " + last;
	}
}
public class LastFirstUser extends User {
	public LastFirstUser (String first, String last, int yob) {
		super(first, last, yob);
	}

	@Override
	public String toString() {
		return last + " " + first;
	}
}

Create conveyor without default builder supplier

AssemblingConveyor<Integer, BuilderEvents, User> conveyor = new AssemblingConveyor<>();
conveyor.setResultConsumer(res -> {
	System.out.println(res.product);
});
conveyor.setReadinessEvaluator((state, builder) -> {
	return state.previouslyAccepted == 3;
});
conveyor.setName("Visiting Assembler");
conveyor.setIdleHeartBeat(100, TimeUnit.MILLISECONDS);
conveyor.setDefaultBuilderTimeout(100, TimeUnit.MILLISECONDS);

Use Create build family methods with explicit builder supplier

CompletableFuture<User> lowerCaseFuture = conveyor.createBuildFuture(1,LowerCaseUserBuilder::new);
CompletableFuture<User> upperCaseFuture = conveyor.createBuildFuture(2,UpperCaseUserBuilder::new);

//then, as usually, send data

conveyor.offer(1, "John", BuilderEvents.SET_FIRST);
conveyor.offer(1, "Doe", BuilderEvents.SET_LAST);
conveyor.offer(1, 1999, BuilderEvents.SET_YEAR);

conveyor.offer(2, "John", BuilderEvents.SET_FIRST);
conveyor.offer(2, "Doe", BuilderEvents.SET_LAST);
conveyor.offer(2, 1999, BuilderEvents.SET_YEAR);

User user1 = lowerCaseFuture.get();
User user2 = upperCaseFuture.get();

System.out.println(user1);
System.out.println(user2);

As expected, output for user1 and user2 will be different, because their fields were processed differently, and they also belong to different classes. Method get() of the Supplier interface in this case becomes a Factory Method

john doe
DOE JOHN
⚠️ **GitHub.com Fallback** ⚠️