Статьи

Встреча пластик: простой ChainBuilder

Это простая реализация шаблона проектирования « Цепочка ответственности» или « Цепочка командования» . У нас уже есть такой сервис в 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. Следует следующие шаги

  1. Интерфейс команды представлен (реализован) вновь созданным классом.
  2. Создается поле для хранения массива команд, которое вводится в новый класс (вставляется в него).
  3. Массив команд вводится в поле
  4. Мы перебираем методы в интерфейсе и создаем цепочку для каждого метода.

В 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/