◆ require の仕組みを置き換えたり static プロパティを通してインスタンス取得したり
◆ 簡単かつあってもなくてもそう変わらない実装にするのは難しそう

JavaScript に限ったものじゃない作り方です
とりあえず書きやすい JavaScript で書いてます
class A では Foo と Bar のインスタンスを使いたいです

const Foo = require("./foo.js")
const Bar = require("./bar.js")

class A {
foo = new Foo()
bar = new Bar()
}

でも Foo を Foo2 に置き換えたり同じインターフェースがある別のクラスのインスタンスに置き換えたい場合も出てきます
テスト用に最低限のメソッドがある plain なオブジェクトにしたり

変更可能で外部から指定するのだとやっぱりコンストラクタでしょうか

class A {
constructor(Foo, Bar) {
this.foo = new Foo()
this.bar = new Bar()
}
}

今 2 つですが もっとプロパティが多い場合もあります
一つずつ書いていくと長くなって邪魔ですし オブジェクトにまとめます
あと 変更したくないのに毎回渡さないといけないのも面倒なのでデフォルト値も用意します

const Foo = require("./foo.js")
const Bar = require("./bar.js")

class A {
constructor(container) {
this.foo = container.Foo ? new container.Foo() : new Foo()
this.bar = container.Bar ? new container.Bar() : new Bar()
}
}

それでもやっぱり 余計なものがある感じが残ります
引数のひとつを container に取られたくないですし

クラス側で処理するのではなくモジュールのインポートを変えてみます
require の処理を変えて ユーザが登録したものを呼び出すようにするとクラス側は何もしなくていいので影響が少ないです

[require2.js]
const map = {}
module.exports = {
require2(name) {
const { path, value } = map[name] || {}
if (path) return require(path)
if (value) return value
return require(name)
},
setPath(name, path) {
map[name] = { path }
},
setValue(name, value) {
map[name] = { value }
}
}

[main.js]
const { setPath } = require("./require2.js")
setPath("foo", "./foo2.js")
setPath("bar", "./bar2.js")

require("./a.js")

[a.js]
const { require2 } = require("./require2.js")
const Foo = require2("foo")
const Bar = require2("bar")

class A {
constructor() {
this.foo = new Foo()
this.bar = new Bar()
}
}

require2 でロードする前に setPath や setValue で foo や bar で何を取得するのか設定が必要です
a.js のモジュールロード時に require2 を行うので a.js ファイルを require する前に設定します
ESModules だと 起動時の require2 を設定を終えたあとで a.js ファイルを import で動的に呼び出すことになります

動的な言語だと割とこういう事ができそうですが 静的な言語だと require を置き換えみたいなことは難しそうです
上の方で書いたみたいな constructor で処理するしかないのかな?
ただ require の置き換えはインスタンスごとじゃなくてクラス全体の設定になります
それに合わせるなら static プロパティにセットして constructor では自クラスの static プロパティを参照すればいいです
こうすれば constructor の引数はいらなくなります

const Foo = require("./foo.js")
const Bar = require("./bar.js")

class A {
static Foo = Foo
static Bar = Bar

constructor() {
this.foo = new constructor.Foo()
this.bar = new constructor.Bar()
}
}

//

A.Foo = require("./foo2.js")
new A()

ただやっぱり 静的言語だとクラスが値じゃなくて代入できないものも多そうです
そうなると 型名を文字列で入れておいてリフレクションでインスタンス作るとか static プロパティに保存する値を require2 みたいな仕組みをもったオブジェクトにして特定のメソッド呼び出しでインスタンスを返すようにするとかちょっと工夫が必要そうです