Фронт-энд / Back-end дизайн: как полностью отделить бэк-энд от фронт-энда? - PullRequest
0 голосов
/ 12 апреля 2011

Мой вопрос: (является ли это выше | что есть) правильный способ создания ненавязчивого интерфейса?

Я объясняю свою проблему на упрощенном примере.

Iу меня есть серверная часть, реализующая бинарное дерево:

// 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 "is-a" Node не является решением, так как мне пришлось бы поместить метод accept () в класс Node каквиртуальный и переопределить его в GNode, но я не могу изменить серверную часть.Тогда кто-то мог бы также сказать, что нет необходимости объявлять accept () в бэк-энде, сработает downcast Node * до GNode *.Да, это работает, но это приводит к снижению ...

В моем случае у меня есть ~ 10 видов узлов (это график), поэтому я ищу что-то элегантное, гибкое, с несколькими строками кода.насколько это возможно (отсюда и идея шаблона оболочки):)

Большое спасибо.

Ответы [ 4 ]

2 голосов
/ 13 апреля 2011

Абсолютно разобщать код невозможно.Они должны говорить.Если вы действительно хотите обеспечить максимальную развязку, следует использовать какой-то механизм IPC / RPC и иметь две разные программы.

Тем не менее, мне не нравятся шаблоны посетителей.

У вас есть графический объект, который связан с объектом поведения.Возможно, существуют правила между поведением и графикой, например, границы не могут перекрываться.

Вы можете делать свои отношения сущностей между графическими объектами и поведением, это логический вопрос бизнеса ...

Вам понадобится некоторый thungus, который содержит ваш контекст рисования (img, screen, buffer)).

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

Ваш графический объект будет иметь наследование или композиционные отношения с поведением.В любом случае, у них будет интерфейс, необходимый для рисования.

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

Если вы обнаружите, что ваш рендеринг становится основанным на регистре, в зависимости от типа графика, я предлагаю перенести эти случаи наГрафика и рефакторинг для получения get_primitives_list(), в котором необходимые примитивы возвращаются для возврата графическому (я предполагаю, что на каком-то уровне у вас есть основные примитивы, линии, круги, дуги, метки и т. Д.).

Я всегда обнаруживал, что ОО-анализ тратит ментальную энергию впустую и должен выполняться только для выполнения поставленной задачи.Ягни - это потрясающий принцип.

1 голос
/ 12 апреля 2011

Если ваш класс-оболочка (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зовет себя.

0 голосов
/ 13 апреля 2011

Я наконец-то нашел «элегантное» решение с шаблоном оформления декоратора. Этот шаблон используется для расширения объекта без изменения его интерфейса.

GNode украшает / расширяет Узел:

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 является узлом . Теперь мы можем создать двоичное дерево GNodes и посетить его благодаря виртуальному методу accept () в серверной структуре.

В случае, когда мы полностью следуем моему вопросу, то есть мы не можем модифицировать бэкэнд и у него нет виртуальной точки входа, представленной выше, мы можем добавить функции в GNode, отображающие узел, который он переносит на себя. Так что посетитель, посещающий GNodes (который может иметь доступ только к своим сыновьям), может найти GNodes своих сыновей. Да, это ключевое слово virtual с указанным выше решением! Но мы никогда не знаем, будет ли кто-то в этом случае по-настоящему.

В качестве заключения ко всему этому: способ выражения проблемы всегда влияет на способ ее решения.

0 голосов
/ 12 апреля 2011

Лично я хотел бы создать класс рисования с перегруженными функциями (по одной для каждого типа узла), а не пытаться подключиться к существующей структуре с помощью какого-то сложного решения наследования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...