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




Я не совсем понимаю ваш замысел. Какова именно цель рекурсивных обобщений в определениях интерфейсов, таких как 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
senderwon'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>, но в любом случае это не имело бы большого смысла.
Что касается потоковой передачи, то после прочтения вашего ответа, скорее всего, не будет никакого способа гарантировать желаемый результат, если не нужно просто доверять пользователю, чтобы он не делал глупостей. В конце концов, я не думаю, что пользовательский интерфейс Swing проверяет, все ли вызовы происходят в EDT.
Вам будет лучше объяснять, чего вы пытаетесь достичь, чем давать фрагменты кода и объяснять их.