Как-то раз мне захотелось сделать так
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();
Мало ли кому-то пригодится...