Устранение шаблона операции GWT ActivityMapper
Я использую структуру GWT Activities and Places для структурирования своего приложения, и оно получается красиво. Одна вещь, которая меня раздражает, заключается в том, что реализация ActivityMapper
- это (1) получение всех представлений в приложении (2) содержит гигантский блок if/else для создания экземпляров на основе полученного места. Это будет только ухудшаться по мере увеличения количества просмотров.
![ActivityMapper screenshot]()
Я уже использую Gin, но я не вижу, как я могу использовать его здесь.
Как я могу уменьшить или исключить шаблон из моего ActivityMapper
?
Ответы
Ответ 1
Пока нет отличного ответа. У меня есть схемы генерации кода, но на данный момент все это написано на белых досках. Для пользователей Gin кажется, что область места может быть удобной.
Re: каскад if/else, один общий подход заключается в том, чтобы ваши объекты Place реализовали шаблон посетителя. Например. предположим, что у вас есть AssistedInject, настроенный для ваших действий (и простить инъекцию неаккуратного поля, это просто эскиз).
class BasePlace extends Place {
<T> T acceptFilter(PlaceFilter filter);
}
interface PlaceFilter<T> {
T filter(FooPlace place);
T filter(BarPlace place);
T filter(BazPlace place);
}
public class MainActivities implements ActivityMapper {
@Inject FooFactory fooMaker;
@Inject BarFactory barMaker;
@Inject BazFactory bazMaker;
public Activity getActivity(PlaceChangeEvent e) {
return ((BasePlace)e.getPlace()).acceptFilter(
new PlaceFilter<Activity>() {
Activity filter(FooPlace place) {
return fooMaker.create(place);
}
Activity filter(BarPlace place) {
return barMaker.create(place);
}
Activity filter(BazPlace place) {
return bazMaker.create(place);
}
})
}
}
Ответ 2
Одна из возможностей заключается в том, чтобы корень вашей иерархии классов Place определял метод createActivity(), а подклассы Place могли возвращать новый экземпляр Activity, с которым они связаны.
@Override
public Activity getActivity(Place place) {
return ((BaseAppPlace)place).createActivity();
}
Преимущество этого заключается в том, что он исключил этот блок if/else и имеет меньшее место для изменения при добавлении нового места/активности. Недостатком этого является то, что он вызывает загрязнение вашего класса Place с помощью поведения создания активности, даже если вы просто делегируете Ginjector.
Ответ 3
На самом деле я использую специальный шаблонный код для этой задачи:
public class PuksaActivityMapper implements ActivityMapper {
private HashMap<String, ActivityContainer> mappings;
@Inject
private SearchResultActivityContainer searchResultContainer;
@Inject
private HelloActivityContainer helloContainer;
@Override
public Activity getActivity(Place place) {
ActivityContainer container = getMappings().get(place.getClass().getName());
return container.getActivity(place);
}
public HashMap<String, ActivityContainer> getMappings() {
if (mappings == null) {
mappings = new HashMap<String, ActivityContainer>();
mappings.put(ShowResultsPlace.class.getName(), searchResultContainer);
mappings.put(HelloPlace.class.getName(), helloContainer);
}
return mappings;
}
}
Где ActivityContainer - это простой тип factory (с этого момента можно использовать классические методы ioc).
Конечно, теперь это только изменение "if block" с поиском/населением карты, но в сочетании с Gin multibinding (ведьма в настоящее время не существуют) могли бы сделать это.
Также Улучшение Gin - общий GinModule для GWT Activity/Places выглядит многообещающим.
Ответ 4
Просто для будущих ссылок для таких людей, как я, приземлился здесь и до сих пор не получил ответа.
Я получил следующее решение с использованием GIN и генераторов для PlaceFactory.
Вот что теперь выглядят мои токены. например: # EditUser/id: 15/type: Agent
У меня есть AbstractPlace, что каждое место должно простираться от.
public abstract class AbstractPlace extends Place {
public abstract Activity getActivity();
}
Пример Место:
public class EditUserPlace extends AbstractPlace {
private Long id;
private User.Type type;
//getters and setters
@Override
public Activity getActivity() {
return App.getClientFactory().getEditUserPresenter().withPlace(this);
}
}
Интерфейс PlaceFactory для привязки с привязкой:
public interface PlaceFactory {
Place fromToken(String token);
String toToken(Place place);
}
и аннотации для регистрации классов классов
public @interface WithPlaces {
Class<? extends Place>[] value() default {};
}
и PlaceFactoryGenerator
Настройка генератора в вашем модуле GWT
<generate-with class="app.rebind.place.PlaceFactoryGenerator">
<when-type-assignable class="app.client.common.AppPlaceFactory"/>
</generate-with>
public class PlaceFactoryGenerator extends Generator {
private TreeLogger logger;
private TypeOracle typeOracle;
private JClassType interfaceType;
private String packageName;
private String implName;
private Class<? extends Place>[] placeTypes;
@Override
public String generate(TreeLogger logger,
GeneratorContext generatorContext, String interfaceName)
throws UnableToCompleteException {
this.logger = logger;
this.typeOracle = generatorContext.getTypeOracle();
this.interfaceType = typeOracle.findType(interfaceName);
this.packageName = interfaceType.getPackage().getName();
this.implName = interfaceType.getName().replace(".", "_") + "Impl";
// TODO Trocar annotation por scan
WithPlaces places = interfaceType.getAnnotation(WithPlaces.class);
assert (places != null);
Class<? extends Place>[] placeTypes = places.value();
this.placeTypes = placeTypes;
PrintWriter out = generatorContext.tryCreate(logger,
packageName, implName);
if (out != null) {
generateOnce(generatorContext, out);
}
return packageName + "." + implName;
}
private void generateOnce(GeneratorContext generatorContext, PrintWriter out) {
TreeLogger logger = this.logger.branch(
TreeLogger.DEBUG,
String.format("Generating implementation of %s",
interfaceType));
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
packageName, implName);
factory.addImport(interfaceType.getQualifiedSourceName());
factory.addImplementedInterface(interfaceType.getSimpleSourceName());
factory.addImport(StringBuilder.class.getCanonicalName());
factory.addImport(Map.class.getCanonicalName());
factory.addImport(HashMap.class.getCanonicalName());
factory.addImport(Place.class.getCanonicalName());
for (Class<? extends Place> place : placeTypes)
factory.addImport(place.getCanonicalName());
SourceWriter sw = factory.createSourceWriter(generatorContext, out);
sw.println("public Place fromToken(String token) {");
sw.indent();
sw.println("int barAt = token.indexOf('/');");
sw.println("String placeName = token;");
sw.println("Map<String, String> params = new HashMap<String, String>();");
sw.println("if (barAt > 0) {");
sw.indent();
sw.println("placeName = token.substring(0, barAt);");
sw.println("String[] keyValues = token.substring(barAt + 1).split(\"/\");");
sw.println("for (String item : keyValues) {");
sw.indent();
sw.println("int colonAt = item.indexOf(':');");
sw.println("if (colonAt > 0) {");
sw.indent();
sw.println("String key = item.substring(0, colonAt);");
sw.println("String value = item.substring(colonAt + 1);");
sw.println("params.put(key, value);");
sw.outdent();
sw.println("}");
sw.outdent();
sw.println("}");
sw.outdent();
sw.println("}\n");
for (Class<? extends Place> placeType : placeTypes) {
String placeTypeName = placeType.getSimpleName();
int replaceStrPos = placeTypeName.lastIndexOf("Place");
String placeName = placeTypeName.substring(0, replaceStrPos);
sw.println("if (placeName.equals(\"%s\")) {", placeName);
sw.indent();
sw.println("%s place = new %s();", placeTypeName, placeTypeName);
generateSetExpressions(sw, placeType);
sw.println("return place;");
sw.outdent();
sw.println("}\n");
}
sw.println("return null;");
sw.outdent();
sw.println("}\n");
sw.println("public String toToken(Place place) {");
sw.indent();
sw.println("StringBuilder token = new StringBuilder();\n");
for (Class<? extends Place> placeType : placeTypes) {
String placeTypeName = placeType.getSimpleName();
int replaceStrPos = placeTypeName.lastIndexOf("Place");
String placeName = placeTypeName.substring(0, replaceStrPos);
sw.println("if (place instanceof %s) {", placeTypeName);
sw.indent();
sw.println("%s newPlace = (%s)place;", placeTypeName, placeTypeName);
sw.println("token.append(\"%s\");", placeName);
generateTokenExpressions(sw, placeType);
sw.println("return token.toString();");
sw.outdent();
sw.println("}\n");
}
sw.println("return token.toString();");
sw.outdent();
sw.println("}\n");
sw.outdent();
sw.println("}");
generatorContext.commit(logger, out);
}
private void generateTokenExpressions(SourceWriter sw,
Class<? extends Place> placeType) {
for (Field field : placeType.getDeclaredFields()) {
char[] fieldName = field.getName().toCharArray();
fieldName[0] = Character.toUpperCase(fieldName[0]);
String getterName = "get" + new String(fieldName);
sw.println("token.append(\"/%s:\");", field.getName());
sw.println("token.append(newPlace.%s().toString());", getterName);
}
}
private void generateSetExpressions(SourceWriter sw, Class<? extends Place> placeType) {
for (Field field : placeType.getDeclaredFields()) {
char[] fieldName = field.getName().toCharArray();
fieldName[0] = Character.toUpperCase(fieldName[0]);
String setterName = "set" + new String(fieldName);
List<Method> methods = findMethods(placeType, setterName);
for (Method method : methods) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0 || parameterTypes.length > 1)
continue;
Class<?> parameterType = parameterTypes[0];
String exp = "%s";
if (parameterType == Character.class) {
exp = "%s.charAt(0)";
} else if (parameterType == Boolean.class) {
exp = "Boolean.parseBoolean(%s)";
} else if (parameterType == Byte.class) {
exp = "Byte.parseInt(%s)";
} else if (parameterType == Short.class) {
exp = "Short.parseShort(%s)";
} else if (parameterType == Integer.class) {
exp = "Integer.parseInt(%s)";
} else if (parameterType == Long.class) {
exp = "Long.parseLong(%s)";
} else if (parameterType == Float.class) {
exp = "Float.parseFloat(%s)";
} else if (parameterType == Double.class) {
exp = "Double.parseDouble(%s)";
} else if (parameterType.getSuperclass().isAssignableFrom(Enum.class)) {
exp = parameterType.getCanonicalName() + ".valueOf(%s)";
} else if (parameterType != String.class){
continue;
}
String innerExp = String.format("params.get(\"%s\")", field.getName());
String wrapperExp = String.format(exp, innerExp);
sw.println("place.%s(%s);", setterName, wrapperExp);
}
}
}
private List<Method> findMethods(Class<? extends Place> placeType, String name) {
Method[] methods = placeType.getMethods();
List<Method> found = new ArrayList<Method>();
for (Method method : methods) {
if (method.getName().equals(name)) {
found.add(method);
}
}
return found;
}
}
Как выглядит мой ActivityMapper?
public class AppActivityMapper implements ActivityMapper {
public Activity getActivity(Place place) {
AbstractPlace abstractPlace = (AbstractPlace)place;
return abstractPlace.getActivity();
}
}
Нам нужен пользовательский PlaceHistoryMapper
public class AppPlaceHistoryMapper implements PlaceHistoryMapper {
private AppPlaceFactory placeFactory = GWT.create(AppPlaceFactory.class);
public Place getPlace(String token) {
return placeFactory.fromToken(token);
}
public String getToken(Place place) {
return placeFactory.toToken(place);
}
}
И, наконец, PlaceFactory, это будет сгенерировано для того, чтобы просто помещать ваши классы места в аннотацию и быть счастливым!
@WithPlaces(value = {
HomePlace.class,
EditUserPlace.class
})
public interface AppPlaceFactory extends PlaceFactory {
}
Ответ 5
Прежде всего, я создал проблему с проблемами GWT, поэтому, пожалуйста, запустите ее и прокомментируйте.
Вот как я это делаю:
public abstract class PlaceWithActivity extends Place {
public Activity getActivity();
}
Затем в вашем ActivityMapper:
Public Activity get Activity(Place newPlace) {
return ((PlaceWithActivity) newPlace).getActivity();
}
Все ваши места должны расширять PlaceWithActivity. Единственная проблема заключается в откате, которая рискует ClassCastException. У Place был getActivity(), тогда вам не пришлось бы сбрасывать, но это не значит, что вам нужно понизить его до класса, который это делает.
Мне не нравится, что вам нужно делать кастинг и создавать класс PlaceWithActivity.
Это не было бы необходимо, если бы GWT добавила поддержку тому, что я делаю. Если бы они включали класс PlaceWithActivity, который вам не нужно было бы делать, и если ActivityManager просто вызвал бы метод getActivity() класса PlaceWithActivity, вам не оставалось бы не только сбрасывать, но вам даже не нужно было писать ActivityMapper!
Ответ 6
Я нашел аккуратный подход Игоря Климера. Он использует шаблон посетителя, чтобы протолкнуть логику решения в реализацию Place, таким образом ActivityMapper остается довольно простым. Отметьте его сообщение в блоге для деталей реализации.