シングルトンまたは静的クラス?

この記事は主に、「シングルトンクラスと静的クラスの主な違いは何か、どちらを使用するか、もう一方はいつ使用するか」という質問を聞いたときにインタビューで迷子になった開発者に役立ちます。 そして、「パターン」という言葉で落胆したり、自分自身の表現をやめるように要求したりする開発者にとっては確かに役立つでしょう。

静的クラスとは何ですか?


最初に、静的クラスとは何か、なぜ静的クラスが必要なのかを思い出しましょう。 CLI準拠の言語では、グローバル変数をカプセル化するための次のパラダイムが使用されます。グローバル変数はありません 。 静的メンバーを含むすべてのメンバーはクラス内でのみ宣言でき、クラス自体は任意の名前空間でグループ化できます( ただし、グループ化はできません )。 また、プライベートコンストラクターを使用して静的クラスの動作を模倣する必要がある場合、プラットフォームレベルでの静的クラスのサポートが.NET Framework 2.0に追加されました。 静的クラスと通常の非静的クラスの主な違いは、 new演算子を使用してこのクラスをインスタンス化できないことです。 静的クラスは本質的に一種の名前空間です-後者とは異なり、型ではなく静的変数とメソッドを保持するように設計されています。


シングルトンとは何ですか?


Gang of Four (GoF)によって最初に記述された生成パターンの1 。 クラスが1つのインスタンスのみを持ち、それにグローバルアクセスポイントを提供するようにします。 ここでは、このパターン、その目的、およびそれが解決するタスクを詳細に検討しません。ネットワークには、このパターンに関する多くの詳細情報があります(たとえば、 hereおよびhere )。 シングルトーンはスレッドセーフであり、初期化が単純で遅延しているため、スレッドセーフではないことに注意してください。

そして違いがない場合-なぜもっと生産するのですか?


