web-dev-qa-db-ja.com

パブリック静的メソッドがプライベートコンストラクターを呼び出す

私は主に元開発者によって書かれたC#コードベースで作業しており、このパターンは広く使用されています...

public class AuditInserter
{
    public static void Insert(
        DataContext dataContext,
        Person person,
        AuditableAction action)
    {
        return
            new AuditInserter(
                dataContext,
                person)
            .InsertAudit(action);
    }

    private AuditInserter(
        DataContext dataContext,
        Person person)
    {
        _dataContext = dataContext;
        _person = person;
    }

    private readonly _dataContext;
    private readonly _person;

    private void InsertAudit(AuditableAction action)
    {
        _dataContext.AuditTable.Insert(_person, action);
    }
}

// Consuming object
public class MainProgram
{
    public static void Main()
    {
        AuditInserter.Insert(
            new DataContext,
            new Person,
            new AuditableAction);
    }
}

コンストラクターはプライベートであり、オブジェクトの構築は静的メソッドによって処理されます。これはシングルトンパターンの例ではないことに注意してください。オブジェクトはそれ自体の静的インスタンスへの参照を保持していません。

このデザインのメリットはありますか?そのように設計するのは私の自然な傾向です:

public class AuditInserter
{
    public AuditInserter(
        DataContext dataContext,
        Person person)
    {
        _dataContext = dataContext;
        _person = person;
    }

    private readonly _dataContext;
    private readonly _person;

    public void Insert(
        AuditableAction action)
    {
        return InsertAudit(action);
    }

    private void InsertAudit(AuditableAction action)
    {
        _dataContext.AuditTable.Insert(_person, action);
    }
}

// Consuming object
public class MainProgram
{
    public static void Main()
    {
        new AuditInserter(
            new DataContext,
            new Person)
        .Insert(
            new AuditableAction);
    }
}

私は、この既存のパターンが提供する利点(もしあれば)を理解せずに、盲目的にただ従うのは嫌です。

コードはC#にありますが、この問題はC#固有ではありません。

3
amarsha4

