【问题标题】:Conditionally extend the current class有条件地扩展当前类
【发布时间】:2020-09-04 23:28:31
【问题描述】:

我想要一个 JavaScript 类,它可以有条件地从单独的文件中将其他方法添加到自身中。这个想法是将应用程序的不同关注点分成更易于管理的独立模块,这些模块仍然可以与母应用程序类中的方法进行交互。因此,单独文件中的附加方法必须能够引用主类中的方法和变量。请参阅下面的代码示例。

我查看了很多不同的解决方案,但它们都有我想要的缺点。

  • 我可以做new Uploads(),但我找不到Uploads.js 中的方法引用主App 类中的方法的方法
  • 我可以使用 extends,但这不允许有条件扩展 AFAIK
  • I could just define new methods into the prototype itself,但这意味着外部文件需要“知道”它将被使用的类,这不会使其可广泛重复使用。

目前为止我最好的如下:

app.js

const Uploads = require("./Uploads.js");
const config = { hasUploads: true }; // Probably loaded from a file

class App {
    constructor() {
        /* Only include the separate module if the config says so */
        if(config.hasUploads) {
            Object.assign(this, Uploads);
        }
    }

    foo() {
        /* Something */
    }
}

上传.js

module.exports = {
    bar() {
        this.foo();
    }
};

有效,但不知道这是不是最好的解决方案;

  • 没有构造函数,所以如果Uploads.js 需要做一些设置,app.js 需要包含这样做的逻辑(或者至少知道调用一些唯一命名的假构造函数方法),这似乎并不理想.
  • 如果Uploads.js 包含与app.js 或任何其他可能正在加载的模块同名的方法,它们将被覆盖,从而导致意外行为。
  • Uploads.js 是函数对象,而app.js 定义一个类。 理想情况下(虽然我猜不一定)为了代码可管理性,它们都应该使用相同的语法。

有没有更好/更清洁/更好的方法来做到这一点?

【问题讨论】:

  • 这有点违反了许多面向对象的原则,但我会让 OO 学者对此表示赞同。实际上,我通常会使用虚拟或抽象的 Uploads 实现(什么都不做)来定义基类,然后您可以从该类派生并在派生类中提供该方法的新实现,或者您可以有条件地替换现有的在任何给定实例上实现。
  • 在多个文件中拆分一个巨大的class 是行不通的。正如你自己所说,你想要 self-contained 模块。
  • "我可以做new Uploads(),但我找不到Uploads.js 中的方法引用主App 类中的方法的方法" - 通过App 实例作为上传构造函数的参数:this.uploads = new Uploads(this);(在 Appconstructor 内)。
  • 我可以完全接受我的方法可能不是应该做的......但我确实认为想要将一个巨大的类拆分成更小的更易于管理的文件的根本问题在于本身就是一个有效的问题。如果我理解正确,从虚拟类派生也意味着最终“完整”类最终将是 Uploads,而不是 App,对吗?最终应用需要调用的任何类都会根据配置而改变?
  • 使用 JS“原型”。请参阅几个先前回答的问题,例如stackoverflow.com/questions/13521833/…

标签: javascript node.js ecmascript-6 es6-class


【解决方案1】:

与其尝试执行某种疯狂的多重继承,不如尝试拥抱组合?它非常适合解决这类问题。

class App {
    
    constructor(modules) {
      if (modules.uploads) {
        this.uploads = modules.uploads(this);
      }
    }
    
    foo() {
        console.log('foo!');
    }
}

class Uploads {
  constructor(context) {
    this.context = context;
  }
  
  method() {
    this.context.foo();
  }
}

const app = new App({ uploads: (ctx) => new Uploads(ctx) });
app.uploads.method();

您可以对此非常感兴趣,并使用构建器来配置具有特定类型模块的应用程序。

根据您预期的复杂性,您可能需要考虑使用event busesmediatorscommands 将事物与主机本身分离。

【讨论】:

  • 我想得越多,我认为这就是这样做的方法。我之前无法上传到 App 中的引用方法,但经过一些修补后,我让它在我的代码中工作。谢谢!
  • 甚至是聚合。
【解决方案2】:

修复从上传文件覆盖现有方法的一个选项是在循环中分配新方法并检查重复项(Object.assign 在这种情况下并不理想)并且只添加一次更新:

const Uploads = {
    bar() {
         this.foo("called from bar");
    }
};

const config = { hasUploads: true, // Probably loaded from a file
                 configured: false
};

class App {
    constructor() {
        /* Only include the separate module if the config says so */
        if(config.hasUploads && !config.configured) {
             const proto = this.constructor.prototype;
             const methods = Object.keys(Uploads);
             methods.forEach( name=> {
                 if( proto[ name] ) {
                    throw new Error( "App already has method " + name);
                 }
                 proto[name] = Uploads[name];
             });
             config.configured = true;
        }
    }

    foo(arg) {
        /* Something */
        console.log( arg );
    }
}

const app = new App();
app.bar();

更好(更干净)的替代方法可能是在调用其构造函数之前向类添加更新,使用静态类方法,因为它的 this 值是构造函数。测试示例:

static addMethods(uploads) {  // inside class declaration
    const proto = this.prototype;
    for (const [name, method] of Object.entries(uploads)) {
         if( proto[name]) {
             throw new Error("App already has a ${name} method");
         }
         proto[name] = method;
    }
}

根据需要调用

 if( config.hasUploads) {
     App.addMethods( Uploads);
 }

【讨论】:

  • "帖子中显示的上传文件不是有效的对象初始化器" - 实际上是,使用concise method definitions
  • 感谢@Bergi 告诉我,我不知道简洁的函数/getter 语法。答案根据静态方法更新完成和测试。
猜你喜欢
  • 2020-04-28
  • 1970-01-01
  • 2012-07-20
  • 2017-10-13
  • 2015-03-01
  • 1970-01-01
  • 2016-06-11
  • 1970-01-01
  • 2017-07-24
相关资源
最近更新 更多