C#のファクトリパターン:オブジェクトインスタンスがファクトリクラスによってのみ作成されることを確実にする方法は?


93

最近、自分のコードの一部を保護することを考えています。オブジェクトが直接作成されないようにするにはどうすればよいのか興味がありますが、ファクトリクラスのメソッドを介してのみ作成できます。「ビジネスオブジェクト」クラスがあり、このクラスのインスタンスが有効な内部状態になるようにしたいとします。これを実現するには、オブジェクトを作成する前に、おそらくそのコンストラクタで、いくつかのチェックを実行する必要があります。このチェックをビジネスロジックの一部にすることを決定するまで、これで問題ありません。それでは、ビジネスロジッククラスのいくつかのメソッドを介してのみ、直接ではなく、ビジネスオブジェクトを作成できるようにするにはどうすればよいでしょうか。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インスタンスを直接作成できることを思い出すまでは問題ありません。その技術的な可能性を完全に排除したいと思います。

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


MyBoClassメソッドはスペルミスですか?
pradeeptp

2
私は混乱しています。なぜあなたのビジネスオブジェクトを彼ら自身の不変条件を保護する責任があるものにしたくないのですか?(私が理解しているように)ファクトリパターンの要点は、A)多くの依存関係を持つ複雑なクラスの作成に対処するか、またはB)ファクトリを返して、実行時の型を決定し、インターフェイス/要約のみを公開することです。クライアントへのクラス。ビジネスルールをビジネスオブジェクトに入れることは、OOPの本質です。
サラ2015年

@kai確かに、ビジネスオブジェクトは不変条件を保護する責任がありますが、コンストラクターの呼び出し元は、引数をコンストラクターに渡す前に引数が有効であることを確認する必要がありますCreateBusinessObjectメソッドの目的は、コンストラクターに渡す引数が有効かどうかを呼び出し元がすぐに知らない場合に、単一のメソッド呼び出しで引数を検証して(新しい有効なオブジェクトを返すか、エラーを返す)です。
Lightman、2015

2
同意しません。コンストラクターは、指定されたパラメーターに対して必要なチェックを行う責任があります。コンストラクターを呼び出しても例外がスローされない場合、作成されたオブジェクトが有効な状態であることを信頼しています。「間違った」引数を使用してクラスをインスタンス化することは物理的に可能であってはなりません。Distance負の引数を持つインスタンスを作成すると、ArgumentOutOfRangeExceptionたとえば、DistanceFactory同じチェックを行うを作成しても何も得られません。ここであなたが何を得ようとしているのかまだわかりません。
サラ2015

回答:


55

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

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);

6
ああ、nullを返す代わりに例外をスローすることもできます。
Ricardo Nolde 2010年

5
カスタムコンストラクターを宣言している場合は、プライベートのデフォルトコンストラクターを使用しても意味がありません。また、この場合、実際のコンストラクターで検証を実行するだけでなく、静的な「コンストラクター」を使用しても文字どおり何も得られません(とにかくクラスの一部であるため)。NREに開放され、「このnullは一体何を意味するのか?」質問。
sara

インターフェイス/抽象基本クラスを通じてこのアーキテクチャを要求する良い方法はありますか?つまり、実装者がctorを公開してはならないことを指示するインターフェース/抽象基本クラスです。
Arash Motamedi 2016

1
@Arashいいえ。インターフェイスはコンストラクタを定義できません。保護されたコンストラクタまたは内部コンストラクタを定義する抽象クラスは、継承するクラスが独自のパブリックコンストラクタを介してそれを公開することを防止できません。
Ricardo Nolde 2016年

66

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

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

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

これは、入れ子にされた型が、それらを囲む型のプライベートメンバーにアクセスできるため機能します。私はそれが少し制限的であることを知っています、しかしうまくいけばそれが助けになるでしょう...


私はそれについて考えましたが、これらのチェックをビジネスオブジェクト自体に効果的に移動しましたが、これは避けようとしています。
ユーザー

