0

Я упрощаю вещи, чтобы указать на мою основную проблему с дизайном.Несколько посетителей по смежным классам

У меня есть иерархия, как это:

   R    <-- interface 
      / \ 
     / \ 
     /  \ 
     BR   RR   <-- abstract classes 
    /| \  /| \ 
    /| \ /| \ 
    BRA BRB BRC RRA RRB RRC  <-- classes 

где BRA, BRB, BRC, RRA, RRB и RRC классы, которые необходимо посетить.

У меня также есть два класса посетителей, которые не имеют общего класса предков (на данный момент). Таким образом, в конце концов, код структурирован следующим образом:

public interface R { 
    /* . . . */ 
} 

public abstract class BR implements R {   
    /* . . . */   
    public abstract void accept(VisitorBR vbr); 
} 

public abstract class RR implements R { 
    /* . . . */ 
    public abstract void accept(VisitorRR vrr); 
} 


public class BRA extends BR { 
    /* . . . */ 
    public void accept(VisitorBR vbr) { vbr.visit(this); } 
} 

public class BRB extends BR { 
    /* . . . */ 
    public void accept(VisitorBR vbr) { vbr.visit(this); } 
} 

public class BRC extends BR { 
    /* . . . */ 
    public void accept(VisitorBR vbr) { vbr.visit(this); } 
} 


public class RRA extends RR { 
    /* . . . */ 
    public void accept(VisitorRR vrr) { vrr.visit(this); } 
} 

public class RRB extends RR { 
    /* . . . */ 
    public void accept(VisitorRR vrr) { vrr.visit(this); } 
} 

public class RRC extends RR { 
    /* . . . */ 
    public void accept(VisitorRR vrr) { vrr.visit(this); } 
} 

и

public class VisitorBR { 
    /* . . . */ 
    public void visit(BRA r) { /* . . . */ } 
    public void visit(BRB r) { /* . . . */ } 
    public void visit(BRC r) { /* . . . */ } 
} 

public class VisitorRR { 
    /* . . . */ 
    public void visit(RRA r) { /* . . . */ } 
    public void visit(RRB r) { /* . . . */ } 
    public void visit(RRC r) { /* . . . */ } 
} 

Класс клиент имеет BlockingQueue<R> и одну ссылку на объект каждого класса посетителей, и должен обрабатывать все элементы очереди, используя наиболее подходящего посетителя. Нам нравится это:

public class Client implements Runnable { 
    private VisitorBR vbr; 
    private VisitorRR vrr; 
    private BlockingQueue<R> q; 

    /* . . . */ 

    @Override 
    void run() { 
     for (;;) { 
      R r = q.take(); 

      /*** Somehow handle r with the most suitable visitor, ***/ 
      /*** based on whether it's descendant of BR or RR. ***/ 

     } 
    } 
} 

Что будет самым элегантным решением для этого? Кстати, в любом случае, посетители не должны быть вложенными классами, и я стараюсь избегать использования instanceof.

Мой обходной путь заключается в определении public static final перечислений полей в абстрактных классах BR и RR различать их и использовать if блок, что-то вроде этого:

@Override 
void run() { 
    for (;;) { 
     R r = q.take(); 

     if (r.getType() == BR) 
      ((BR) r).accept(vbr); 
     else // if (r.getType() == RR) 
      ((RR) r).accept(vrr) 
    } 
} 

Но там должно быть более элегантное решение объединить два класса посетителей, чем это.

+0

Я думаю, что вы ищете двойную отправку. См. Https://sourcemaking.com/design_patterns/visitor/java/2. –

+0

@AdiLevin Я знаю об этом источнике, я просто не мог приспособить его к моему делу. – chrk

ответ

0

Вот пример шаблона посетителя. Обратите внимание, что здесь представлен интерфейс Visitor с методами visit() для каждого типа посещенных точек.

public class Demo { 
    public static void main (String [] args) { 
     Point p = new Point2d(1, 2); 
     Visitor v = new Chebyshev(); 
     p.accept(v); 
     System.out.println(p.getMetric()); 
    } 
} 

interface Visitor { 
    public void visit (Point2d p); 
    public void visit (Point3d p); 
} 

abstract class Point { 
    public abstract void accept (Visitor v); 
    private double metric = -1; 
    public double getMetric() { 
     return metric; 
    } 
    public void setMetric (double metric) { 
     this.metric = metric; 
    } 
} 