(回答はコメントに照らして書き直されました、「申し訳ありませんが、私が混乱した主な設計要素を示すのに役立つ非常に簡単な例を作成したことを述べたはずです。InsertAuditメソッドは他のいくつかのプライベートインスタンスメソッドを呼び出します。 "。この説明により、静的なアプローチが「深く混乱して混乱する」から、パラメータの引き渡しを回避するための実行可能な解決策に変わります。)

あなたが与える例から、あなたの同僚のコードは非常に混乱していて混乱しています。ただし、InsertAuditメソッドが他のいくつかのプライベートインスタンスメソッドを呼び出すことを明確にすると、何が起こっているかがより明確になります。表示するすべての静的コードは、機能を変更せずに次のように減らすことができます。

public class AuditInserter
{
    public static void Insert(DataContext dataContext,
                              Person person,
                              AuditableAction action)
    {
        dataContext.AuditTable.Insert(person, action);
    }
}

しかし、そのコードを再検討し、いくつかのプライベートメソッドを追加して、実際のコードをよりよく表すようにします。簡潔にするためにコードのサイズを小さくするために、C#7の構文機能を自由に使用しています):

public class AuditInserter
{
    public static void Insert(DataContext dataContext,
                              Person person,
                              AuditableAction action)
        => new AuditInserter(dataContext, person).InsertAudit(action);

    private AuditInserter(DataContext dataContext, Person person)
        => (_dataContext, _person) = (dataContext, person);

    private readonly _dataContext;
    private readonly _person;

    private void InsertAudit(AuditableAction action)
    {
        DoSomethingFirst();
        DoTheInsert(action);
        DoSomethingAfter();
    }

    private void DoSomethingFirst()
        => // do something with _person and _dataContext here

    private void DoTheInsert(AuditableAction action)
        => _dataContext.AuditTable.Insert(_person, action);

    private void DoSomethingAfter()
        => // do something with _person and _dataContext here
}

繰り返しますが、コードをすべて静的にすることで、コードを1つの点で簡略化できます。コンストラクターとフィールドを削除できます。ただし、別の点で複雑さを増しています。メソッドには複数のパラメーターがあり、これらの値を渡すことができます。

public class AuditInserter
{
    public static void Insert(DataContext dataContext,
                              Person person,
                              AuditableAction action)
    {
        DoSomethingFirst(dataContext, person);
        DoTheInsert(dataContext, person, action);
        DoSomethingAfter(dataContext, person);
    }

    private void DoSomethingFirst(DataContext dataContext,
                                  Person person)
        => // do something with person and dataContext here

    private void DoTheInsert(DataContext dataContext,
                             Person person,
                             AuditableAction action)
        => dataContext.AuditTable.Insert(person, action);

    private void DoSomethingAfter(DataContext dataContext,
                                  Person person)
        => // do something with person and dataContext here
}

どちらのアプローチを取るかは、個人的な好みと実際の例で持っているパラメータの数の組み合わせです。これら2つの項目については、完全に静的なアプローチを採用します。 5、6、またはそれ以上に増えた場合、私はおそらくその元の開発者に似たものを採用するでしょう。他の人々は他の個人的な好みを持っていて、すべての場合にそのパラメータのキャッシングアプローチを採用することさえあるかもしれません(以前の開発者が行ったようです)。ただし、コードのバージョンの動作は、静的バージョンとはかなり異なります。これは、Mainを次のように変更することで表示できます。

public class MainProgram
{
    public static void Main()
    {
        var inserter = new AuditInserter(new DataContext, new Person);
        inserter.Insert(new AuditableAction);
        inserter.Insert(new AuditableAction);
    }
}

同じAuditInserterを使用して2つのアクションを挿入しています。これは良いことかもしれません。その場合、作業しやすいと感じた場合は、アプローチの使用に切り替えることができます。しかし、それは悪いことかもしれません。その場合、物事をそのままにします(または完全に静的なアプローチに切り替えます)。

ただし、コード例を簡略化する際に誤って削除した他の重要な詳細が明らかになる場合もありますが、このパターンは完全にファクトリーとは無関係です。

6
David Arno

これは factory pattern の例です。

実装と現在の実装との間に小さいながらも大きな違いがあることがわかります。実装では、AuditInserterを1つ(および1つだけ)含まずにAuditableActionを作成することは完全に可能です。

  • Insert(new AuditableAction);を省略できないわけではありません
  • また、同じAuditInserterInsert()を繰り返し呼び出すことを妨げるものは何もありません。

もちろん、AuditableActionのコンストラクタの一部としてAuditInserterを要求するだけで同じようにアーカイブできますが、それを望まない理由がある場合もあります(例:Insert()関数は例外をスローする可能性がありますが、AuditInserterのコンストラクターが例外をスローするのを避けたい、またはnotはコンストラクタに属しているか、および/または 特定の順序で実行される )である必要があります。

同様に、AuditInserterにはいくつかのフレーバー/サブタイプがあり、ファクトリー関数は入力に応じて適切なタイプを提供できます(たとえば、FinancialAuditInserterが関連している場合、AuditableAction財務、またはHrAuditInserter HRに関連している場合など...)。

結局のところ、それはすべてそれがどのように使用されるかに依存します-すべての設計パターンは悪用される可能性があり、アプリケーションでの使用のコンテキストでのみ、それが賢明かどうかを判断できます。

4
CharonX

デザインは少し混乱しています。目的は、実装とオブジェクト作成ステップを隠すことでした。しかし、目的が明確でないため、実装は開発者を混乱させます。私はExtension Methodsを使用することをお勧めします。これは、より単純な既知のアプローチです。 「AuditInserter」クラスである複雑さの追加のレイヤーを導入する必要はありません。

メソッドは次のようになります

public static class DataContextExtension
{
    public static void InsertAudit(this DataContext context, Person person, AuditableAction action)
    {
        context.AuditTable.Insert(person, action);
    }
}

そして使用法は次のようになります

public class MainProgram
{
    public static void Main()
    {
        new DataContext().InsertAudit(
        new Person(),
        new AuditableAction());
    }
}
1
Arafat Hegazy

そのように静的メソッドを使用することは混乱を招くため、IMOをリファクタリングするか、少なくとも新しいコードに移行しないようにすることに同意します。私は、クラスが有効な状態で構築されることを保証するために、データコンテナークラスでファクトリスタティックメソッドを使用します。依存関係があるようなものでは決して使用しません。 IoCコンテナーを結び付けてデリゲートを使用して静的メソッドを呼び出すことはできますが、これはコンテナーを使用する利点の一部を幾分無効にします。 (追加コーディング)

たとえば、成功した呼び出しを表す2つの状態、または失敗したもの(検証失敗の詳細)を持つ「結果」タイプのDTOがある場合、通常、Success静的ファクトリメソッドを持つプライベートコンストラクターを使用して、結果、または障害の静的なメソッドを使用して、障害の理由でインスタンスを初期化します。単純なデータコンテナーを独自のファクトリとして機能させることも、より純粋なファクトリパターンを使用することもできますが、クラスを簡単に「ロックダウン」して、目的に応じて常に有効な状態を表すようにすることはできません。

0
Steve Py