Самый главный объект в 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()
Как-то так.
Может кому пригодится...
Комментариев нет:
Отправить комментарий