Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

вторник, 15 февраля 2011 г.

Шаблоны: Object configurator

Как-то раз мне захотелось сделать так
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();

Мало ли кому-то пригодится...

Комментариев нет:

Отправить комментарий