class Point2d extends Point { 
    public Point2d (double x, double y) { 
     this.x = x; 
     this.y = y; 
    } 

    public void accept (Visitor v) { 
     v.visit(this); 
    } 

    private double x; 
    public double getX() { return x; } 

    private double y; 
    public double getY() { return y; } 
} 

class Point3d extends Point { 
    public Point3d (double x, double y, double z) { 
     this.x = x; 
     this.y = y; 
     this.z = z; 
    } 
    public void accept (Visitor v) { 
     v.visit(this); 
    } 

    private double x; 
    public double getX() { return x; } 

    private double y; 
    public double getY() { return y; } 

    private double z; 
    public double getZ() { return z; } 
} 

class Euclid implements Visitor { 
    public void visit (Point2d p) { 
     p.setMetric(Math.sqrt(p.getX()*p.getX() + p.getY()*p.getY())); 
    } 
    public void visit (Point3d p) { 
     p.setMetric(Math.sqrt(p.getX()*p.getX() + p.getY()*p.getY() + p.getZ()*p.getZ())); 
    } 
} 

class Chebyshev implements Visitor { 
    public void visit (Point2d p) { 
     double ax = Math.abs(p.getX()); 
     double ay = Math.abs(p.getY()); 
     p.setMetric(ax>ay ? ax : ay); 
    } 
    public void visit (Point3d p) { 
     double ax = Math.abs(p.getX()); 
     double ay = Math.abs(p.getY()); 
     double az = Math.abs(p.getZ()); 
     double max = ax>ay ? ax : ay; 
     if (max<az) max = az; 
     p.setMetric(max); 
    } 
} 
+0

Ладно, извините, я должен был упомянуть об этом в моем вопросе. 'instanceof' - это то, чего я пытаюсь избежать здесь, поэтому я использую поля enum (я знаю, это примерно то же самое). Редактирование вопроса, чтобы включить это. – chrk

+0

Добавлено новое решение без 'instanceof' –

+0

Ну, это точно так же, как обходной путь, который я предлагаю, просто использует String вместо enum :) Я искал что-то более элегантное, чем это, если что-то подобное существует. – chrk

1

Хорошо, я думаю, что я понял что лучше сейчас, вдохновил на Selective Visitor Pattern, as described here. Тем не менее, я все еще открыт для элегантных решений.

Определен новый класс SelectiveVisitor, который теперь является единственной ссылкой на класс посетителя, которым владеет Client.Интерфейс R Теперь объявляет дополнительный метод:

public interface R { 
    /* . . . */ 
    public accept(SelectiveVisitor sv); 
} 

Так, BR и RR модифицируются так:

public abstract class BR implements R {   
    /* . . . */ 
    public void accept(SelectiveVisitor sv) { 
     sv.visit(this); 
    } 
    public abstract void accept(VisitorBR vbr); 
} 

public abstract class RR implements R { 
    /* . . . */ 
    public void accept(SelectiveVisitor sv) { 
     sv.visit(this); 
    } 
    public abstract void accept(VisitorRR vrr); 
} 

Новый класс определяется следующим образом:

public class SelectiveVisitor { 
    private VisitorBR vbr; 
    private VisitorRR vrr; 

    public SelectiveVisitor(VisitorBR vbr, VisitorRR vrr) { 
     this.vbr = vbr; 
     this.vrr = vrr; 
    } 

    public void visit(R r) { 
     // this method should never be called in practice 
     // it's here only to satisfy the selective visitor pattern 
     return; 
    } 

    public void visit(BR r) { 
     r.accept(this.vbr); 
    } 

    public void visit(RR r) { 
     r.accept(this.vrr); 
    } 
} 

Client в настоящее время изменения до:

public class Client implements Runnable { 
    private SelectiveVisitor sv; 
    private BlockingQueue<R> q; 

    /* . . . */ 

    @Override 
    void run() { 
     for (;;) { 
      R r = q.take(); 

      r.accept(sv); 
     } 
    } 
} 

Каждый раз, когда вызывается r.accept(selectiveVisitor), то visit метод SelectiveVisitor класса вызывается один из подклассов BR и RR.

visit метод SelectiveVisitor перегружен. Каждый раз, когда он вызывается, наиболее конкретная версия выбирается динамически, поэтому наиболее подходящий посетитель посещает r.


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