访问者模式和责任链模式

285 阅读3分钟

我们这里实现一个在线markdown编辑器,可以把一些特定的基础markdown语法映射为对应的HTML标签。仓库代码地址在这里

下面谈谈具体代码

责任链决定用哪个标签

这种模式适合回答这样一个问题:我应该处理这个标签吗?如果是否定的,就把这个往下传递。像链表一样将一系列处理流程链接在一起。

具体来说,责任链的代码可以构造成这样:

abstract class Handler<T> {
    protected next : Handler<T> | null = null;
    // 指定类链中下一个类
    public SetNext(next : Handler<T>) : void {
        this.next = next;
    }

    public HandleRequest(request : T) : void {
        if (!this.CanHandle(request)) {
            if (this.next != null) {
                this.next.HandleRequest(request);
            }
            return;
        }
    }

    protected abstract CanHandle(request : T) : boolean;
}

我们可以使用一个类来实现基类CanHandle的抽象方法:

class ParseChainHandler extends Handler<ParseElement> {
    private readonly visitable : IVisitable = new Visitable();
    protected CanHandle(request: ParseElement): boolean {
        let split = new LineParser().Parse(request.CurrentLine, this.tagType);
        if (split[0]){
            request.CurrentLine = split[1];
            this.visitable.Accept(this.visitor, request, this.document);
        }
        return split[0];
    }
    constructor(private readonly document : IMarkdownDocument, 
        private readonly tagType : string, 
        private readonly visitor : IVisitor) {
        super();
    }
}

在这个类上可以派生出很多不同的具体功能的类出来,当然也可以使用工厂模式来去生成不同功能的类:

class Header1ChainHandler extends ParseChainHandler {
    constructor(document : IMarkdownDocument) {
        super(document, "# ", new Header1Visitor());
    }
}

class Header2ChainHandler extends ParseChainHandler {
    constructor(document : IMarkdownDocument) {
        super(document, "## ", new Header2Visitor());
    }
}

最后我们再把这些派生类拼装一下,做出一条这样的职责链出来:

class ChainOfResponsibilityFactory {
    Build(document : IMarkdownDocument) : ParseChainHandler {
        let header1 : Header1ChainHandler = new Header1ChainHandler(document);
        let header2 : Header2ChainHandler = new Header2ChainHandler(document);
        let header3 : Header3ChainHandler = new Header3ChainHandler(document);
        let horizontalRule : HorizontalRuleHandler = new HorizontalRuleHandler(document);
        const quoteRuleHandler: QuoteRuleHandler = new QuoteRuleHandler(document)
        let paragraph : ParagraphHandler = new ParagraphHandler(document);

        header1.SetNext(header2);
        header2.SetNext(header3);
        header3.SetNext(horizontalRule);
        horizontalRule.SetNext(quoteRuleHandler);
        quoteRuleHandler.SetNext(paragraph)

        return header1;
    }
}

访问者模式

访问者模式建议将新行为放入一个名为访问者的独立类中, 而不是试图将其整合到已有类中。 现在, 需要执行操作的原始对象将作为参数被传递给访问者中的方法, 让方法能访问对象所包含的一切必要数据。

也就是说我们不需要修改原有的代码,而是重新构造一个新的类称为访问者,新功能都在访问者里面,由他去拜访原有的功能,实现灵活扩展。

举个现实中的例子,比如说你会接到卖保险的电话,他根据你自身特点向你推销保险产品,也可能会接到卖理财产品的电话,他根据你自身特点,向你推销理财产品。我们本身没有变化,但是通过访问者的不同,实际上对我们自身能力实现了灵活的扩展。

对于我们的markdown编辑器,访问者可以这样设计,当发生访问行为的时候,从被访问者身上获取一些数据Visit,操作一些行为:

interface IVisitor {
    Visit(token : ParseElement, markdownDocument : IMarkdownDocument) : void;
}

abstract class VisitorBase implements IVisitor {
    constructor (private readonly tagType : TagType, private readonly TagTypeToHtml : TagTypeToHtml) {}
    Visit(token: ParseElement, markdownDocument: IMarkdownDocument): void {
        // 添加一段html
        markdownDocument.Add(this.TagTypeToHtml.OpeningTag(this.tagType), token.CurrentLine, this.TagTypeToHtml.ClosingTag(this.tagType));
    }
}

class Header1Visitor extends VisitorBase {
    constructor() {
        super(TagType.Header1, new TagTypeToHtml());
    }
}

被访问者可以这样设计,里面有一个Accept方法,把对应数据或者信息交给访问者:

interface IVisitable {
    Accept(visitor : IVisitor, token : ParseElement, markdownDocument : IMarkdownDocument) : void;
}

class Visitable implements IVisitable {
    Accept(visitor: IVisitor, token: ParseElement, markdownDocument: IMarkdownDocument): void {
        visitor.Visit(token, markdownDocument);
    }
}