簡體   English   中英

前端/后端設計:如何將后端與前端完全分離?

[英]Front-end/Back-end design: how to absolutely dissociate the back-end from the front-end?

我的問題是:(上述|是什么)創建非侵入式前端的正確方法?

我用一個簡化的例子來解釋我的問題。

我有一個實現二叉樹的后端:

// Back-end
struct Node
{
  Label label;
  Node* r, l;
};

我現在想實現前端以圖形方式打印樹。 所以我的想法是通過包裝后端來擴展圖形屬性:

// Front-end
struct Drawable
{
  uint x, y;
};

class Visitor;
template <class T> struct GNode : public Drawable
{
  T* wrapped;
  template <class V> void accept(V& v); // v.visit(*this);
}

現在存在創建訪問者以打印二叉樹的問題:

struct Visitor
{
    void visit(GNode<Node>& n)
    {
      // print the label and a circle around it: ok.

      if (n.wrapped.l) // l is a Node, not a GNode, I can't use the visitor on it
        // Problem: how to call this visitor on the node's left child?

      // the same with n.wrapped.r
    };
};

如評論中所述,后端不使用我的擴展類。

編寫GNode“是”節點也不是解決方案,因為我必須將Node類中的accept()方法設置為虛擬,並在GNode中覆蓋它,但是我無法修改后端。 然后,有人也可以說不需要在后端聲明accept(),將Node *向下轉換為GNode *即可。 是的,它可以工作,但是很沮喪……

就我而言,我有約10種節點(它是一個圖形),因此我正在尋找一種優雅,靈活的東西,並使用盡可能少的代碼行(因此有包裝模板的想法):)

非常感謝你。

完全分離代碼是不可能的。 他們必須說話。 如果您確實要最大程度地實施去耦,則應使用某種IPC / RPC機制,並且該機制具有兩個不同的程序。

話雖如此-我不喜歡訪客模式。

您有一個圖形對象,該對象與行為對象鏈接。 行為和圖形之間可能存在規則,例如邊界不能重疊。

您可以在圖形和行為之間建立實體關系,這是一個業務邏輯問題...

您將需要一些保存您的繪圖上下文(img,屏幕,緩沖區)的thungus。

class DrawingThungus { 
  void queue_for_render(Graphical*);
  void render();
};

圖形用戶將與行為具有繼承關系或構圖關系。 無論如何,他們將具有進行繪圖所需的界面。

//abstract base class class Graphical  {   
  get_x();  
  get_y();  
  get_icon(); 
  get_whatever(); 
};

如果您發現您的Render依賴於Graphical的類型而成為基於案例的案例,我建議將案例推到Graphical上,並重構為具有get_primitives_list() ,其中將所需的原語返回給Graphical以返回(I假設您在某種程度上擁有核心圖元,直線,圓,圓弧,標簽等)。

我一直發現,OO分析很容易浪費精力,應該僅對即將完成的任務進行足夠的分析。 YAGNI是一個偉大的原則。

如果您的包裝器類(GNode)不必在每次訪問時都保持任何狀態(即,它只有一個字段-包裝的Node對象),則可以使用指針或對包裝對象的引用來代替副本,並且那么您將能夠在運行時包裝任何節點。

但是,即使您確實維護了狀態(x,y坐標),您是否真的只是從包裝的對象中推斷出了狀態? 在這種情況下,將訪問的類與推斷的數據分開會更好嗎? 例如,考慮以下實現:

// This is an adapter pattern, so you might want to call it VisitorAdapter if you
// like naming classes after patterns.
template typename<T>
class VisitorAcceptor
{
private:
    T& wrapped;
public:
    VisitorAcceptor(T& obj)
    {
        wrapped = obj;
    }

    template <typename VisitorT>
    void accept(VisitorT& v)
    {
        v.visit(wrapped);
    }
};

struct GNode
{
    uint x, y;
    shared_ptr<GNode> l,r; // use your favourite smart pointer here

    template <typename VisitorT>
    void accept(VisitorT& v)
}

// You don't have to call a visitor implementation 'Visitor'. It's better to name
// it according to its function, which is, I guess, calculating X,Y coordinates.
{
    shared_ptr<GNode> visit(Node& n)
    {
        shared_ptr<GNode> gnode = new GNode;
        // calculate x,y
        gnode->x = ...
        gnode->y = ...

        if (n.l)
            gnode->l = VisitorAdapter(n.r).accept(*this);
        if (n.r)
            gnode->r = VisitorAdapter(n.l).accept(*this);
    };
};

Now you can have a different visitor for drawing:

struct GNodeDrawer
{
    void visit(GNode& gnode)
    {
        // print the label and a circle around it: ok.

        if (n.r)
            visit(n.l);
        if (n.r)
            visit(n.r);
    };
};

當然,如果不需要訪問者模式提供的所有可擴展性,則可以將其完全丟棄,然后使用XYCalculator.visit調用自身以遞歸方式遍歷樹。

就我個人而言,我將使用重載函數(每個節點類型一個)來制作一個繪圖類,而不是嘗試使用某種復雜的繼承解決方案來插入現有結構。

我終於找到了帶有裝飾器設計模式的“優雅”解決方案。 此模式用於擴展對象而不更改其接口。

GNode 裝飾/擴展 Node:

template <class T> struct GNode : public T, public Drawable
{
  virtual void accept(Visitor& v); // override Node::accept()
}

如您所見,它需要對后端結構進行一些更改:

struct Node
{
  Label label;
  Node* r, l;
  virtual void accept(Visitor& v);
};

而已 ! GNode 是一個節點。 現在,由於后端結構中的虛擬方法accept(),我們可以創建GNode的二叉樹並對其進行訪問。

在我們絕對遵循我的問題的情況下,即我們無法修改后端並且沒有上面提供的虛擬入口點,我們可以向GNode添加功能,以將其包裝的Node映射到自身。 這樣訪問GNode的訪問者(只能訪問其子節點)可以找到其子的GNode。 是的,這是上述解決方案的虛擬關鍵字作業! 但是我們永遠不知道在這種情況下是否有人會是真實的。

作為所有這些的結論:表達問題的方式始終會影響解決問題的方式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM