Самый главный объект в Bomberman - это доска, на которой происходит вся игра. Доска имеет ряд public методов, один из которых - дай мне все бомбы на поле. В первой версии он был простой как двери и потому небезопсный.
Допустим, синхронными должны быть не только таймеры в бомбах, но и сам список бомб, который вернули из board. Дело в том, что спустя несколько тактов могу на руках иметь список, якобы содержащий бомбы, но на карте уже будет совсем другая история.
Любопытно, как это у меня получится!
Да, совсем забыл, я тут усложняю специально. На самом деле никому из клиентов игры (а это я сам) не нужен немодифицируемый список, и все то лишнее что я тут пишу не привносит пользы, кроме как одной - я получаю опыт, мне интересно и я экспериментирую со сложностями.
Итак, мне надо вернуть List но необычный, а синхронный с оригинальным. Хехех :) Но в начале тест, которй скажет что все ок, когда я это сделаю...
Суть нового функционала в том, что я могу взять любой объект и встроить в него шпиона, который будет делать полезные для меня действия. AOP только без спринга. Напомню критикам, что я всего лишь играюсь в код... Не воспринимайте это серьензо.
Как-то так.
Может кому пригодится...
public List<Bomb> getBombs() {
return bombs;
}
Вернуть копию списка - не вариант, поскольку, получив список на руки, я как клиент смогу воздействовать на бомбы непосредственно, меняя ход истории.
public List<Bomb> getBombs() {
return ListUtils.unmodifiableList(bombs); // из apache commons collections
}
Тогда стоит итерироваться по всему списку и пересоздавать все бомбочки
public List<Bomb> getBombs() {
List<Bomb> result = new LinkedList<Bomb>();
for (Bomb bomb : super.getBombs()) {
result.add(new Bomb(bomb));
}
return result;
}
Но тут другая задача. Те бомбы, что я скопирую рассинхронизированы с игрой - у них таймер замер сразу после копирования.
public Bomb(Bomb bomb) {
this.power = bomb.power;
this.x = bomb.x;
this.y = bomb.y;
this.timer = bomb.timer;
this.affect = null; // бомба - муляж
}
Копирующий конструктор мне в помощь! Он кроме того, что копирует необходимые свойства, еще и подписывает копии бомб на события, которые получает оригинальная бомба. Для этого был создан наследник, чтобы не влазить в работающий Bomb.
public class BombCopier extends Bomb {
private List<Tickable> copies;
public BombCopier(int x, int y, int power) {
super(x, y, power);
copies= new LinkedList<Tickable>();
}
public BombCopier(Bomb bomb) {
this(bomb.x, bomb.y, bomb.power);
this.affect = bomb.affect;
this.timer = bomb.timer;
if (bomb instanceof BombCopier) {
BombCopier copier = (BombCopier)bomb;
copier.copies.add(this);
this.affect = null; // бомба - муляж
}
}
public void tick() {
for (Tickable bomb : copies) {
bomb.tick();
}
super.tick();
}
}
Теперь еще усложним себе жизнь!Допустим, синхронными должны быть не только таймеры в бомбах, но и сам список бомб, который вернули из board. Дело в том, что спустя несколько тактов могу на руках иметь список, якобы содержащий бомбы, но на карте уже будет совсем другая история.
Любопытно, как это у меня получится!
Да, совсем забыл, я тут усложняю специально. На самом деле никому из клиентов игры (а это я сам) не нужен немодифицируемый список, и все то лишнее что я тут пишу не привносит пользы, кроме как одной - я получаю опыт, мне интересно и я экспериментирую со сложностями.
Итак, мне надо вернуть List но необычный, а синхронный с оригинальным. Хехех :) Но в начале тест, которй скажет что все ок, когда я это сделаю...
@Test
public void shouldReturnShouldSynchronizedBombsList_whenUseBoardApi() {
bomberman.bomb();
bomberman.right();
board.tact();
List<Bomb> bombs1 = board.getBombs();
assertEquals(1, bombs1.size());
board.tact();
board.tact();
board.tact();
board.tact();
List<Bomb> bombs2 = board.getBombs();
assertEquals(0, bombs2.size());
assertEquals(0, bombs1.size());
}
А вот и реализация
public List<Bomb> getBombs() {
return ListUtils.getUnmodifiableList(new ListUtils.ListFactory() {
@Override
public List create() {
List<Bomb> result = new LinkedList<Bomb>();
for (Bomb bomb : getSuperBombs()) {
result.add(new BombCopier(bomb));
}
return result;
}
});
}
При этом пришлось откопать свои старые залежи и немного порефлексировать с ними, расширив ProxyFactory, который я давным давно писал. В результате теперь я имею возможность делать так
public class ListUtils {
public interface ListFactory {
Object create();
}
public static List<Bomb> getUnmodifiableList(final ListFactory factory) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = factory.create();
Object invoke = method(method.getName()).withParameterTypes(method.getParameterTypes()).in(result).invoke(args);
return ProxyFactory.resultBuilder().dontCallRealMethod().returns(invoke).get();
}
};
return ProxyFactory.object(factory.create()).spy(handler).getAs(List.class);
}
}
Короче поигрался в свое удовольствие. Старая версия описана тут, а новую версию ProxyFactory можно скачать отсюда (а вот и репозиторий).Суть нового функционала в том, что я могу взять любой объект и встроить в него шпиона, который будет делать полезные для меня действия. AOP только без спринга. Напомню критикам, что я всего лишь играюсь в код... Не воспринимайте это серьензо.
interface Some {
String method(String input);
}
class SomeImpl implements Some {
public String method(String input) {
return "SomeImpl say: " + input;
}
public String method2(String input) {
return "SomeImpl say from another method: " + input;
}
}
@Test
public void shouldCanUseAfterRealMethod() throws Throwable {
final ProxyFactory.After after = new ProxyFactory.After() {
public Object doit(Object result) {
return result + "_zxc";
}
};
InvocationHandler before = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return ProxyFactory.resultBuilder().with("asd_" + args[0]).doAfter(after).get();
}
};
Some impl2 = ProxyFactory.object(impl).spy(before).getAs(Some.class);
assertEquals("SomeImpl say: asd_qwe_zxc", impl2.method("qwe"));
}
Тут ProxyFactory.resultBuilder() можно использоваться несколькими способами.
ProxyFactory.resultBuilder().with("new args").returns("new answer").get()
сразу после handler будет вызван исходный метод объекта но с новым аргументом "new args", а после его выполнения результат подменится на "new answer".
ProxyFactory.resultBuilder().get()Ничего не изменился - сразу после отработки handler будет вызван исходный метод с исходными артументами, а его результат вернется клиенту.
ProxyFactory.resultBuilder().call("method2").with("new args").get()
Сразу после handler будет вызван другой (method2) метод объекта, за которыми шпионим, с другими аргмуентами "new args", а результат выполнения уйдет клиенту.
ProxyFactory.resultBuilder().call("method2").get()
Сразу после handler будет вызван другой (method2) метод объекта, за которыми шпионим, с другими исходными аргументами, а результат выполнения уйдет клиенту.
ProxyFactory.resultBuilder().returns("new answer").get()
Ничего не трогаем, переопределяем только результат "new answer"
ProxyFactory.resultBuilder().dontCallRealMethod().returns("new answer").get()
Говорим, чтобы выполнялся только handler и клиенту подсунем новый результат "new answer"ProxyFactory.resultBuilder().after(new ProxyFactory.After() {
public Object doit(Object result) {
return result; // do smth
}
}).get()
Говорим, что после выполнения исходного метода надо вызвать наш Answer.doit() Как-то так.
Может кому пригодится...

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