Это простая реализация шаблона проектирования « Цепочка ответственности» или « Цепочка командования» . У нас уже есть такой сервис в TapestryIOC, и я подумал о том, чтобы внедрить его в Plastic.
В шаблоне проектирования Chain of Command мы создаем единый сервис из набора команд, которые реализуют общий интерфейс. Этот шаблон может быть предоставлен из коробки с использованием преобразований класса. Собранный сервис будет запускать все команды в данной последовательности, если ни одна из команд не выдаст исключение. Я придерживаюсь простого примера, заставляя методы в интерфейсе команд возвращать только void.
Начнем с интерфейса ChainBuilder.
/**
* A "Chain Of Responsibility" or "Chain Of Commands" pattern
*/
public interface ChainBuilder {
/**
* Builds a chain instance from a given chain of commands implementing a particular
* interface
* @param <T>
* @param comamndInterface interface type of the command
* @param commands list of commands
* @return chain instance
*/
<T> T build(Class<T> commandInterface, List<T> commands);
}
а теперь его реализация
/**
* Implementation of a ChainBuilder interface using Plastic
*/
public class ChainBuilderImpl implements ChainBuilder {
private PlasticManager pm;
/**
* Constructor
* @param pm plastic manager
*/
public ChainBuilderImpl(PlasticManager pm) {
this.pm = pm;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <T> T build(Class<T> commandInterface, List<T> commands) {
// Create a new class implementing this interface
return (T) pm.createClass(Object.class,
new ChainBuilderTransformer<T>(commandInterface, commands)).newInstance();
}
static public class ChainBuilderTransformer<T> implements PlasticClassTransformer {
private final Class<T> commandInterface;
private final List<T> commands;
public ChainBuilderTransformer(Class<T> commandInterface, List<T> commands) {
this.commandInterface = commandInterface;
this.commands = commands;
}
public void transform(PlasticClass pc) {
// Implement the interface
pc.introduceInterface(commandInterface);
// Add a field which will be an array containing the commands
final Object[] arrayOfCommands = commands.toArray();
final PlasticField arrayOfCommandsField = pc.introduceField(
Object[].class, "_commands$"
+ commandInterface.getSimpleName());
arrayOfCommandsField.inject(arrayOfCommands);
// For each method create chain
for (Method method : commandInterface.getMethods()) {
createChain(pc, arrayOfCommandsField, method);
}
}
private void createChain(final PlasticClass pc,
final PlasticField arrayOfCommandsField,
final Method method) {
pc.introduceMethod(method).changeImplementation(
new InstructionBuilderCallback() {
public void doBuild(InstructionBuilder builder) {
builder.loadThis().getField(arrayOfCommandsField)
.iterateArray(new InstructionBuilderCallback() {
public void doBuild(InstructionBuilder builder) {
builder.loadArguments().invoke(method);
return;
}
});
builder.returnDefaultValue();
}
});
}
}
}
Служба создает класс, используя PlasticManager.createClass, используя Object.class в качестве базового класса и предоставляя ему ChainBuilderTransformer, который является реализацией PlasticClassTransformer.
ChainBuilderTransformer реализует единственный метод transform, присутствующий в PlasticClassTransformer. Следует следующие шаги
- Интерфейс команды представлен (реализован) вновь созданным классом.
- Создается поле для хранения массива команд, которое вводится в новый класс (вставляется в него).
- Массив команд вводится в поле
- Мы перебираем методы в интерфейсе и создаем цепочку для каждого метода.
В createChain мы заменяем код этого метода, вызывая changeImplementation, который принимает InstructionBuidlerCallback в качестве аргумента, который имеет единственный метод doBuild, имеющий InstructionBuilder в качестве аргумента.
Теперь самое сложное (помните, что машины стеков и язык ассемблера не прошли даром!). Что нам нужно сделать здесь
void myAssembledCommand(arguments){
for(T command: commands){
command(arguments);
}
}
Для выполнения цикла необходимо, чтобы массив команд находился на вершине стека. Чтобы получить поле экземпляра поверх стека, вы должны поместить экземпляр в стек, а затем вызвать getField (). Мы делаем это с помощью builder.loadThis (). GetField (). Как только мы получим массив команд на вершине стека, мы используем iterateArray для итерации по массиву. iterateArray принимает InstructionBuilderCallback в качестве аргумента, который позволяет вам генерировать код внутри цикла. В этом методе doBuild () все, что мы делаем, это вызываем указание сборщику загрузить аргументы основного метода из стека и вызвать команду с этими аргументами. Вот и все !! мы сделали.
использование
Спок тест для использования вышеупомянутого ChainBuilder как под
/**
* Tests {@link plasticdemo.transforms.ChainBuilderImpl}
*/
class ChainBuilderTest extends Specification {
def pm
def setup(){
pm = PlasticManager.withContextClassLoader().delegate(new StandardDelegate(new RunTransformer())).
packages(["plasticdemo.controlled"]).create();
}
def "test if foo is runnable"(){
setup:
def chainBuilder = new ChainBuilderImpl(pm)
MyService service1 = Mock(MyService)
MyService service2 = Mock(MyService)
def chain = chainBuilder.build(MyService, [service1, service2])
when:
chain.process()
then:
1 * service1.process()
1 * service2.process()
}
}
Полный исходный код вместе с примерами из других постов можно найти здесь .
Во время реализации сервиса я наткнулся на пластиковую реализацию ChainBuilder, которая уже реализована в Tapestry Trunk . Он заботится о возвращаемом значении, что очень легко. Вы можете следить за всеми пластическими изменениями в ядре Tapestry5 здесь.
От http://tawus.wordpress.com/2011/04/19/plastic-chains-simple-chainbuilder/