[英]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.