Устранение шаблона GWT ActivityMapper

Я использую структуру GWT Activities and Places для структурирования своего приложения, и все получается хорошо. Одна вещь, которая меня раздражает, это то, что реализация ActivityMapper (1) получение всех представлений в приложении (2) содержит гигантский блок if / else для создания экземпляров действий на основе полученного места. Будет только хуже по мере увеличения количества просмотров.

Снимок экрана ActivityMapper

Я уже использую джин, но не понимаю, как его здесь можно использовать.

Как я могу уменьшить или исключить шаблон из моего ActivityMapper?


person Robert Munteanu    schedule 27.04.2011    source источник


Ответы (6)


Пока нет отличного ответа. У меня есть схемы генерации кода, но сейчас это все каракули на белых досках. Для пользователей джина кажется, что область видимости может быть удобной.

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);
         }
       })         
   }
}
person rjrjr    schedule 27.04.2011

Одна из возможностей состоит в том, чтобы корень вашей иерархии классов Place определял метод createActivity (), а подклассы Place могли возвращать новый экземпляр Activity, с которым они связаны.

@Override
public Activity getActivity(Place place) {
    return ((BaseAppPlace)place).createActivity();
}

Это имеет то преимущество, что устраняется этот блок if / else и на одно место меньше для изменения при добавлении нового места / действия. Обратной стороной этого является то, что он как бы загрязняет ваш класс Place поведением создания Activity, даже если вы просто делегируете Ginjector.

person Jay    schedule 04.12.2011

На самом деле я использую собственный шаблонный код для этой задачи:

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 с поиском по карте / населением, но в сочетании с Привязка джина (ведьмы в настоящее время не существует) могла бы справиться со своей задачей.

Также Gin Enhancement - общий GinModule для GWT Activity / Places выглядит многообещающим.

person mmazur    schedule 27.04.2011

Просто на будущее, люди вроде меня приземлились здесь и до сих пор не получили ответа. В итоге я получил следующее решение, использующее 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 {

}
person rseidi    schedule 08.04.2012

Прежде всего я создал проблему в 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!

person jgleoj23    schedule 07.01.2015

Я нашел изящный подход от Игоря Климера. Он использует шаблон посетителя, чтобы протолкнуть логику принятия решения в реализацию Place, таким образом, ActivityMapper остается довольно простым. Ознакомьтесь с его сообщением в блоге для реализации. Детали.

person Adrian B.    schedule 23.11.2015