web-dev-qa-db-ja.com

C#のファクトリパターン:ファクトリクラスでのみオブジェクトインスタンスを作成できるようにする方法

最近、コードの一部を保護することを考えています。ファクトリクラスのメソッドを介してのみ、オブジェクトを直接作成できないようにする方法を知りたいのですが。 「ビジネスオブジェクト」クラスがあり、このクラスのインスタンスが有効な内部状態を持っていることを確認したいとします。これを実現するには、おそらくコンストラクターでオブジェクトを作成する前に、いくつかのチェックを実行する必要があります。このチェックをビジネスロジックの一部にすることを決定するまで、これで十分です。それでは、ビジネスロジッククラスのメソッドを介してのみ作成でき、直接は作成できないビジネスオブジェクトをどのように配置できますか? C++の古き良き「友人」キーワードを使用する最初の自然な欲求は、C#では不十分です。したがって、他のオプションが必要です...

いくつかの例を試してみましょう:

public MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    public MyBusinessObjectClass (string myProperty)
    {
        MyProperty = myProperty;
    }
}

public MyBusinessLogicClass
{
    public MyBusinessObjectClass CreateBusinessObject (string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return new MyBusinessObjectClass (myProperty);

        return null;
    }
}

入力をチェックせずにMyBusinessObjectClassインスタンスを直接作成できることを思い出すまでは、大丈夫です。その技術的な可能性を完全に排除したいと思います。

それで、コミュニティはこれについてどう思いますか?

84
User

オブジェクトを作成する前にビジネスロジックを実行するだけのように見えるので、なぜ「BusinessClass」内にすべてのダーティな「myProperty」チェックを行う静的メソッドを作成し、コンストラクターをプライベートにしないのですか。

public BusinessClass
{
    public string MyProperty { get; private set; }

    private BusinessClass()
    {
    }

    private BusinessClass(string myProperty)
    {
        MyProperty = myProperty;
    }

    public static BusinessClass CreateObject(string myProperty)
    {
        // Perform some check on myProperty

        if (/* all ok */)
            return new BusinessClass(myProperty);

        return null;
    }
}

それを呼び出すことは非常に簡単です:

BusinessClass objBusiness = BusinessClass.CreateObject(someProperty);
56
Ricardo Nolde

コンストラクターをプライベートにし、ファクトリーをネスト型にすることができます。

public class BusinessObject
{
    private BusinessObject(string property)
    {
    }

    public class Factory
    {
        public static BusinessObject CreateBusinessObject(string property)
        {
            return new BusinessObject(property);
        }
    }
}

これは、ネストされた型がそれらを囲む型のプライベートメンバーにアクセスできるためです。私はそれが少し制限されていることを知っていますが、うまくいけばそれが役立つでしょう...

61
Jon Skeet

または、本当に凝りたい場合は、制御を反転させます。クラスにファクトリーを返させ、クラスを作成できるデリゲートでファクトリーをインストルメントします。

public class BusinessObject
{
  public static BusinessObjectFactory GetFactory()
  {
    return new BusinessObjectFactory (p => new BusinessObject (p));
  }

  private BusinessObject(string property)
  {
  }
}

public class BusinessObjectFactory
{
  private Func<string, BusinessObject> _ctorCaller;

  public BusinessObjectFactory (Func<string, BusinessObject> ctorCaller)
  {
    _ctorCaller = ctorCaller;
  }

  public BusinessObject CreateBusinessObject(string myProperty)
  {
    if (...)
      return _ctorCaller (myProperty);
    else
      return null;
  }
}

:)

49
Fabian Schmied

MyBusinessObjectClassクラスのコンストラクターを内部に作成し、コンストラクターとファクトリーを独自のアセンブリに移動できます。これで、クラスのインスタンスを構築できるのはファクトリーのみになります。

15
Matt Hamilton

Jonが提案したこととは別に、最初にファクトリメソッド(チェックを含む)をBusinessObjectの静的メソッドにすることもできます。次に、コンストラクターをプライベートにします。そうしないと、他の全員が静的メソッドを使用する必要があります。

