9

在54:53 分钟的演讲中,Rich Hickey 谈到了使用队列作为解耦依赖程序部分的一种手段。你能给我举个例子,说明如何将以下 Java 伪代码解耦以改进其设计和/或灵活性:

// Warning: Java-pseudo-code ahead
class Job {
    public void doRun(A a) {
        saveObjectToDatabase(a);

        B b = computeB(a);
        saveObjectToDatabase(b);

        C c = computeC(b);
        logToFile(c);
    }
}

saveObjectToDatabaseandsaveObjectToDatabase可以看作是一种有副作用的方法,而computeB's 和computeC' 的输出只依赖于a.

我知道这个问题相当模糊/广泛。我想了解如何利用排队机制而不会使我的程序变得非常复杂,并且仍然确保它以正确的顺序做正确的事情。任何指向正确方向的指针都值得赞赏。

4

4 回答 4

3

好吧,这不是一个很好的例子,但是(在最直接的设计中)您基本上有两个队列,并且(取决于所涉及的数据量)您可能会省略数据库。

第一个进程将从a“外部世界”接收您的对象并将它们排入队列 1。第二个进程将从队列 1 中取出对象,执行computeB并将结果排入队列 2。第三个进程将从队列 2 中取出对象,执行computeC,并记录结果或其他内容。

正如我所说,取决于所涉及的数据量(可能还有其他一些因素),队列中传递的“对象”可能是您的实际对象ab也可能只是用于在数据库中查找数据的令牌/键。

队列本身可以通过多种方式实现。可以使用数据库实现队列,例如,尽管细节有点混乱。“进程”可以是单个 Java 进程中的 Java 任务,也可以是单独的 OS 进程,甚至可能在不同的机器上。

当您在 Unix 上使用“管道”时,您实际上是以这种方式使用队列。

于 2012-01-04T12:41:13.900 回答
1

这正是我正在使用的 java 库所使用的原理。这个想法是将组件分配给程序中的各个任务(记录器就是一个很好的例子)。现在每个组件都需要独立于其他组件运行,要么作为线程要么作为事件处理程序。

在事件驱动的情况下,每个组件都会通知他想要监听哪些类型的事件\消息。您有一个调度程序,它收集传入的消息并将它们插入接收者的队列中。接收者进程,并最终产生新的消息。等等...

在你的情况下,是这样的:

class SaveObjectHandler{
//
void handle(Event e, Object o){
  if(e instanceof SaveEvent)
      saveObjectToDatabase(o);
}

};

class TransformObject{
//
 void handle(Event e,Object o){
   if(e instanceof TransformEvent){
      B result = compute(o);
      send(new SaveEvent(),result)
   }

 }

};

class Logger{

   void handle(Event e, Object o){
      if(o instanceof B)
        //perform computeC
        logEvent((B)o);
   }

};

};

有问题的图书馆是SEDA

于 2012-01-04T12:46:38.350 回答
0

我担心 saveObject 方法有副作用,你不能很好地解耦它,或者至少不容易解耦。

但是假设您需要快速写入数据库一些对象。我的观点是,使用关系数据库最快的方法应该是由多个客户端将对象保存到一个队列中,而不是由一两个非常快速的写入者尽可能快地将数据推送到数据库中来获取它们。

于 2012-01-04T17:27:05.413 回答
0

为了完整起见,我想在 Hot Licks 的回答中添加更多信息:

我一直在对这个主题进行更多研究,最后得出结论,解开方法是要走的路。我将使用生产者/消费者/主题的 kafka 术语。有关更多信息,请参阅日志:每个软件工程师都应该了解实时数据的统一抽象,尤其是下图:

在此处输入图像描述

关于我发布的示例的具体问题,有两种方法可以解决:

解决方案 1

  • 消费者1:
    • 从主题消费a
    • 保存到数据库。
  • 消费者 2:
    • 从主题消费a
    • 计算b
    • 保存到数据库。
  • 消费者 3:从主题消费a
    • 计算b
    • 计算c
    • 保存到数据库

这有计算b 两次的缺点。在伪代码中:

class ConsumerA  {
    public void consume(A a) {
        saveObjectToDatabase(a);
    }
}

class ConsumerB  {
    public void consume(A a) {
        B b = computeB(a);
        saveObjectToDatabase(b);
    }
}

class ConsumerLog  {
    public void consume(A a) {
        B b = computeB(a);
        C c = computeC(b);
        logToFile(c);
    }
}

解决方案 2

  • 消费者1:
    • 从主题消费a
    • 保存到数据库。
  • 消费者 2:
    • 从主题消费a
    • 计算b,保存到数据库
    • 发布b到单独的主题b
  • 消费者 3:
    • 从主题消费b
    • 计算c
    • 日志文件c

在伪代码中:

class ConsumerA  {
    public void consume(A a) {
        saveObjectToDatabase(a);
    }
}

class ConsumerB  {
    public void consume(A a) {
        B b = computeB(a);
        saveObjectToDatabase(b);
        publish(b); // republish computed information to another topic b
    }
}

class ConsumerLog  {
    public void consume(B b) {
        C c = computeC(b);
        logToFile(c);
    }
}
于 2016-05-17T15:39:45.140 回答