package com.apofig.FilesCollector; import static org.junit.Assert.assertEquals; import org.junit.Test; public class SomeObjectConfigurationTest { @Test public void testCheckSimpleConfisuration() { SomeObject object = SomeObjectFactory.forSettings("/qwe/", "asd1", "asd2").build(); assertEquals("[Some object has settings: [/qwe/asd1, /qwe/asd2]]", object.toString()); } @Test public void testCheckComplexConfisuration() { SomeObject object = SomeObjectFactory.forSettings("/qwe/", "asd1", "asd2"). andSettings("/wer/", "sdf1", "sdf2", "sdf3"). andSettings("/ert/", "dfg").build(); assertEquals("[Some object has settings: [/wer/sdf1, /wer/sdf2, /wer/sdf3, " + "/ert/dfg, /qwe/asd1, /qwe/asd2]]", object.toString()); } }Ну, чтобы был отдельно объект и логика по сбору дополнительной конфигурации для него. В данном случае объект конфигурировался списком путей к xml файлам, из которых потом читал нечто. Файлы могли находится в разных папках, а чтобы не писать как-то так
SomeObject object = new SomeObject(Arrays.asList(new String[] {"/wer/sdf1", "/wer/sdf2", "/wer/sdf3", "/ert/dfg", "/qwe/asd1", "/qwe/asd2"}));пришлось придумать другой, более удобный интерфейс.
Вот как это было реализовано:
package com.apofig.FilesCollector; import java.util.List; public class SomeObjectFactory { public static FilesCollector<SomeObject> forSettings(String baseDir, String ... files) { return new FilesCollectorImpl<SomeObject>(new GetInstance<SomeObject>() { public SomeObject getInstance(List<String> settings) { return new SomeObject(settings); } }).andSettings(baseDir, files); } }
package com.apofig.FilesCollector; import java.util.List; public class SomeObject { private List<String> pathesList; public SomeObject(List<String> pathesList) { this.pathesList = pathesList; } public String toString() { return "[Some object has settings: " + pathesList.toString() + "]"; } }
package com.apofig.FilesCollector; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class FilesCollectorImpl<T> implements FilesCollector<T> { private Map<String, List<String>> settingsFiles = new HashMap<String, List<String>>(); private GetInstance<T> factory; public FilesCollectorImpl(GetInstance<T> factory) { this.factory = factory; } public FilesCollector<T> andSettings(String baseDir, String ... files) { if (settingsFiles.containsKey(baseDir)) { List<String> list = settingsFiles.get(baseDir); list.addAll(asModifiableList(files)); } else { settingsFiles.put(baseDir, asModifiableList(files)); } return this; } public T build() { return factory.getInstance(getPathesList()); } private static <E> List<E> asModifiableList(E ... array) { return new ArrayList<E>( Arrays.asList(array) ); } private List<String> getPathesList () { List<String> result = new ArrayList<String>(); for (Map.Entry<String, List<String>> entry : settingsFiles.entrySet()) { String baseDir = entry.getKey(); for (String fileName : entry.getValue()) { result.add(baseDir + fileName); } } return result; } }
package com.apofig.FilesCollector; public interface FilesCollector<T> { public FilesCollector<T> andSettings(String baseDir, String... files); public T build(); }
package com.apofig.FilesCollector; import java.util.List; interface GetInstance<T> { T getInstance(List<String> settings); }
Большее развитие этот метод получил, когда и другие классы так же захотели конфигурироваться подобным образом. И чтобы избежать дублирования factory метода в SomeObjectFactory для каждого нового объекта я сделал так.
package com.apofig.FilesCollector; import java.lang.reflect.InvocationTargetException; import java.util.List; public class ObjectFactory { public static <T> FilesCollector<T> forClass(final Class<T> objectToConfigure) { return new FilesCollectorImpl<T>(new GetInstance<T>() { public T getInstance(List<String> settings) { Exception exception; try { return objectToConfigure.getConstructor(List.class).newInstance(settings); } catch (IllegalArgumentException e) { exception = e; } catch (SecurityException e) { exception = e; } catch (InvocationTargetException e) { exception = e; } catch (NoSuchMethodException e) { exception = e; } catch (InstantiationException e) { exception = e; } catch (IllegalAccessException e) { exception = e; } throw new RuntimeException("Please add conctructor with 'List<String> settings' for " + objectToConfigure, exception); } }); } }
Извините за мой французский с Exception. Но теперь тесты можно было запускать так
package com.apofig.FilesCollector; import static org.junit.Assert.*; import org.junit.Test; public class SomeObjectConfigurationTest { @Test public void testCheckSimpleConfisuration() { SomeObject object = ObjectFactory.forClass(SomeObject.class). andSettings("/qwe/", "asd1", "asd2").build(); assertEquals("[Some object has settings: [/qwe/asd1, /qwe/asd2]]", object.toString()); } @Test public void testCheckComplexConfisuration() { SomeObject object = ObjectFactory.forClass(SomeObject.class). andSettings("/qwe/", "asd1", "asd2"). andSettings("/wer/", "sdf1", "sdf2", "sdf3"). andSettings("/ert/", "dfg").build(); assertEquals("[Some object has settings: [/wer/sdf1, /wer/sdf2, /wer/sdf3, /ert/dfg, /qwe/asd1, /qwe/asd2]]", object.toString()); } @Test public void testCheckWithoutConfisuration() { SomeObject object = ObjectFactory.forClass(SomeObject.class).build(); assertEquals("[Some object has settings: []]", object.toString()); } }
Еще большего развития класс получил, когда я захотел абстрагироваться так же и от стратегии сбора информации для создаваемого класса. Получилось вот что
package com.apofig.FilesCollector; import static org.junit.Assert.*; import org.junit.Test; public class SomeObjectConfigurationTest { @Test public void testCheckSimpleConfisuration() { SomeObject object = ObjectFactory.byStrategy(FilesCollector.class). andSettings("/qwe/", "asd1", "asd2").build(SomeObject.class); assertEquals("[Some object has settings: [/qwe/asd1, /qwe/asd2]]", object.toString()); } @Test public void testCheckComplexConfisuration() { SomeObject object = ObjectFactory.byStrategy(FilesCollector.class). andSettings("/qwe/", "asd1", "asd2"). andSettings("/wer/", "sdf1", "sdf2", "sdf3"). andSettings("/ert/", "dfg").build(SomeObject.class); assertEquals("[Some object has settings: [/wer/sdf1, /wer/sdf2, /wer/sdf3, /ert/dfg, /qwe/asd1, /qwe/asd2]]", object.toString()); } @Test public void testCheckWithoutConfisuration() { SomeObject object = ObjectFactory.byStrategy(FilesCollector.class).build(SomeObject.class); assertEquals("[Some object has settings: []]", object.toString()); } }
Выглядит вроде как не очень плохо. В реализации немного кривоватее :)
package com.apofig.FilesCollector; public interface GetInstance { <E> E getInstance(Class<E> clazz, Object data); }
package com.apofig.FilesCollector; import java.lang.reflect.InvocationTargetException; public class ObjectFactory { public static <T> T byStrategy(Class<T> strategy) { GetInstance factory = new GetInstance() { public <E> E getInstance(Class<E> clazz, Object data) { Exception exception; try { return (E) clazz.getDeclaredConstructors()[0].newInstance(data); } catch (IllegalArgumentException e) { exception = e; } catch (SecurityException e) { exception = e; } catch (InstantiationException e) { exception = e; } catch (IllegalAccessException e) { exception = e; } catch (InvocationTargetException e) { exception = e; } throw new RuntimeException(exception); } }; Exception exception; try { return strategy.getConstructor(GetInstance.class).newInstance(factory); } catch (InstantiationException e) { exception = e; } catch (IllegalAccessException e) { exception = e; } catch (IllegalArgumentException e) { exception = e; } catch (SecurityException e) { exception = e; } catch (InvocationTargetException e) { exception = e; } catch (NoSuchMethodException e) { exception = e; } throw new RuntimeException(exception); } }
package com.apofig.FilesCollector; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class FilesCollector { private Map<String, List<String>> files = new HashMap<String, List<String>>(); private GetInstance factory; public FilesCollector(GetInstance factory) { this.factory = factory; } public FilesCollector andSettings(String baseDir, String... filesNames) { ... } public <T> T build(Class<T> clazz) { return factory.getInstance(clazz, getPathesList()); } private static <E> List<E> asModifiableList(E... array) { ... } private List<String> getPathesList() { ... } return result; } }
Конечно следующим этапом будет более красивое информирование конечного пользователя о том, что ему надо:
1) в классе стратегии сбора настроек иметь конструктор SomeCollector(GetInstance factory), причем этот билдер использовать для построения объекта.
2) в классе настраиваемого объекта иметь один конструктор с единственным параметром, принимающий то что выдает класс сбора настроек.
А еще есть обин большой недостаток во второй и третьей версии - там где включается рефлексия - все ошибки переносятся на этап выполнения, а значит их сложнее отловить.
А еще хорошая задачка, которую мне не удалось решить - как сделать так:
@Test public void testCheckSimpleConfisuration() { SomeObject object = ObjectFactory.byStrategy(FilesCollector.class). fillClass(SomeObject.class). withSettings("/qwe/", "asd1", "asd2").build(); assertEquals("[Some object has settings: [/qwe/asd1, /qwe/asd2]]", object.toString()); } @Test public void testCheckComplexConfisuration() { SomeObject object = ObjectFactory.byStrategy(FilesCollector.class). fillClass(SomeObject.class). withSettings("/qwe/", "asd1", "asd2"). withSettings("/wer/", "sdf1", "sdf2", "sdf3"). withSettings("/ert/", "dfg").build(); assertEquals("[Some object has settings: [/wer/sdf1, /wer/sdf2, /wer/sdf3, /ert/dfg, /qwe/asd1, /qwe/asd2]]", object.toString()); }Ах да! Самое главное, чтобы дивелопер который руководствуется принципом "а поставлю ка я точку и погляжу что там в списке методов объекта есть" мог выбирать только среди методов заданной стратегии (в примере это FilesCollector), а когда решится собрать объект, то последний в цепочке метод build вернул бы объект заданного в методе fillClass класса (в примере это SomeObject). Причем без всяких ClassCast на стороне клиента, типа
SomeObject object = (SomeObject)ObjectFactory.byStrategy(FilesCollector.class). fillClass(SomeObject.class). withSettings("/qwe/", "asd1", "asd2").build();
Мало ли кому-то пригодится...
Комментариев нет:
Отправить комментарий