流程同步

根據使用者在畫面之間的互動流程,將畫面與基礎模型同步。

2004 年 11 月 15 日

這是 進階企業應用程式架構開發 的一部分,我在 2000 年代中期撰寫。很遺憾,從那以後,有太多其他事情吸引了我的注意力,所以我沒有時間進一步研究它們,我也看不到未來會有太多時間。因此,這份文件仍處於草稿階段,直到我找到時間再次研究它,我不會進行任何更正或更新。

應用程式通常包含顯示相同資料的多個畫面。如果在一個畫面中編輯資料,則必須更新其他畫面以反映變更。

流程同步 透過讓畫面程式碼在畫面的工作流程中適當的點明確重新同步來執行此動作。

運作方式

流程同步 的說明非常簡單。基本上,每次您執行會變更跨多個畫面共用的狀態的動作時,您會告訴每個畫面更新自身。問題在於,這通常表示每個畫面都與應用程式中的其他畫面有些關聯。如果您有很多畫面以非常開放的方式運作,這可能會很糟糕,這是 觀察者同步 如此強大的替代方案的原因。

因此,流程同步 更適合簡單的導覽樣式。

一種常見的樣式是根與子樣式。在這裡,您有一個在整個應用程式中開啟的根視窗。當您想要更詳細的資訊時,您會開啟一個子視窗,通常是模態的。在這種情況下,很容易使用 流程同步,您需要做的就是每當您關閉子視窗時更新根視窗。如果您在根畫面中執行此動作,則子視窗不需要知道根視窗。

如果子視窗是非模態的,這可能會變得更困難。如果您遵循僅在子視窗關閉時更新資料(因此也更新根視窗)的方法,這很簡單。如果您想要在子視窗仍開啟時進行更新,那麼您正在朝向 觀察者同步 的領域前進。如果資料在子視窗之間共用,這也會有問題。

另一個簡單的樣式是精靈樣式,其中您依序擁有數個畫面。使用精靈時,每個畫面都是模態,從一個畫面移動到另一個畫面涉及關閉舊畫面並開啟另一個畫面。因此,在這種情況下,流程同步很容易執行:在關閉畫面時更新資料,並在開啟下一個畫面時載入最新資料。您可以使用根畫面,其中關閉最後一個子畫面會更新根畫面,來建立模態子畫面的精靈順序。

何時使用

流程同步觀察者同步 的替代方案,用於使用網域資料同步畫面。在許多方面,這是一種更明確且直接的執行方式。您不必使用難以查看和除錯的隱含 觀察者 關係,而是可以在程式碼中清楚地配置明確的同步呼叫。

流程同步 的問題在於,一旦您擁有大量可能共用資料的畫面時,事情可能會變得非常混亂。任何畫面的任何變更都需要觸發其他畫面更新。透過對其他畫面的明確呼叫來執行此操作,會讓畫面非常相互依賴。這是 觀察者同步 容易得多的原因。

儘管有其限制,流程同步 確實有其一席之地。只要使用者介面的導覽流程很簡單,而且一次只有一個或兩個畫面處於活動狀態,它就能發揮良好的作用。此類情況的範例包括畫面順序(例如精靈)和具有模態子畫面的根畫面。 觀察者同步 也適用於這些情況,但您可能會發現更明確的方法較為理想。網頁使用者介面實際上是一連串的畫面,因此可以有效地使用 流程同步,這是客戶端/伺服器連線的協定讓 觀察者同步 非常難以執行的範例。

範例:根和子餐廳清單(Java)

在範例中,我有一些簡單的畫面。根目錄是餐廳清單。若要編輯清單,請按一下餐廳項目以編輯詳細資料。如果您變更名稱,則清單需要自行更新,包括名稱內容和維持字母順序。

餐廳類別很無聊,我不會在此重複說明。僅有對應於 UI 欄位和從屬 getter 和 setter 的字串欄位。這不是一個重要的類別,但這不是本次討論的重點,所以我允許它可悲、可憐的存在。

用於編輯餐廳詳細資料的表單也很簡單。我有一個單一的全域更新偵聽器,我將其附加到每個欄位,因此欄位中的任何變更都會導致基礎網域物件和表單的全球更新 - 這是粗略同步。

class RestaurentForm…

  public class FieldListener extends GlobalListener {
     void update() {
         save();
         load();
     }
  }
  private void load() {
      nameField.setText(model.getName());
      placeField.setText(model.getPlace());
      favoritesField.setText(model.getFavorites());
      directionsField.setText(model.getDirections());
  }
   private void save() {
       model.setName(nameField.getText());
       model.setPlace(placeField.getText());
       model.setFavorites(favoritesField.getText());
       model.setDirections(directionsField.getText());
   }

餐廳清單需要在兩個時間點更新,在表單的初始建立時和每當子視窗關閉時。我將更新清單的程式碼放入一個方法中。

class RestaurentListForm...

  void load() {
      sortModel();
      list.setListData(getRestaurentNames());
  }

  private String[] getRestaurentNames() {
      String[] result = new String[model.size()];
      int i = 0;
      for (Restaurent r : model) result[i++] = r.getName();
      return result;
  }

  private void sortModel() {
      Collections.sort(model, new Comparator<Restaurent>() {
          public int compare(Restaurent r1, Restaurent r2) {
              return r1.getName().compareTo(r2.getName());
          }
      });
  }

然後,我從表單的建構函式和附加到編輯按鈕的動作偵聽器呼叫它。

class RestaurentListForm...

  public RestaurentListForm(List<Restaurent> model) {
      this.model = model;
      buildForm();
      load();
      window.pack();
      window.setVisible(true);
  }
private class EditActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        new RestaurentForm(window, selectedRestaurent());
        load();
    }
}

與自訂清單模型比較

此方法的一個明顯替代方法是使用自訂清單模型。上述實作使用內建清單模型,該模型會從我的餐廳清單複製資料。複製的替代方法是提供我自己的清單模型實作,這只是一個包裝在基礎餐廳清單上的包裝器。

private class RestaurentListModel extends AbstractListModel {
    private List<Restaurent> data;
    public RestaurentListModel(List<Restaurent> data) {
        this.data = data;
    }
    public int getSize() {
        return data.size();
    }
    public Object getElementAt(int index) {
        return data.get(index).getName();
    }
}

現在,我在餐廳清單上建立這個包裝器,並在建立清單小工具時提供給它。這樣,清單使用基礎餐廳清單作為其模型,而不是單獨的字串清單。我對餐廳名稱所做的任何變更都會自動傳播到畫面,因此我不必撰寫或呼叫載入方法。

一個更棘手的問題是,清單的排序順序該怎麼處理。如果排序順序對網域類別來說有意義,那麼我只要重新排序餐廳清單,就可以在變更名稱時,簡單地對其進行排序。然而,排序順序通常是特定於螢幕的,因為不同的螢幕可能會有不同的排序順序。在這種情況下,觸發排序方案可能會造成更多問題。另一種方法是在每次要求時對清單進行排序,但這可能會導致使用者介面沒有回應,特別是如果清單很大。