public class BusinessObject
{
  public static Create (string myProperty)
  {
    if (...)
      return new BusinessObject (myProperty);
    else
      return null;
  }
}

しかし、本当の質問は-なぜこの要件があるのですか?ファクトリーまたはファクトリーメソッドをクラスに移動してもかまいませんか?

7
Fabian Schmied

さらに別の(軽量)オプションてBusinessObjectクラスのstaticファクトリメソッドを作成し、プライベートコンストラクタを維持することです。

public class BusinessObject
{
    public static BusinessObject NewBusinessObject(string property)
    {
        return new BusinessObject();
    }

    private BusinessObject()
    {
    }
}
4
Dan C.

非常に多くの年後、このGOTは尋ねた、と私は見るすべての答えは、残念ながらあなたの代わりにストレートな答えを与えることのあなたのコードを実行する必要がありますどのように語っています。あなたが探していた実際の答えは、プライベートコンストラクタではなくパブリックインスタンスを持つクラスを持っているということです。つまり、他の既存のインスタンスからのみ新しいインスタンスを作成できます。

クラスのインターフェース:

public interface FactoryObject
{
    FactoryObject Instantiate();
}

あなたのクラス:

public class YourClass : FactoryObject
{
    static YourClass()
    {
        Factory.RegisterType(new YourClass());
    }

    private YourClass() {}

    FactoryObject FactoryObject.Instantiate()
    {
        return new YourClass();
    }
}

そして最後に、工場:

public static class Factory
{
    private static List<FactoryObject> knownObjects = new List<FactoryObject>();

    public static void RegisterType(FactoryObject obj)
    {
        knownObjects.Add(obj);
    }

    public static T Instantiate<T>() where T : FactoryObject
    {
        var knownObject = knownObjects.Where(x => x.GetType() == typeof(T));
        return (T)knownObject.Instantiate();
    }
}

あなたが作成するインスタンスのインスタンス化のためか、前処理に余分なパラメータを必要とするなら、あなたは簡単にこのコードを変更することができます。また、このコードを使用すると、クラスコンストラクターがプライベートであるため、ファクトリを介してインスタンス化を強制できます。

4
Alberto Alonso
    public class HandlerFactory: Handler
    {
        public IHandler GetHandler()
        {
            return base.CreateMe();
        }
    }

    public interface IHandler
    {
        void DoWork();
    }

    public class Handler : IHandler
    {
        public void DoWork()
        {
            Console.WriteLine("hander doing work");
        }

        protected IHandler CreateMe()
        {
            return new Handler();
        }

        protected Handler(){}
    }

    public static void Main(string[] args)
    {
        // Handler handler = new Handler();         - this will error out!
        var factory = new HandlerFactory();
        var handler = factory.GetHandler();

        handler.DoWork();           // this works!
    }
2
lin

インターフェイスと実装が適切に分離されている場合
protected-constructor-public-initializerパターンを使用すると、非常に適切なソリューションが得られます。

与えられたビジネスオブジェクト:

_public interface IBusinessObject { }

class BusinessObject : IBusinessObject
{
    public static IBusinessObject New() 
    {
        return new BusinessObject();
    }

    protected BusinessObject() 
    { ... }
}
_

およびビジネス工場:

_public interface IBusinessFactory { }

class BusinessFactory : IBusinessFactory
{
    public static IBusinessFactory New() 
    {
        return new BusinessFactory();
    }

    protected BusinessFactory() 
    { ... }
}
_

BusinessObject.New()イニシャライザに対する次の変更により、ソリューションが提供されます。

_class BusinessObject : IBusinessObject
{
    public static IBusinessObject New(BusinessFactory factory) 
    { ... }

    ...
}
_

ここでは、BusinessObject.New()初期化子を呼び出すために、具体的なビジネスファクトリへの参照が必要です。ただし、必要な参照を持っているのはビジネスファクトリ自体だけです。

必要なものが得られました。BusinessObjectを作成できるのはBusinessFactoryだけです。

2
Reuven Bass

だから、それは私が「純粋」な方法で行うことができない欲しいもののように見えます。それは、常にロジッククラスへの「コールバック」のいくつかの種類です。

