シングルトンパターンとそれがどのように「悪い」のかについては、クラスをテストするのが難しくなるので避けてきました。シングルトンを依存性注入に置き換える方法を説明した記事をいくつか読んだことがありますが、それは不必要に複雑に思えます。
これがもう少し詳しく私の問題です。 React Nativeを使用してモバイルアプリを構築しています。RESTクライアントを作成して、サーバーと通信し、データを取得し、データを投稿し、処理しますログイン(ログイントークンを保存し、ログイン後にリクエストごとに送信します)。
私の最初の計画は、アプリが最初にログインに使用し、必要に応じて資格情報の送信を要求するシングルトンオブジェクト(RESTClient)を作成することでした。 DIのアプローチは本当に複雑に思えます(おそらくDIを使用したことがないためです)が、このプロジェクトを使用してできるだけ多くのことを学び、ここで最善を尽くしたいと思います。提案やコメントは大歓迎です。
編集:私は今、自分の質問の言葉遣いが不十分だと気づきました。 RNでシングルトンパターンを回避する方法についてのガイダンスが必要でした。幸運にも、サミュエルは私が望んでいたような答えをくれました。私の問題は、シングルトンパターンを回避してDIを使用したかったのですが、React Nativeで実装するのは非常に複雑に思われました。Reactsコンテキストシステムを使用してさらに調査して実装しました。
ここに興味のある人のために、私はそれをしました。私が言ったように、私はプロップのようなものであるRNのコンテキストを使用しましたが、それはすべてのコンポーネントに伝搬されます。
ルートコンポーネントでは、次のような必要な依存関係を提供します。
export default class Root extends Component {
getChildContext() {
restClient: new MyRestClient();
}
render() {...}
}
Root.childContextTypes = {restClient: PropTypes.object};
これで、restClientは、ルートの下のすべてのコンポーネントで使用できます。このようにアクセスできます。
export default class Child extends Component {
useRestClient() {
this.context.restClient.getData(...);
}
render() {...}
}
Child.contextTypes = {restClient: PropTypes.object}
これにより、オブジェクトの作成がロジックから効果的に離れ、RESTクライアントの実装がコンポーネントから切り離されます。
依存関係の注入は、まったく複雑である必要はなく、学習して使用する価値は絶対にあります。通常、依存関係注入フレームワークの使用により複雑になりますが、必須ではありません。
最も単純な形式では、依存関係の注入は、依存関係をインポートまたは構築する代わりに、通過する依存関係です。これは、インポートするもののパラメーターを使用するだけで実現できます。 MyList
を使用してデータをフェッチし、ユーザーに表示する必要があるRESTClient
という名前のコンポーネントがあるとします。 「シングルトン」アプローチは次のようになります。
import restClient from '...' // import singleton
class MyList extends React.Component {
// use restClient and render stuff
}
これはMyList
をrestClient
に密結合します。MyList
をテストせずにrestClient
を単体テストする方法はありません。 DIアプローチは次のようになります。
function MyListFactory(restClient) {
class MyList extends React.Component {
// use restClient and render stuff
}
return MyList
}
それだけでDIを使用できます。最大で2行のコードが追加され、インポートが不要になります。私が新しい関数「factory」を導入した理由は、AFAIKがReactで追加のコンストラクターパラメーターを渡すことができないためです。また、移植性がないため、これらのものをReactプロパティを介して渡さないようにしていますすべての親コンポーネントは、すべての小道具を子供に渡すことを知っている必要があります。
これでMyList
コンポーネントを構築する関数ができましたが、それをどのように使用しますか? DIパターンは依存関係チェーンを浮き上がらせます。 MyApp
を使用するコンポーネントMyList
があるとします。 「シングルトン」アプローチは次のようになります。
import MyList from '...'
class MyApp extends React.Component {
render() {
return <MyList />
}
}
DIアプローチは次のとおりです。
function MyAppFactory(ListComponent) {
class MyApp extends React.Component {
render() {
return <ListComponent />
}
}
return MyApp
}
これで、MyApp
を直接テストせずにMyList
をテストできます。まったく別の種類のリストでMyApp
を再利用することもできます。このパターンは composition root まで上昇します。ここでファクトリを呼び出し、すべてのコンポーネントを配線します。
import RESTClient from '...'
import MyListFactory from '...'
import MyAppFactory from '...'
const restClient = new RESTClient(...)
const MyList = MyListFactory(restClient)
const MyApp = MyAppFactory(MyList)
ReactDOM.render(<MyApp />, document.getElementById('app'))
現在、システムはRESTClient
の単一のインスタンスを使用していますが、コンポーネントが疎結合でテストしやすいように設計されています。
あなたの前提(学習)によると、最も単純な答えはいいえ、シングルトンは依存性注入に代わる最良の方法ではありません。
学習が目標であれば、DIはツールボックスでシングルトンよりも価値のあるリソースであることがわかります。複雑に思えるかもしれませんが、学習曲線はほぼゼロから学ぶ必要があるすべてのものです。あなたの快適ゾーンの上に横たわらないでください、さもなければ、まったく学ぶことがありません。
技術的には、singletonとsingle instance(あなたがやろうとしていること)の間にはほとんど違いがありません。 DIを学ぶと、コード全体に単一インスタンスを注入できることがわかります。コードがテストしやすく、疎結合であることがわかります。 (詳細はチェックアウト サミュエルの答え)
しかし、ここで止まらないでください。シングルトンで同じコードを実装してください。次に、両方のアプローチを比較します。
両方の実装を理解し、これに精通することで、いつそれらが適切であるかを知ることができ、おそらくあなたは自分自身に質問に答えることができるでしょう。
ゴールデンハンマーを偽造するときです。DIの学習を避けようとすると、最終的には実装される可能性があります。 DIが必要になるたびにシングルトン。
DIとは何か、どのように使用するかを学ぶことは良い考えであるという他の回答にも同意します。
とはいえ、シングルトンがテストを難しくしすぎるという警告は、通常、静的型付け言語(C++、C#、Javaなど)を使用している人々によって行われます。
動的言語(Javascript、PHP、Python、Rubyなど)とは対照的に、通常、 DIを使用している場合よりもテスト固有の実装を持つシングルトン。
その場合、私はあなたとあなたの共同開発者にとってより自然なデザインを使用することをお勧めします。それは間違いを避ける傾向があるからです。結果がシングルトンになる場合は、それもそうです。
(しかし、もう一度:その決定をする前にDIを学んでください。)