You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中类装饰器模式如何处理原类递归方法?

装饰器模式递归调用问题的解决方案

问题描述

使用经典装饰器模式(非TypeScript实验性装饰器特性)扩展Foo类的next方法时,遇到核心问题:Foo的next方法在特定条件下会递归调用自身,但递归时只会执行原方法逻辑,不会经过Bar等装饰器扩展的逻辑。

附原代码:

interface IFoo {
    next(): number;
}

class Foo implements IFoo {
    next(): number {
        console.log("Foo begin");
        // ...
        if (this.abc()) return this.next();
        // ...
        const result = this.xyz();
        console.log("Foo end", result);
        return result;
    }

    protected abc() {
        return Math.random() < 0.5;
    }
    protected xyz(): number {
        return Math.random();
    }
}

class FooDecorator implements IFoo {
    protected wrappee: IFoo;

    constructor(wrappee: IFoo) {
        this.wrappee = wrappee;
    }

    next() {
        return this.wrappee.next();
    }
}

class Bar extends FooDecorator {
    next() {
        console.log("Bar begin");
        const result = this.wrappee.next() + 1;
        console.log("Bar end", result);
        return result;
    }
}


let instance: IFoo = new Foo();
instance = new Bar(instance);

instance.next();

核心原因

Foo类中this.next()的递归调用,其this指向的是原始Foo实例,而非外层被装饰器包裹后的完整实例。装饰器模式通过包裹原实例扩展功能,但原实例内部的this并不会自动指向装饰后的实例,导致递归时跳过装饰逻辑。

修复方案

方案1:修改Foo类,解耦递归调用

调整Foo的内部逻辑,让递归调用依赖一个可替换的引用,而非直接调用自身:

class Foo implements IFoo {
    // 持有当前完整实例的引用,默认指向自身
    protected currentInstance: IFoo;

    constructor() {
        this.currentInstance = this;
    }

    next(): number {
        console.log("Foo begin");
        // ...
        if (this.abc()) return this.currentInstance.next(); // 改用currentInstance调用
        // ...
        const result = this.xyz();
        console.log("Foo end", result);
        return result;
    }

    // 提供方法更新引用,让装饰器可以设置为装饰后的实例
    setCurrentInstance(instance: IFoo) {
        this.currentInstance = instance;
    }

    protected abc() {
        return Math.random() < 0.5;
    }
    protected xyz(): number {
        return Math.random();
    }
}

实例创建时,需要将装饰后的实例回传给Foo:

const foo = new Foo();
const instance: IFoo = new Bar(foo);
foo.setCurrentInstance(instance);

instance.next();

这样递归时就会调用装饰后的next方法。

方案2:使用Proxy代理实现装饰

利用TypeScript的Proxy特性,直接拦截所有方法调用,包括内部递归的this.next():

// 定义Bar装饰器的Proxy实现
const createBarDecorator = (target: IFoo): IFoo => {
    return new Proxy(target, {
        get(target, prop) {
            if (prop === 'next') {
                return () => {
                    console.log("Bar begin");
                    // 调用原方法,Proxy会拦截所有后续调用
                    const result = (target.next as () => number)() + 1;
                    console.log("Bar end", result);
                    return result;
                };
            }
            // 其他属性直接返回原实例的对应值
            return (target as any)[prop];
        }
    });
};

// 使用方式
let instance: IFoo = new Foo();
instance = createBarDecorator(instance);

instance.next();

这种方式无需修改Foo类的任何代码,Proxy会拦截所有对实例方法的访问,包括Foo内部递归调用的this.next(),因为this指向的是Proxy实例。

方案3:重构Foo的递归逻辑

如果允许修改Foo的核心逻辑,可以把递归逻辑抽离成独立方法,让装饰器可以重写该方法:

class Foo implements IFoo {
    next(): number {
        console.log("Foo begin");
        // ...
        if (this.abc()) return this.recursiveNext();
        // ...
        const result = this.xyz();
        console.log("Foo end", result);
        return result;
    }

    // 抽离递归调用的方法,允许装饰器重写
    protected recursiveNext(): number {
        return this.next();
    }

    protected abc() {
        return Math.random() < 0.5;
    }
    protected xyz(): number {
        return Math.random();
    }
}

// 修改Bar装饰器,继承Foo而非FooDecorator(仅当允许装饰器继承原类时适用)
class Bar extends Foo {
    private wrappee: IFoo;

    constructor(wrappee: IFoo) {
        super();
        this.wrappee = wrappee;
    }

    next() {
        console.log("Bar begin");
        const result = this.wrappee.next() + 1;
        console.log("Bar end", result);
        return result;
    }

    // 重写递归方法,调用装饰后的next
    protected recursiveNext(): number {
        return this.next();
    }
}

这种方式需要调整装饰器的继承关系,仅适用于原类允许被继承扩展的场景。

是否需要更换设计模式?

不需要更换装饰器模式,它依然是扩展类功能的合适选择。上述方案都能解决递归调用的问题,其中Proxy方案最简洁且无需修改原类代码;如果不能使用Proxy,修改原类解耦递归调用的方案也能有效解决问题。

内容的提问来源于stack exchange,提问作者user64675

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 15:00:58