Java: ограничение изменения объекта определенным методом

В настоящее время я пытаюсь создать библиотеку передачи сообщений, и одним из принципов передачи сообщений является то, что изменяемое состояние изменяется только посредством сообщений. «Сообщения», которые будут передаваться, - это функциональные объекты, которые принимают «отправителя» (создавшего сообщение) и «получателя» (работника / исполнителя / того, что вы обрабатываете сообщение из его очереди).

Рабочие определяются следующим образом, и используется самореферентный характер интерфейса, поскольку рабочий может иметь состояние, которое он хочет раскрыть отправителям сообщений, и для этого отправитель должен знать свой уникальный тип.

public interface Worker<T extends Worker<T>> {
    T getWorker(); //convert a Worker<T> to it's <T> since T extends Worker<T>
    <S extends Worker<S>> void message(Message<S,T> msg);
}

где <S> - тип исполнителя, отправившего сообщение.

Сообщения определяются следующим образом:

public interface Message<S extends Worker<S>,R extends Worker<R>> {
    void process(S sender, R thisWorker);
}

где <S> - это тип класса отправителя, а <R> - тип класса обработчика. Как ожидается от интерфейса и механизма передачи сообщений, функция сможет изменять состояние thisWorker. Однако, если бы сообщение напрямую изменяло sender, возникло бы состояние гонки, потому что нет синхронизации между рабочими / потоками (в этом весь смысл использования сообщений для начала!)

Хотя я мог бы объявить <S> общим Worker<?>, это нарушило бы способность сообщения «отвечать» отправителю осмысленным образом, поскольку оно больше не может ссылаться на свои конкретные поля.

Следовательно, как я могу гарантировать, что sender не будет изменен, кроме как в контексте сообщения, адресованного именно ему?

T extends Worker<T> действительно используется для предотвращения возврата классом типа, не являющегося рабочим. Я, вероятно, мог бы обойтись только Worker<T> и доверять пользователю.

То, что я специально пытаюсь достичь, - это система передачи сообщений, в которой сообщения представляют собой код, что позволило бы избежать необходимости какой-либо специальной обработки разных типов сообщений в каждом работнике. Однако я, вероятно, понимаю, что нет элегантного способа сделать это, не придерживаясь какого-либо протокола сообщений, если бы я хотел его принудительно применить, например, с выполнением в Swing EDT.

Вам будет лучше объяснять, чего вы пытаетесь достичь, чем давать фрагменты кода и объяснять их.

user1803551 06.09.2018 07:44
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
189
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не совсем понимаю ваш замысел. Какова именно цель рекурсивных обобщений в определениях интерфейсов, таких как interface Worker<T extends Worker<T>>? Разве interface Worker<T> не хватило бы, по-прежнему позволяя реализации T getWorker() получить конкретный тип класса, реализующего интерфейс Worker? Это попытка ограничить реализацию интерфейса возвратом чего-либо, кроме Worker, из getWorker()? В этом случае, я думаю, interface Worker<T extends Worker<?>> внесет меньше путаницы.

Therefore, how can I ensure that sender won't be modified except in the context of a message addressed specifically to it?

Насколько мне известно, единственный способ предотвратить вызов методов объекта вне желаемого контекста - это ограничить выполнение методов объекта одним потоком, недоступным для клиентского кода. Это потребует от объекта проверки текущего потока в первую очередь в каждом отдельном методе. Ниже приведен простой пример. По сути, это стандартная реализация производителя / потребителя, в которой объект, который модифицируется (нет, Mailbox / реализация потребителя!) Предотвращает изменения самого себя в потоках, отличных от указанного потока (того, который потребляет входящие сообщения).

class Mailbox {

    private final BlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<>();

    private final Thread mReceiverThread = new Thread(() -> {
        while (true) {
            try {
                Runnable job = mQueue.take();
                job.run();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    public Mailbox() {
        mReceiverThread.start();
    }

    public void submitMsg(Runnable msg) {
        try {
            mQueue.put(msg);
        } catch (InterruptedException e) {
            // TODO exception handling; rethrow wrapped in unchecked exception here in order to allow for use of lambdas.
            throw new RuntimeException(e);
        }
    }

    public void ensureMailboxThread(Object target) {
        if (Thread.currentThread() != mReceiverThread) {
            throw new RuntimeException("operations on " + target + " are confined to thread " + mReceiverThread);
        }
    }
}

class A {

    // Example use
    public static void main(String[] args) {
        Mailbox a1Mailbox = new Mailbox();
        Mailbox a2Mailbox = new Mailbox();
        A a1 = new A(a1Mailbox);
        A a2 = new A(a2Mailbox);
        // Let's send something to a1 and have it send something to a2 if a certain condition is met.
        // Notice that there is no explicit sender:
        // If you wish to reply, you "hardcode" the reply to what you consider the sender in the Runnable's run().
        a1Mailbox.submitMsg(() -> {
            if (a1.calculateSomething() > 3.0) {
                a2Mailbox.submitMsg(() -> a2.doSomething());
            } else {
                a1.doSomething();
            }
        });
    }

    private final Mailbox mAssociatedMailbox;

    public A(Mailbox mailbox) {
        mAssociatedMailbox = mailbox;
    }

    public double calculateSomething() {
        mAssociatedMailbox.ensureMailboxThread(this);
        return 3 + .14;
    }

    public void doSomething() {
        mAssociatedMailbox.ensureMailboxThread(this);
        System.out.println("hello");
    }

}

Позвольте мне повторить подчеркнутую часть: каждый отдельный класс, участвующий в передаче сообщений, должен проверять текущий поток в начале каждого отдельного метода. Невозможно извлечь эту проверку в универсальный класс / интерфейс, поскольку метод объекта может быть вызван из любого потока. Например, Runnable, представленный с использованием submitMsg в приведенном выше примере, мог выбирает создание нового потока и пытается изменить объект в этом потоке:

a1Mailbox.submitMsg(() -> {
    Thread t  = new Thread(() -> a1.doSomething());
    t.start();
});

Однако этого можно было бы предотвратить, но только потому, что проверка является частью самого A.doSomething().

Рекурсивные универсальные шаблоны предназначены для предотвращения того, чтобы <T> был типом, не являющимся рабочим. Однако я, вероятно, мог бы оставить его как Worker<T>, поскольку его истинная цель - разрешить сообщениям попадать в определенное состояние некоторого рабочего экземпляра, поскольку, реализуя интерфейс, мы уже знаем, что это Worker. Конечно, я не мог помешать людям создавать Worker <String>, но в любом случае это не имело бы большого смысла.

Kitten 06.09.2018 14:52

Что касается потоковой передачи, то после прочтения вашего ответа, скорее всего, не будет никакого способа гарантировать желаемый результат, если не нужно просто доверять пользователю, чтобы он не делал глупостей. В конце концов, я не думаю, что пользовательский интерфейс Swing проверяет, все ли вызовы происходят в EDT.

Kitten 06.09.2018 14:56

Другие вопросы по теме