では、これら2つのエンティティの違いは何ですか?また、いつ使用する必要がありますか? これを次の表で説明するのが最善だと思います。
シングルトン
静的クラス
アクセスポイントの数
1つだけのアクセスポイント- インスタンスの静的フィールド
N(パブリッククラスメンバーとメソッドの数に依存)
クラス継承
おそらくではありますが、常にではありません(以下で詳しく説明します)
不可能-静的クラスのオブジェクトのインスタンスを作成できないため、静的クラスをインスタンス化できません
インターフェイスの継承
おそらく制限なし
クラスの継承が不可能であるのと同じ理由で不可能
パラメーターとして渡す機能
おそらくシングルトンが実際のオブジェクトを提供するため
行方不明です
オブジェクト寿命監視
おそらく-たとえば、 初期化の遅延 (またはオンデマンドの作成
クラスの継承が不可能であるのと同じ理由で不可能
抽象ファクトリーを使用してクラスをインスタンス化する
たぶん
インスタンスを作成する可能性がないため不可能
連載
たぶん
インスタンスが不足しているため、該当しません。

上記の基準をより詳細に検討してください。

アクセスポイントの数

もちろん、 外部アクセスポイント、つまり、クラスとそのクライアントの対話のためのパブリックコントラクトを意味します。 コードを使用してこれを説明する方が便利です。

「標準」実装のシングルトン:
public class Session { private static Session _instance; //   ... public static Session Instance { get { // ... return _instance; } } public IUser GetUser() { // ... } public bool IsSessionExpired() { // ... } public Guid SessionID { get { // ... } } } 


静的クラス:
 public static class Session { //   1 public static IUser GetUser() { // ... } //   2 public static bool IsSessionExpired() { // ... } // ... //   N public static Guid SessionID { get { // ... } } } 


クラス継承

静的クラスの継承は簡単です-言語レベルではサポートされていません。 シングルトンでは、物事はもう少し複雑です。 使いやすさのために、多くの開発者はほとんどの場合、次のパターン実装を使用します。
 public class Singleton<T> where T : class { private static T _instance; protected Singleton() { } private static T CreateInstance() { ConstructorInfo cInfo = typeof(T).GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]); return (T)cInfo.Invoke(null); } public static T Instance { get { if (_instance == null) { _instance = CreateInstance(); } return _instance; } } } public class Session : Singleton<Session> { public IUser GetUser() { // ... } public bool IsSessionExpired() { // ... } public Guid SessionID { get { // ... } } } 

また、C#およびCLI互換言語では多重継承が禁止されているため、他の有用なクラスからSessionクラスを継承することはできません。 解決策は、アクセスインスタンスインスタンスをシングルトンにデラグすることです。
 public class Session : CoreObject { private Session() { } public static Session Instance { get { return Singleton<Session>.Instance; } } } 

インターフェースの継承

インターフェイスを使用すると、柔軟性が高まり、再利用可能なコードの数が増え、テスト容易性が向上し、最も重要なことには、オブジェクトの強力な接続を回避できます。 静的クラスは原則として継承をサポートしていません。 一方、シングルトンは通常のクラスであるため、インターフェイスの継承を完全にサポートしています。 ただし、この機会を利用する価値があるのは、混合シナリオでシングルトンインスタンスを入力パラメーターとして送信する場合、または海外にブロードキャストする場合のみです。 混合シナリオの例:
 //        ISession public class Session: CoreObject, ISession { private Session() { } public static Session Instance { get { return Singleton<Session>.Instance; } } } //                //     public class VpnSession : ISession { } public interface ISessionManager { ISession GetSession(Guid sessionID); //   ISession,     bool IsSessionExpired(ISession session); } 


パラメーターとして渡す機能

静的クラスの場合、これはサポートされていません-型のみを渡すことができますが、ほとんどの場合、リフレクションメカニズムが使用される場合を除き、それは役に立たない。 シングルトンは基本的にオブジェクトの通常のインスタンスです。
 // ... ISessionManager _sessionManager; // ... bool isExpired = _sessionManager.IsSessionExpired(Session.Instance); 

オブジェクト寿命監視

静的クラスのライフタイムは、ドメインのライフタイムによって制限されます。このドメインを手動で作成した場合、すべての静的タイプのライフタイムを間接的に制御します。 希望に応じてシングルトンの存続期間を制御できます。 顕著な例は、遅延初期化です。
 public class Singleton<T> where T : class { // ... public static T Instance { get { if (_instance == null) { //  " " _instance = CreateInstance(); } return _instance; } } } 

また、シングルトンインスタンスの削除操作を追加することもできます。
 public class Singleton<T> where T : class { // ... public static T Instance { // ... } //   ! public void RemoveInstance() { _instance = null; } } 

この操作は非常に安全ではありません。シングルトンは何らかの状態を保存できるため、再作成するとクライアントに望ましくない結果をもたらす可能性があります。 それでもこのようなメソッドの必要性が生じた場合(設計エラーを示している可能性が高い)、その使用による可能性のある悪を最小限に抑えるようにする必要があります。たとえば、特定の条件下でメソッドを閉じて内部のInstanceプロパティを呼び出します。
 public class Singleton<T> where T : class { // ... public static T Instance { get { if (!IsAlive) { //    RemoveInstance(); } if (_instance == null) { //  " " _instance = CreateInstance(); } return _instance; } } private void RemoveInstance() { _instance = null; } } 


抽象ファクトリーを使用してクラスをインスタンス化する

静的クラスのインスタンスを作成できないため、静的クラスはこの機能をサポートしません。 シングルトンの場合、すべてがシンプルに見えます。
 public interface IAbstractFactory { T Create<T>(); bool IsSupported<T>(); } public class Singleton<T> where T : class { private static T _instance; private static IAbstractFactory _factory; protected Singleton(IAbstractFactory factory) { _factory = factory; } public static T Instance { get { if (_instance == null) { _instance = _factory.Create<T>(); } return _instance; } } } //       public class Session : Singleton<Session> { protected Session() : base(new ConcreteFactory()) { } // ... } 

確かに、シングルトン集約のバリアントでは、あまり美しくなく、少し面倒な解決策を適用する必要があります。
 public class Session : CoreObject, ISession { private class SessionSingleton : Singleton<Session> { protected SessionSingleton() : base(new ConcreteFactory2()) { } } private Session() : base(new CoreContext()) { } public static Session Instance { get { return SessionSingleton.Instance; } } // ... } 


連載

シリアル化は、クラスインスタンスにのみ適用されます。 静的クラスはインスタンスを持つことができないため、この場合、シリアル化するものはありません。

それでは、SingletonまたはStaticクラスを使用するのは何ですか?


いずれにしても、ソリューションの選択は、開発者と彼が解決しているタスクの詳細に依存します。 しかし、いずれにしても、次の結論が導き出されます。

シングルトンの使用は、次の場合に正当化されます。

シングルトン用にリストされているスクリプトを実装する必要がない場合は、静的クラスを使用することをお勧めします。 静的クラスの主な目的は、論理的に類似したメソッド、定数、フィールド、プロパティのグループ化にあります。 例: System.MathSystem.BitConverterSystem.BufferSystem.Convertなど。

Source: https://habr.com/ru/post/J103681/


All Articles