たぶん私は、簡単な方法でそれを行うことができ、単に入力をチェックするためのオブジェクトクラスの最初の呼び出しの論理クラスのコンストラクタメソッドを作りますか?

public MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    private MyBusinessObjectClass (string myProperty)
    {
        MyProperty  = myProperty;
    }

    pubilc static MyBusinessObjectClass CreateInstance (string myProperty)
    {
        if (MyBusinessLogicClass.ValidateBusinessObject (myProperty)) return new MyBusinessObjectClass (myProperty);

        return null;
    }
}

public MyBusinessLogicClass
{
    public static bool ValidateBusinessObject (string myProperty)
    {
        // Perform some check on myProperty

        return CheckResult;
    }
}

この方法では、ビジネスオブジェクトは直接作成できず、ビジネスロジックのパブリックチェックメソッドも害を与えません。

2
User

この溶液は、コンストラクタでトークンを使用する munificents 考えオフに基づいています。この回答で完了 オブジェクトがファクトリー(C#)によってのみ作成されていることを確認してください

  public class BusinessObject
    {
        public BusinessObject(object instantiator)
        {
            if (instantiator.GetType() != typeof(Factory))
                throw new ArgumentException("Instantiator class must be Factory");
        }

    }

    public class Factory
    {
        public BusinessObject CreateBusinessObject()
        {
            return new BusinessObject(this);
        }
    }
1
Lindhard

トレードオフの異なる複数のアプローチが言及されています。

  • 個人的に構築されたクラスでのファクトリクラスをネストすることのみ工場は1クラスを構築することができます。その時点で、あなたはより良いオフCreate方法やプライベートCTORとしています。
  • 継承と保護されたctorの使用には同じ問題があります。

私は、publicコンストラクタを持つプライベート入れ子になったクラスが含まれていパーシャルクラスとして工場を提案したいと思います。ファクトリが構築しているオブジェクトを100%隠し、1つまたは複数のインターフェイスを介して選択したものだけを公開しています。

あなたは、工場内のインスタンスの100%を追跡するとき、私はこれを聞いたユースケースは次のようになります。この設計は誰も保証しませんが、工場は「工場」で定義された「化学物質」のインスタンスを作成するためのアクセス権を持ち、それを達成するために別のアセンブリの必要性を取り除きます。

== ChemicalFactory.cs ==
partial class ChemicalFactory {
    private  ChemicalFactory() {}

    public interface IChemical {
        int AtomicNumber { get; }
    }

    public static IChemical CreateOxygen() {
        return new Oxygen();
    }
}


== Oxygen.cs ==
partial class ChemicalFactory {
    private class Oxygen : IChemical {
        public Oxygen() {
            AtomicNumber = 8;
        }
        public int AtomicNumber { get; }
    }
}



== Program.cs ==
class Program {
    static void Main(string[] args) {
        var ox = ChemicalFactory.CreateOxygen();
        Console.WriteLine(ox.AtomicNumber);
    }
}
1
ubershmekel

ファクトリをドメインクラスと同じアセンブリに配置し、ドメインクラスのコンストラクターを内部としてマークします。この方法では、ドメイン内の任意のクラスは、インスタンスを作成することができるかもしれないが、あなたは、右に自分を信用していませんか?ドメイン層のコードの外側を書いて誰もが自分の工場を使用する必要があります。

public class Person
{
  internal Person()
  {
  }
}

public class PersonFactory
{
  public Person Create()
  {
    return new Person();
  }  
}

しかし、私はあなたのアプローチを疑問視しなければなりません:-)

私はあなたのPersonクラスは、作成時に有効になりたい場合は、コンストラクタ内のコードを入れなければならないと思います。

public class Person
{
  public Person(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
    Validate();
  }
}
1
Peter Morris

あなたは、「ビジネスオブジェクト」から「ビジネスロジック」を分離したい理由を私は理解していません。このオブジェクト指向の歪みのような音、そしてあなたはそのアプローチを取ることによってノットで自分自身を抱き合わせてしまいます。

0
Jim Arnold

