为了澄清,我使用的是https://refactoring.guru/design-patterns/decorator中的decorator 模式/方法,而不是TypeScript中的实验decorator 特性.

我正在try 在我的主类Foo上扩展next方法. 有几个功能由不同的类实现,如BarBaz...所以我觉得装饰者的模式就可以了.然而,问题是原始的next方法可以在某些条件下调用自己.当它发生时,它当然调用原始next而不是decorator next.什么是正确的方法呢?我应该使用不同的模式吗?

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();

TS Playground

推荐答案

不幸的是,你遇到了一个基本的缺点,如你的源代码中所描述的,但很少强调(与Proxy相同的问题):"装饰"只有当它从"外部"调用时才会生效.当实例引用自身时(到this,或者递归地,就像你的例子,或者通过另一个方法),它绕过了任何应用的修改(修饰或代理).

例如,如果你已经"修饰"了abc()方法并调用了next(),则修饰将永远不会执行,因为abc()是从within实例(在其next()方法内部)调用的.


话虽如此,您仍然可以实现添加额外的行为的目的,这些行为总是有效的(即,即使方法递归地调用自己,或者被同一实例的另一个方法调用):由于JavaScript中对象的可变性,包括它们的方法,所以很容易"交换"它们.但是我们需要手动保留对原始方法的引用,因为在这种情况下没有"超级"sugar:

const instance: IFoo = new Foo();

// JavaScript object methods are mutable
const originalNext = instance.next.bind(instance); // Keep a reference to the "super" method
instance.next = function () {
    console.log("Custom begin");
    const result = originalNext() + 1;
    console.log("Custom end", result);
    return result;
}

instance.next();

还有一种更"TypeScript"的方法来实现这一点:使用类Mixins

function DecorateNext<BC extends GConstructor<{ next(): number }>>(BaseClass: BC) {
    return class DecoratedNext extends BaseClass {
        next() {
            console.log("Mixin begin");
            const result = super.next() + 1; // Here we can use "super" to refer to the base class behavior
            console.log("Mixin end", result);
            return result;
        }
    }
}

const instance2 = new (DecorateNext(Foo))();

instance2.next();

... GConstructor:

// For TypeScript mixin pattern
type Constructor = new (...args: any[]) => {};
type GConstructor<T = {}> = new (...args: any[]) => T;

然后,您可以应用连续的Mixin来根据需要添加更多的行为.

Playground Link

Typescript相关问答推荐

如何使用axios在前端显示错误体

TS不推断类型在条件判断后具有属性'

我们过go 不能从TypeScript中的联合中同时访问所有属性?

如果存在ts—mock—imports,我们是否需要typescpt中的IoC容器

如何正确地对类型脚本泛型进行限制

在实现自定义主题后,Angular 不会更新视图

使用2个泛型类型参数透明地处理函数中的联合类型

路由链接始终返回到

TypeError:正文不可用-NextJS服务器操作POST

在保留类型的同时进行深层键名转换

正确使用相交类型的打字集

打字错误推断

为什么特定的字符串不符合包括该字符串的枚举?

如何使用TypeScrip在EXPO路由链路组件中使用动态路由?

TypeScrip:如何缩小具有联合类型属性的对象

在类型脚本中创建显式不安全的私有类成员访问函数

仅当类型为联合类型时映射属性类型

通过辅助函数获取嵌套属性时保留类型

如何从联合类型推断函数参数?

如何使用 Angular 确定给定值是否与应用程序中特定单选按钮的值匹配?