97

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

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

4

17 に答える 17

69

コンストラクタを非公開にし、ファクトリをネストされた型にすることができます。

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

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

これが機能するのは、入れ子になった型が、それを囲む型のプライベート メンバーにアクセスできるためです。少し制限があることは承知していますが、役に立てば幸いです...

于 2009-02-05T10:18:28.253 に答える
56

オブジェクトを作成する前にいくつかのビジネス ロジックを実行したいだけのように見えます。では、「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);
于 2009-02-05T14:06:02.397 に答える
15

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

于 2009-02-05T10:17:19.783 に答える
7

Jonが提案したこととは別に、ファクトリメソッド(チェックを含む)を最初にBusinessObjectの静的メソッドにすることもできます。次に、コンストラクターを非公開にすると、他のすべての人は強制的に静的メソッドを使用するようになります。

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

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

于 2009-02-05T10:29:57.107 に答える
4

もう 1 つの (軽量) オプションは、BusinessObject クラスで静的ファクトリ メソッドを作成し、コンストラクターを非公開にすることです。

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

    private BusinessObject()
    {
    }
}
于 2009-02-05T10:31:47.977 に答える
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.

于 2011-11-08T09:41:42.390 に答える
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!
    }
于 2015-04-23T00:32:35.267 に答える
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;
    }
}

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

于 2009-02-05T11:10:41.690 に答える
1

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

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();
  }
}
于 2009-02-05T13:08:46.567 に答える
1

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

  • ファクトリ クラスをプライベートに構築されたクラスにネストすると、ファクトリは 1 つのクラスのみを構築できます。その時点で、Createメソッドとプライベート ctor を使用したほうがよいでしょう。
  • 継承と保護された 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);
    }
}
于 2016-04-22T02:32:51.577 に答える
0

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

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

于 2012-12-11T07:59:37.077 に答える
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()
    {
    }
}
于 2014-07-09T09:02:04.843 に答える