それが非表示に何かをdoesntの - 私はすべての彼は上記の私見を悪化問題とちょうどあなたのオブジェクトを使用する工場を呼び出して文句を言わないストップ人であるのpublic staticファクトリが必要、問題はより悪くはない解決策があるとは思いません。アセンブリは信頼できるコードであるため、それが最善の保護である場合は、インターフェイスを公開するか、コンストラクタを内部として保持するのが最善です。

1つのオプションは、IOCコンテナーのようなものでファクトリを登録する静的コンストラクターを持つことです。

0
user1496062

ここでは、「あなたは、あなたがしなければならないという意味ではありませんすることができますという理由だけで」の静脈内の別の解決策は...

これは、ビジネス・オブジェクト・コンストラクタのプライベートを維持し、別のクラスでは工場出荷時のロジックを置くの要件を満たしていません。その後それは少し大ざっぱなります。

ファクトリクラスは、ビジネス・オブジェクトを作成するための静的メソッドがあります。それはプライベートなコンストラクタを呼び出す静的保護工法にアクセスするために、ビジネス・オブジェクト・クラスから派生します。

(それはまた、ビジネスオブジェクトになるので、そのためには奇妙なだろう)、あなたが実際にそれのインスタンスを作成することはできませんし、クライアントコードはそれから派生することはできませんので、それはプライベートなコンストラクタを持っているので、工場は抽象的です。

何を妨げていないことも、クライアントコードである、ビジネス・オブジェクト・クラスから派生し、保護された(ただし、未検証)、静的な工法を呼び出します。それとも悪いことに、我々は最初の場所でコンパイルするファクトリクラスを取得するために追加する必要がありました保護された既定のコンストラクタを呼び出します。 (どのちなみに、ビジネス・オブジェクト・クラスからファクトリクラスを分離して任意のパターンに問題がある可能性があります。)

私はこのような何かを行う必要があります彼らの権利念頭に置いて誰を提案しようとしていないんだけど、それは興味深い演習でした。 FWIWは、私の好適な解決策は、ガードとして内部コンストラクタおよびアセンブリの境界を使用することです。

using System;

public class MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    private MyBusinessObjectClass(string myProperty)
    {
        MyProperty = myProperty;
    }

    // Need accesible default constructor, or else MyBusinessObjectFactory declaration will generate:
    // error CS0122: 'MyBusinessObjectClass.MyBusinessObjectClass(string)' is inaccessible due to its protection level
    protected MyBusinessObjectClass()
    {
    }

    protected static MyBusinessObjectClass Construct(string myProperty)
    {
        return new MyBusinessObjectClass(myProperty);
    }
}

public abstract class MyBusinessObjectFactory : MyBusinessObjectClass
{
    public static MyBusinessObjectClass CreateBusinessObject(string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return Construct(myProperty);

        return null;
    }

    private MyBusinessObjectFactory()
    {
    }
}
0
yoyo

このソリューションにはいくつかの考えを聞いていただければ幸いです。 「MyClassPrivilegeKey」を作成することができる唯一の一つは工場です。そして、「MyClassのは、」コンストラクタで、それを必要とします。したがって、工場に民間業者/「登録」での反射を回避することができます。

public static class Runnable
{
    public static void Run()
    {
        MyClass myClass = MyClassPrivilegeKey.MyClassFactory.GetInstance();
    }
}

public abstract class MyClass
{
    public MyClass(MyClassPrivilegeKey key) { }
}

public class MyClassA : MyClass
{
    public MyClassA(MyClassPrivilegeKey key) : base(key) { }
}

public class MyClassB : MyClass
{
    public MyClassB(MyClassPrivilegeKey key) : base(key) { }
}


public class MyClassPrivilegeKey
{
    private MyClassPrivilegeKey()
    {
    }

    public static class MyClassFactory
    {
        private static MyClassPrivilegeKey key = new MyClassPrivilegeKey();

        public static MyClass GetInstance()
        {
            if (/* some things == */true)
            {
                return new MyClassA(key);
            }
            else
            {
                return new MyClassB(key);
            }
        }
    }
}
0
Ronen Glants