Language agnostic 访客与来访者之间的沟通

Language agnostic 访客与来访者之间的沟通,language-agnostic,visitor,Language Agnostic,Visitor,我的当前项目包含一个复杂的对象层次结构。以下结构是该层次结构的简化示例,用于演示: 图书馆 “小说”类 “科幻小说”类 书A(每本书都包含页面,此处不显示) B册 “犯罪”类别 C册 “非虚构”类 (许多子类别) 现在,每当我需要数据结构中的一些信息时,我希望避免在代码中出现嵌套循环,因为当结构更改时,我必须更新所有循环 所以我计划使用访问者模式,这似乎给了我所需要的灵活性。它看起来像这样: 类库 { 无效接受(ILibraryVisitor) { 迭代分类(this.c

我的当前项目包含一个复杂的对象层次结构。以下结构是该层次结构的简化示例,用于演示:

  • 图书馆
    • “小说”类
      • “科幻小说”类
        • 书A(每本书都包含页面,此处不显示)
        • B册
      • “犯罪”类别
        • C册
    • “非虚构”类
      • (许多子类别)
现在,每当我需要数据结构中的一些信息时,我希望避免在代码中出现嵌套循环,因为当结构更改时,我必须更新所有循环

所以我计划使用访问者模式,这似乎给了我所需要的灵活性。它看起来像这样:

类库
{
无效接受(ILibraryVisitor)
{
迭代分类(this.categories,visitor);
}
无效迭代范畴(
IEnumerable categorySequence,
图书馆(访客)
{
foreach(类别序列中的var类别)
{
visitor.VisitCategory(category.Name);
迭代分类(category.Subcategories,visitor);
foreach(分类中的var帐簿。帐簿)
{
//也可以通过一本书的实例,还不确定。。。
visitor.VisitBook(书.标题,书.作者,书.出版日期);
foreach(book.Pages中的变量页)
{
visitor.VisitPage(页码、页码、内容);
}
}
}
}
}
界面ILibraryVisitor
{
作废VisitCategory(字符串名称);
作废VisitBook(字符串标题、字符串作者、DateTime publishingDate);
无效访问页(整数页码、字符串内容);
}
我已经看到一些可能的问题,所以我希望你能给我一些建议

问题1 如果我想创建一个以其所属(子)类别为前缀的书名列表(例如小说»科幻»图书a),一个简单的访问者实现似乎可以做到这一点:

//LibraryVisitor是一个基本实现,没有op方法
类BookListingVisitor:LibraryVisitor
{
私有堆栈categoryStack=新堆栈();
无效VisitCategory(字符串名称)
{
this.categoryStack.Push(名称);
}
//其他方法
}
在这里,我已经遇到了一个问题:我不知道何时弹出堆栈,因为我不知道类别何时结束。将VisitCategory方法分为以下两种方法是常见的方法吗

接口ILibraryVisitor
{
作废VisitCategoryStart(字符串名称);
void VisitCategoryEnd();
//其他方法
}
或者有没有其他方法来处理这样的结构,它们有一个清晰的范围,有一个开始和结束

问题2 假设我只想列出1982年出版的书。decorator访问者会将过滤与列表逻辑分开:

1982年出版的图书类:图书馆访客
{
私人图书馆访客;
1982年出版的公共图书(图书馆访客)
{
this.visitor=访客;
}
作废VisitBook(字符串标题、字符串作者、DateTime publishingDate)
{
如果(出版日期.年份==1982)
{
this.visitor.VisitBook(字符串标题、字符串作者、发布日期);
}
}
//简单委托给this.visitor的其他方法
}
这里的问题是,对于1982年未出版的书籍,VisitPage仍将被调用。因此,装饰者需要与访问的对象进行通信:

参观者:“嘿,这本书不是1982年的,所以请不要告诉我任何关于它的事。” 图书馆:“哦,好吧,那我就不给你看它的书页了。”

访问方法当前返回void。我可以将其更改为返回一个布尔值,指示是否访问子项,但这感觉有点脏。是否有让来访者知道应该跳过某些项目的常规做法?或者我应该研究一种不同的设计模式


另外,如果你认为这应该是两个独立的问题,请告诉我,我很乐意将它们分开。

访客模式,如GoF书所述,处理的是类层次结构,而不是对象层次结构。简单地说,添加一个新的访问者类型就像向基类和所有子类添加一个新的虚拟函数,而不涉及它们的代码

访问者的机制包括层次结构中每个类的一个
Visitor::Visit
函数,以及父类和所有子类中的
Accept
函数。它的工作原理是通过父类引用调用
Accept(visitor)
。它完全正交于根类的不同子类的实例之间可能存在的任何对象层次结构

在您的情况下,
ILibraryVisitor
界面将有一个
VisitLibrary(Library)
方法、一个
VisitCategory(Category)
方法、一个
VisitBook(Book)
方法等等,而
Library
Category
中的每一个,
Book
等将继承一个公共基类并重新实现其
Accept(ILibraryVisitor)
方法

到目前为止还不错。但从这一点上看,您的实现似乎有点迷失方向。访问者不会调用自己的访问函数!层次结构的成员确实需要,访问者为了他们的利益而实现这些功能。那么,我们如何沿着分类树走下去呢

请记住,调用
Accept(FooVisitor)
将替换层次结构根中的方法
Foo
class LibraryVisitor : ILibraryVisitor
{
  IterateChildren (List<ILibraryObject> objects) {
    foreach obj in objects {
      obj.Accept(this);
    }
  }
  IterateSubcategories (Category cat) {
    stack.push (cat);                 # we need a stack here to build a path
    IterateChildren (cat.children);   # both books and subcategories
    stack.pop();
  }
  VisitLibrary (Library) = abstract
  VisitCategory (Category) = abstract
  VisitBook (page) = abstract
  VisitPage (Page) = abstract
}

class MyLibraryVisitor : LibraryVisitor {
  VisitLibrary (Library l ) { ... IterateChildren (categories) ... }
  VisitCategory (Category c) = { ... IterateSubcategories (c) ... }
  VisitBook (Book) = { ... IterateChildren (pages) ... }
  VisitPage (Page) = { ... no children here, end of walk ... }
}