@ so-tester:BusinessObjectタイプではなく、ファクトリでチェックを行うことができます。
Jon Skeet、

3
@JonSkeet私はこの質問が本当に古いことを知っていますが、静的メソッドをクラスの直接のメソッドにする代わりにCreateBusinessObject、ネストされたFactoryクラスの内部にメソッドを置くことの利点は何なのか知りたいですBusinessObject...そうする動機は?
Kiley Naro、2011

3
@KileyNaro:ええ、それが質問が求めていたものです:)これは必ずしも多くの利点をもたらすわけではありませんが、それは質問に答えます...これが役立つこともあります-ビルダーのパターンが思い浮かびます。(その場合、ビルダーはネストされたクラスであり、それはと呼ばれるインスタンスメソッドを持ちますBuild。)
Jon Skeet

53

または、本当に凝ったものにしたい場合は、制御を逆にします。クラスにファクトリを返させ、クラスを作成できるデリゲートをファクトリに装備させます。

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;
  }
}

:)


とても素敵なコード。オブジェクト作成ロジックは別のクラスにあり、オブジェクトはファクトリを使用してのみ作成できます。
Nikolai Samteladze 2013年

涼しい。BusinessObjectFactoryの作成を静的なBusinessObject.GetFactoryに制限できる方法はありますか?
Felix Keil、2015年

1
この反転の利点は何ですか?
yatskovsky

1
@yatskovskyこれは、オブジェクトを制御された方法でのみインスタンス化できるようにすると同時に、作成ロジックを専用クラスに分解できるようにするための方法にすぎません。
Fabian Schmied、2016

15

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


7

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

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

しかし、本当の問題は-なぜこの要件があるのですか?ファクトリまたはファクトリメソッドをクラスに移動することは許可されますか?


これはほとんど要件ではありません。ビジネスオブジェクトとロジックを明確に分離したいだけです。ページのコードビハインドでこれらのチェックを行うことは不適切であるのと同様に、オブジェクト自体でこれらのチェックを行うことは不適切であると思います。まあ、基本的な検証かもしれませんが、実際にはビジネスルールではありません。
ユーザー

便宜を図るためだけにクラスのロジックと構造を分離したい場合は、常に部分クラスを使用して、両方を別々のファイルに
含めることが

7

非常に長い年月を経て、これは尋ねられました、そして私が見るすべての答えは、残念ながら、正直な答えを与えるのではなく、あなたがコードをどうやってすべきかをあなたに伝えています。あなたが探していた実際の答えは、プライベートコンストラクターを持つクラスにパブリックインスタンシエーターを含めることです。つまり、他の既存のインスタンスからのみ新しいインスタンスを作成できます...ファクトリでのみ利用可能です。

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

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

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

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

    private BusinessObject()
    {
    }
}

2

だから、私が望んでいることは「純粋な」方法ではできません。これは常に、ロジッククラスに対するある種の「コールバック」です。

多分私はそれを簡単な方法で行うことができます、オブジェクトクラスのコンストラクタメソッドに最初にロジッククラスを呼び出して入力をチェックさせるだけですか?

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

インターフェイスと実装が適切に分離されている場合、
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


それで、オブジェクトの唯一の機能するパブリックコンストラクターがファクトリーを必要とし、その必要なファクトリーパラメーターはファクトリーの静的メソッドを呼び出すことによってのみインスタンス化できることを確認しますか?有効な解決策のようですpublic static IBusinessObject New(BusinessFactory factory) が、スニペットのようなものは、誰かがコードを維持していれば、多くの眉をひそめると感じています。
2014年

@Flater同意。パラメータ名factoryをに変更しますasFriend。私のコードベースでは、それは次のように表示されますpublic static IBusinessObject New(BusinessFactory asFriend)
Reuven Bass

まったくわかりません。それはどのように機能しますか?ファクトリはIBusinessObjectの新しいインスタンスを作成できず、そのための意図(メソッド)もありません...?
ラプサス2015

2
    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!
    }

