Слабосвязанный шаблон наблюдателя

Я понимаю, что эта тема была покрыта до смерти, но я все еще борюсь и мог бы сделать с некоторой конкретной помощью.

Моя цель — реализовать простой шаблон наблюдателя между каким-то наблюдаемым (скажем, собакой) и каким-то слушателем (скажем, владельцем).

В конце концов, Owner станет «представлением», а Dog — «моделью» в парадигме MVC. Я использую Dog и Owner только для того, чтобы попытаться упростить ситуацию.

Я попытался использовать встроенные в Java классы Observer/Observable, но понял, насколько плох метод update() Observers - он получает POJO, и мне нужно будет проверить/привести этот POJO в методе update(). Я бы предпочел, чтобы мой метод update() получал то, что он может ожидать.

Итак, я следовал нескольким руководствам, в том числе этому, в котором в качестве примера используется собака/владелец:

http://www.youtube.com/watch?v=qw0zZAte66A

Здесь мне показали, как свернуть мои собственные классы Observer/Observed. В псевдокоде у меня сейчас есть следующее:

Dog/Model {

    List listeners;

    public fireDogHungryEvent() {

        foreach listener {
            listener.onDogHungry(this);
        }
    }

    public fireDogPeeEvent() {

        foreach listener {
            listener.onDogNeedsToPee(this);
        }
    }

    public getHungerLevel() { return hungerLevel; }
    public getBladderCapacity() { return bladderCapacity; }
}

Owner/View {

    public onDogHungry(model) {
        println(model.getHungerLevel());
    }

    public onDogNeedsToPee(model) {
        println(model.getBladderCapacity());
    }
}

Итак, теперь вместо одного метода update() у меня есть методы, обрабатывающие определенные события. Блестящий. В настоящее время я доволен классом Owner/view. Он знает о методах Собаки/модели, и это нормально (я думаю).

Что мне не нравится, так это то, что Dog/model имеет ссылки на методы в Owner/view. Я читал бесчисленное количество раз и полностью согласен с тем, что модель не должна быть тесно связана со своими взглядами, как это кажется выше. Я также не заинтересован в том, чтобы все «огненные» методы в Dog/model делали почти одно и то же; перебирая всех своих слушателей и просто вызывая разные методы для каждого слушателя.

Можно ли еще больше отделить эту связь и не использовать специальные методы вызова Dog/model? Если да, то как лучше всего получить данные о собаке/модели в поле «Владелец/представление» и правильно с ними работать?

Спасибо


person whoshotdk    schedule 11.05.2013    source источник
comment
Какие методы Dog имеют ссылки на представление? Я ничего не вижу.   -  person SJuan76    schedule 11.05.2013
comment
Лучше всего использовать интерфейс, который предлагает видеоурок. Является ли слушатель интерфейсом в вашей реализации?   -  person Kevin Bowersox    schedule 11.05.2013
comment
SJuan76 - ссылки, о которых я говорю, - это вызовы listener.onDogHungry и listener.onDogNeedsToPee в методах 'fire' Dog.   -  person whoshotdk    schedule 11.05.2013
comment
Кевин - слушатель на самом деле является интерфейсом, как и предполагает учебник. Однако в конце урока у меня все еще остались вызовы методов слушателя из наблюдаемых методов.   -  person whoshotdk    schedule 11.05.2013


Ответы (2)


Вы должны interface отказаться от знаний о конкретной реализации как у Observer, так и у Observable

public enum EventType {

    HUNGRY,
    PEE;
}

public interface DogEvent {

    EventType getType();
}

public interface DogListener {

    void fireEvent(DogEvent event);
}

public class Dog {

    private final Set<DogListener> listeners = new CopyOnWriteArraySet<DogListener>();

    public void register(final DogListener dogListener) {
        listeners.add(dogListener);
    }

    public void unregister(final DogListener dogListener) {
        listeners.remove(dogListener);
    }

    public void firePeeEvent() {
        fireEvent(new DogEvent() {
            @Override
            public EventType getType() {
                return EventType.PEE;
            }
        });
    }

    public void fireHungryEvent() {
        fireEvent(new DogEvent() {
            @Override
            public EventType getType() {
                return EventType.HUNGRY;
            }
        });
    }

    private void fireEvent(final DogEvent dogEvent) {
        for (final DogListener listener : listeners) {
            listener.fireEvent(dogEvent);
        }
    }
}

public class Owner implements DogListener {

    @Override
    public void fireEvent(DogEvent event) {
        switch (event.getType()) {
            case PEE:
                System.out.println("Someone take the dog out");
                break;
            case HUNGRY:
                System.out.println("I can't believe the dog is hungry _again_!");
                break;
        }
    }
}

В этом случае Dog не знает о реализации Owner, он просто знает, что Owner является DogListener.

С другой стороны, Owner не знает о Dog, он просто знает, что у него есть входящий DogEvent.

person Boris the Spider    schedule 11.05.2013
comment
Мне нравится этот ответ за его ясность, спасибо, Борис. Мне особенно не нравится оператор case, который должен проверять, какое событие произошло, но я думаю, что это не происходит просто по волшебству: DI попробует это с парой наблюдаемых/слушателей и посмотрит как я поживаю. Сегодня я приму этот ответ, если смогу придумать что-нибудь достаточно эффективное! - person whoshotdk; 11.05.2013
comment
Ах, я только что понял, что это гораздо лучшая реализация того, что я пробовал ранее в своих попытках; за исключением того, что я использовал отражение для вызова методов на основе имени строки вместо объекта события. Я думаю, это сработает :D - person whoshotdk; 11.05.2013
comment
enum было просто предложением — вы могли бы использовать шаблон посетителя, чтобы полностью использовать полиморфизм. - person Boris the Spider; 11.05.2013

Для целей MVC (особенно для MVP) я накопил хороший опыт программирования баз событий. Таким образом, вам понадобится EventBus, который будет использоваться собакой и владельцем. Владелец подпишется на определенные классы событий, такие как HungerLevelIncrease, и собака будет запускать такие события. Для вашего примера с владельцем собаки это было бы довольно странно, потому что владелец не знал своих собак, но для программирования с графическим интерфейсом это хорошее и простое решение для разделения вещей, особенно между другими контроллерами.

Вы можете легко создать автобус самостоятельно или использовать его из google guava< /а>.

Это лучший известный мне способ создания действительно слабой связи.

person mszalbach    schedule 11.05.2013
comment
@ user2373021 вы смотрели на что-то вроде mbassador - это основано на аннотациях, поэтому все, что вам нужно, это аннотировать своих слушателей и он выведет, что они хотят слушать, из параметров метода. Он также может отправлять сообщения асинхронно... - person Boris the Spider; 11.05.2013
comment
@user2373021 user2373021 это правда, что вам нужно добавить автобус ко всем классам, которые в нем нуждаются. Однако в большинстве случаев вы все равно будете добавлять контейнер для внедрения зависимостей в более крупные приложения, которые могут внедрить для вас eventBus. Но добавление новых типов событий никак не должно повлиять на вашу шину. Если у вас есть только несколько классов, я бы тоже выбрал простое решение для прослушивания, но если вы получаете больше диалогов и имеете несколько диалогов, разговаривайте друг с другом и с другими классами (например, с сервером). Я бы попробовал механизм EventBus. - person mszalbach; 11.05.2013