Ликвидация шаблона GWT ActivityMapper - PullRequest
13 голосов
/ 27 апреля 2011

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

ActivityMapper screenshot

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

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

Ответы [ 6 ]

3 голосов
/ 27 апреля 2011

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

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 голосов
/ 05 декабря 2011

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

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

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

1 голос
/ 08 апреля 2012

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

}
1 голос
/ 27 апреля 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).

Конечно, теперь он только меняет 'если блок' с поиском / населением карты, но в сочетании с мульти-связыванием джина (ведьма в настоящее время не существует) может выполнять свою работу.

Также Улучшение джина - универсальный GinModule для GWT Activity / Places выглядит многообещающе.

0 голосов
/ 23 ноября 2015

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

0 голосов
/ 07 января 2015

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

...