私のアプローチを試してください。「ファクトリー」はハンドラーのコンテナーであり、それだけがインスタンスを作成できます。多少の変更を加えると、ニーズに合う場合があります。
lin 2015

1
あなたの例では、次のことは可能ではありません(意味がありません)?: factory.DoWork();
Whyser 2015

1

ファクトリをドメインクラスと同じアセンブリに配置し、ドメインクラスのコンストラクタを内部としてマークします。このように、ドメイン内の任意のクラスがインスタンスを作成できる可能性がありますが、自分はそうではないと信じていますよね?ドメイン層の外でコードを書く人は誰でもあなたのファクトリを使わなければなりません。

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

このソリューションは、コンストラクターでトークンを使用するという地方自治体の考えに基づいています。この回答では、オブジェクトがファクトリ(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

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

  • プライベートに構築されたクラスにファクトリクラスをネストすると、ファクトリは1つのクラスのみを構築できます。その時点で、CreateメソッドとプライベートActorの方がよいでしょう。
  • 継承と保護されたctorの使用には同じ問題があります。

ファクトリーを、パブリックコンストラクターを持つネストされたプライベートクラスを含む部分クラスとして提案したいと思います。ファクトリーが構築しているオブジェクトを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);
    }
}

0

「ビジネスロジック」を「ビジネスオブジェクト」から分離する理由がわかりません。これはオブジェクトの方向のゆがみのように聞こえ、そのアプローチをとることによって結局は結び目に縛られることになります。


この区別もわかりません。なぜこれが行われるのか、そしてビジネスオブジェクトにはどのようなコードが含まれるのか、誰かが説明できますか?
pipTheGeek 2009

これは、使用できるアプローチの1つにすぎません。ビジネスオブジェクトを主にデータ構造にしたいが、すべてのフィールドを読み取り/書き込み用に開いていない。しかし、問題は本当に、直接ではなく、ファクトリメソッドの助けを借りてのみオブジェクトをインスタンス化できることでした。
ユーザー

@ジム:私はあなたの意見に個人的に同意しますが、専門的にはこれは常に行われます。私の現在のプロジェクトは、HTML文字列を受け取り、いくつかの事前設定されたルールに従ってそれをクリーンアップし、クリーンアップされた文字列を返すWebサービスです。「すべてのプロジェクトは同じプロジェクトテンプレートからビルドする必要があるため」、私は自分のコードの7層を通過する必要があります。SOでこれらの質問をするほとんどの人がコードのフロー全体を変更する自由を得られないのは当然のことです。
2014年

0

私は問題よりも悪くない解決策はないと思います、上記のすべては私に悪い問題であり、人々があなたのオブジェクトを使用するためにファクトリーを呼び出すだけの人々を止めません-それは何も隠しません。アセンブリは信頼されたコードなので、インターフェイスを公開するか、コンストラクタを内部として保持することが最善の方法です。

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


0

これは、「できるからといって、そうする必要があるわけではない」という別の解決策です...

これは、ビジネスオブジェクトコンストラクターをプライベートに保ち、ファクトリロジックを別のクラスに配置するという要件を満たしています。その後、それは少し大ざっぱになります。

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

ファクトリは抽象的であるため、実際にインスタンスを作成することはできません(これはビジネスオブジェクトにもなるため、奇妙です)。プライベートコンストラクターがあるため、クライアントコードはそこから派生できません。

防止されていないのはクライアントコードでありますビジネスオブジェクトクラスから派生し、保護された(ただし検証されていない)静的構築メソッドを呼び出すことです。さらに悪いことに、ファクトリクラスを最初からコンパイルするために追加しなければならない、保護されたデフォルトコンストラクターを呼び出しています。(偶然に、ファクトリー・クラスとビジネス・オブジェクト・クラスを分離するパターンに問題がある可能性があります。)

私は、正しい考えを持つ人がこのようなことをするように提案するつもりはありませんが、興味深い演習でした。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

このソリューションについての考えを聞いていただければ幸いです。「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);
            }
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.