访问者模式:访问同一个结构的不同方式

Visitor Pattern: different ways to visit the same structure

我有这种table结构:

public class Table : IVisitable
{
    public List<Row> rows;
    public void accept(IVisitor visitor)
    {
        foreach(Row row in rows)
            row.accept(visitor);
        visitor.visit(this);
    }
}

public class Row : IVisitable
{
    public List<Cell> columns; 
    public void accept(IVisitor visitor)
    {
        foreach(Cell cell in columns)
            cell.accept(visitor);
        visitor.visit(this);
    }
}
public class Cell : IVisitable
{
    public void accept(IVisitor visitor)
    {visitor.visit(this);}
}

解析器将创建这个 classes 的对象,所以我有点手忙脚乱地扩展 classes 或类似的东西(但我可以在很好解决方案)。

现在,你可以看到我实现的accept()方法访问整个table,但现在我想定义一个访问者仅 table 的前 2 行。我如何在不添加另一个 accept() 的情况下做到这一点?

更新: 我考虑过这个解决方案,我想听听您的意见。如果不重写上面的整个代码,只需想象每个 class 都没有实现 IVisitable 接口(因此每个 class 都没有 accept() )。创建两个扩展 Table 的 class 怎么样?像这样:

public class VisitTwoRowTable : Table,IVisitable
{
    public VisitTwoRowTable(Table table)
    {
        foreach(Row row in table.rows)
              this.rows.add(row);
    }        
    public void accept(IVisitor visitor)
    {
        for(int i=0;i<2;i++)
            row[i].accept(visitor);
        visitor.visit(this);
    }
}

还有这个:

public class VisitWholeTable : Table,IVisitable
{
    public VisitWholeTable(Table table)
    {
        foreach(Row row in table.rows)
              this.rows.add(row);
    }
    public void accept(IVisitor visitor)
    {
        foreach(Row row in rows)
            row.accept(visitor);
        visitor.visit(this);
    }
}

这个解决方案唯一非常丑陋的地方是构造函数部分,它创建了一个副本(浅拷贝)

你可以移动

foreach (Row row in rows)
    row.accept(visitor);

进入visitor.Visit(table)方法

然后在不同的访问者中提供不同的实现方式,例如在您的新访客中将其更改为

foreach (Row row in rows.Take(2))
    row.accept(visitor);

(您还必须在 Table class 上创建行 public。)

您可以简单地为您的访问者添加一个状态并跟踪您已经访问过的行数。

鉴于这些类型(为简洁起见,我省略了 Cell):

public interface IVisitor
{
    void visit(Row v);
    void visit(Table v);
}

public interface IVisitable
{
    void accept(IVisitor visitor);
}

public class Table : IVisitable
{
    public List<Row> rows;
    public void accept(IVisitor visitor)
    {
        foreach(Row row in rows)
            row.accept(visitor);
        visitor.visit(this);
    }
}

public class Row : IVisitable
{
    public int number;
    public void accept(IVisitor visitor)
    {
        visitor.visit(this);
    }
}

您的访客可能看起来像:

public class FirstTwoRowVisitor : IVisitor
{
    int _numOfRows = 0;

    public void visit(Row r)    
    { 
        if (_numOfRows == 2)
            return;
        Console.WriteLine("Visited Row #{0}", r.number);
        _numOfRows++;
    }

    public void visit(Table t)  
    {
        Console.WriteLine("Table has {0} Rows total", t.rows.Count);
    }
}

示例:

var t = new Table() { rows = new List<Row>() };
int i = 0;
t.rows.Add(new Row() {number = i++});
t.rows.Add(new Row() {number = i++});
t.rows.Add(new Row() {number = i++});
t.rows.Add(new Row() {number = i++});
var v = new FirstTwoRowVisitor();
t.accept(v);

会输出

Visited Row #0
Visited Row #1
Table has 4 Rows total

(这当然只是一个简单的例子,每个实例只能处理两行;但您可以根据需要进行更改)


回复您的评论:

public interface IVisitor
{
    void visit(Row v);
    void visit(Table v);
    bool keepgoing { get; }
}

public class Table : IVisitable
{
    public List<Row> rows;
    public void accept(IVisitor visitor)
    {
        foreach(Row row in rows)
            if (visitor.keepgoing) row.accept(visitor);
            else break;
        visitor.visit(this);
    }
}

public class FirstTwoRowVisitor : IVisitor
{
    int _numOfRows = 0;

    public bool keepgoing { get { return _numOfRows < 2; } }

    public void visit(Row r)    
    { 
        if (!keepgoing)
            return;
        Console.WriteLine("Visited Row #{0}", r.number);
        _numOfRows++;
    }

    public void visit(Table t)  
    {
        Console.WriteLine("Table has {0} Rows total", t.rows.Count);
    }
}