我当前的项目包含一个复杂的对象层次结构。以下结构是此层次结构的简化示例,用于演示目的:
- 图书馆
- “小说”类别
- 分类“科幻”
- 书A (每本书包含页数,此处不显示)
- 书 B
- 类别“犯罪”
- C书
- 类别“非小说”
- (许多子类别)
现在,每当我需要数据结构中的一些信息时,我想避免在我的代码中使用嵌套循环,因为当结构发生变化时,我必须更新所有循环。
所以我计划使用访问者模式,这似乎给了我所需的灵活性。它看起来像这样:
class Library
{
void Accept(ILibraryVisitor visitor)
{
IterateCategories(this.categories, visitor);
}
void IterateCategories(
IEnumerable<Category> categorySequence,
ILibraryVisitor visitor)
{
foreach (var category in categorySequence)
{
visitor.VisitCategory(category.Name);
IterateCategories(category.Subcategories, visitor);
foreach (var book in category.Books)
{
// Could also pass in a book instance, not sure about that yet...
visitor.VisitBook(book.Title, book.Author, book.PublishingDate);
foreach (var page in book.Pages)
{
visitor.VisitPage(page.Number, page.Content);
}
}
}
}
}
interface ILibraryVisitor
{
void VisitCategory(string name);
void VisitBook(string title, string author, DateTime publishingDate);
void VisitPage(int pageNumber, string content);
}
我已经看到了一些可能的问题,所以我希望你能给我一些建议。
问题 1
如果我想创建一个以它所属的(子)类别为前缀的书名列表(例如Fiction » Science Fiction » Book A),一个简单的访问者实现似乎可以解决问题:
// LibraryVisitor is a base implementation with no-op methods
class BookListingVisitor : LibraryVisitor
{
private Stack<string> categoryStack = new Stack<string>();
void VisitCategory(string name)
{
this.categoryStack.Push(name);
}
// Other methods
}
这里我已经遇到了一个问题:我不知道什么时候出栈,因为我不知道一个类别什么时候结束。将 VisitCategory 方法拆分为两种方法是否是一种常用方法,如下所示?
interface ILibraryVisitor
{
void VisitCategoryStart(string name);
void VisitCategoryEnd();
// Other methods
}
或者有没有其他方法来处理这样的结构,它们有一个明确的范围和开始和结束?
问题2
假设我只想列出 1982 年出版的书籍。装饰器访问者会将过滤与列表逻辑分开:
class BooksPublishedIn1982 : LibraryVisitor
{
private ILibraryVisitor visitor;
public BooksPublishedIn1982(ILibraryVisitor visitor)
{
this.visitor = visitor;
}
void VisitBook(string title, string author, DateTime publishingDate)
{
if (publishingDate.Year == 1982)
{
this.visitor.VisitBook(string title, string author, publishingDate);
}
}
// Other methods that simply delegate to this.visitor
}
这里的问题是,对于 1982 年未出版的书籍,仍然会调用 VisitPage。所以装饰器需要以某种方式与访问的对象进行通信:
访客:“嘿,这本书不是 1982 年的,所以请不要告诉我任何关于它的事情。”
图书馆:“哦,好吧,那我就不给你看它的页面了。”
访问方法当前返回 void。我可以将其更改为返回一个布尔值,指示是否访问子项目,但这感觉有点脏。是否有让访问者知道它应该跳过某些项目的常见做法?或者也许我应该研究一种不同的设计模式?
PS如果您认为这应该是两个独立的问题,请告诉我,我很乐意将它们分开。