我正在使用 GWT 活动和地点框架来构建我的应用程序,结果很好。让我烦恼的一件事是,ActivityMapper
实现是 (1) 接收应用程序中的所有视图 (2) 包含一个巨大的 if/else 块,用于根据接收到的位置实例化活动。随着观看次数的增加,情况只会变得更糟。
我已经在使用Gin,但我不知道如何在这里使用它。
我怎样才能减少或消除我的样板ActivityMapper
?
还没有很好的答案。我心中有代码生成方案,但目前这一切都在白板上乱涂乱画。对于 Gin 用户来说,Place Scope 似乎很方便。
回复: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);
}
})
}
}
一种可能性是让 Place 类层次结构的根定义一个 createActivity() 方法,并且 Place 子类可以返回与其关联的 Activity 的新实例。
@Override
public Activity getActivity(Place place) {
return ((BaseAppPlace)place).createActivity();
}
这样做的好处是消除了 if/else 块,并在添加新的 Place/Activity 时减少了一个需要修改的地方。这样做的缺点是它有点污染你的 Place 类的活动创建行为,即使你只是委托给一个 Ginjector。
实际上,我正在为此任务使用自定义样板代码:
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 是一个简单的工厂类型(从这一点上可以使用经典的 ioc 方法)。
当然,现在它只是通过地图查找/人口来更改“if block”,但结合Gin 多重绑定(女巫目前不存在)可以完成它的工作。
此外Gin 增强- GWT Activity/Places 的通用 GinModule 看起来很有希望。
仅供像我这样的人将来参考降落在这里仍然没有答案。我最终使用 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 {
}
首先,我为此创建了一个关于 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 只需调用 PlaceWithActivity 类的 getActivity() 方法,那么您不仅不必向下转换,而且甚至不需要编写活动映射器!
我发现了Igor Klimer的一个巧妙的方法。他使用访问者模式将决策逻辑推送到Place实现中,这样ActivityMapper就非常简单。查看他的博客文章了解实现细节。