C#의 팩토리 패턴:객체 인스턴스가 팩토리 클래스에서만 생성될 수 있도록 하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/515269

문제

최근에 나는 내 코드의 일부를 보호하는 것에 대해 생각하고 있습니다.객체를 직접 생성할 수 없고 팩토리 클래스의 일부 메서드를 통해서만 생성할 수 있는지 어떻게 확인할 수 있는지 궁금합니다."비즈니스 개체" 클래스가 있고 이 클래스의 모든 인스턴스가 유효한 내부 상태를 갖는지 확인하고 싶다고 가정해 보겠습니다.이를 달성하려면 객체를 생성하기 전에 생성자에서 몇 가지 검사를 수행해야 합니다.이 수표를 비즈니스 로직의 일부로 만들기로 결정하기 전까지는 괜찮습니다.그렇다면 비즈니스 로직 클래스의 일부 메소드를 통해서만 비즈니스 객체를 생성할 수 있고 직접적으로는 생성할 수 없도록 하려면 어떻게 해야 합니까?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 인스턴스를 직접 생성할 수 있다는 점을 기억하기 전까지는 아무 문제가 없습니다.나는 그러한 기술적 가능성을 완전히 배제하고 싶습니다.

그렇다면 커뮤니티에서는 이에 대해 어떻게 생각하나요?

도움이 되었습니까?

해결책

객체를 만들기 전에 비즈니스 로직을 실행하고 싶을 것 같습니다. 왜 더러운 "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);

다른 팁

생성자를 비공개로 만들 수 있고 공장을 중첩 된 유형으로 만들 수 있습니다.

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

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

중첩 된 유형은 동봉 유형의 개인 구성원에게 액세스 할 수 있기 때문에 작동합니다. 조금 제한적이라는 것을 알고 있지만 도움이되기를 바랍니다.

또는, 당신이 정말로 화려하게 가고 싶다면, 반전 통제 : 클래스가 공장을 반환하고, 수업을 만들 수있는 대의원과 함께 공장을 기기로 반환하도록하십시오.

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

:)

MyBusinessObjectClass 클래스 내부에서 생성자를 만들어서 공장과 공장을 자신의 조립으로 옮길 수 있습니다. 이제 공장만이 클래스의 인스턴스를 구성 할 수 있어야합니다.

Jon이 제안한 것 외에도 공장 방법 (수표 포함)을 처음부터 정적 인 BusinessObject의 정적 방법이 될 수도 있습니다. 그런 다음 생성자를 비공개로두면 다른 모든 사람들이 정적 방법을 사용해야합니다.

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

그러나 실제 질문은 - 왜이 요구 사항이 있습니까? 공장이나 공장 방법을 클래스로 옮기는 것이 허용됩니까?

또 다른 (가벼운) 옵션은 BusinessObject 클래스에서 정적 공장 메소드를 만들고 생성자를 비공개로 유지하는 것입니다.

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

    private BusinessObject()
    {
    }
}

오랜 세월이 흐른 후 이 질문을 받게 되었고, 제가 본 모든 답변은 불행히도 직접적인 답변을 제공하는 대신 코드를 어떻게 수행해야 하는지 알려주는 것이었습니다.당신이 찾고 있던 실제 대답은 클래스에 비공개 생성자를 사용하고 공개 인스턴스화기를 사용하는 것입니다. 즉, 다른 기존 인스턴스에서만 새 인스턴스를 만들 수 있다는 의미입니다.공장에서만 사용할 수 있는 것:

수업을 위한 인터페이스:

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

그런 다음 인스턴스화를 위해 추가 매개변수가 필요하거나 생성한 인스턴스를 전처리하기 위해 이 코드를 쉽게 수정할 수 있습니다.그리고 이 코드를 사용하면 클래스 생성자가 비공개이므로 팩토리를 통해 인스턴스화를 강제로 수행할 수 있습니다.

따라서 내가 원하는 것은 "순수한"방식으로 할 수없는 것처럼 보입니다. 그것은 항상 논리 클래스에 어떤 종류의 "호출"입니다.

어쩌면 간단한 방식으로 할 수 있습니다. 객체 클래스에서 대조적 메소드를 작성하여 먼저 로직 클래스를 호출하여 입력을 확인하십시오.

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

이런 식으로 비즈니스 객체는 직접 생성되지 않으며 비즈니스 로직의 공개 점검 방법은 해를 끼치 지 않습니다.

인터페이스와 구현 사이의 좋은 분리의 경우
보호 된-건설자-공개-이니티얼 라이저 패턴은 매우 깔끔한 솔루션을 허용합니다.

비즈니스 개체가 주어진 :

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

공장을 도메인 클래스와 같은 어셈블리에 넣고 도메인 클래스의 생성자 내부를 표시합니다. 이런 식으로 도메인의 모든 클래스는 인스턴스를 생성 할 수 있지만 자신을 신뢰하지 않습니까? 도메인 레이어 외부에 코드를 작성하는 사람은 공장을 사용해야합니다.

public class Person
{
  internal Person()
  {
  }
}

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

그러나 나는 당신의 접근 방식에 의문을 제기해야합니다 :-)

나는 당신이 당신의 사람 클래스가 생성시 유효하기를 원한다면, 당신은 코드를 생성자에 넣어야한다고 생각합니다.

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

이 솔루션은 기반입니다 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 클래스를 구성 할 수 있습니다. 그 시점에서 당신은 a로 더 좋습니다 Create 방법 및 개인 CTOR.
  • 상속 및 보호 된 CTOR를 사용하면 동일한 문제가 있습니다.

공개 생성자가있는 개인 중첩 클래스가 포함 된 부분 클래스로 공장을 제안하고 싶습니다. 당신은 당신의 공장이 구성하고있는 객체를 100% 숨기고 있고 하나 또는 여러 인터페이스를 통해 선택한 것을 노출시키는 것만을 숨기고 있습니다.

내가 들었던 유스 케이스는 공장에서 인스턴스의 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);
    }
}

왜 "비즈니스 로직"을 "비즈니스 객체"와 분리하려는 이유를 이해하지 못합니다. 이것은 객체 방향의 왜곡처럼 들리며, 그 접근 방식을 취함으로써 매듭으로 자신을 묶게됩니다.

나는 문제보다 더 나쁘지 않은 솔루션이 있다고 생각하지 않습니다. 위의 모든 사람들은 IMHO가 더 나쁜 문제이며 사람들이 공장에 전화를 걸어 객체를 사용하는 것을 막지 않는 공개 정적 공장을 요구합니다. 아무것도 숨기지 않습니다. 어셈블리가 신뢰할 수있는 코드이기 때문에 최상의 보호 장치 인 경우 인터페이스를 노출하거나 생성자를 내부로 유지하는 것이 가장 좋습니다.

한 가지 옵션은 IOC 컨테이너와 같은 어딘가에 공장을 등록하는 정적 생성자가있는 것입니다.

다음은 "당신이해야한다는 의미는 아니기 때문에"라는 정맥의 또 다른 해결책입니다.

비즈니스 객체 생성자를 개인을 비공개로 유지하고 다른 클래스에 공장 논리를 넣는 요구 사항을 충족합니다. 그 후 약간 스케치됩니다.

공장 클래스에는 비즈니스 객체를 만드는 정적 방법이 있습니다. 개인 생성자를 호출하는 정적 보호 구조 방법에 액세스하기 위해 비즈니스 객체 클래스에서 파생됩니다.

공장은 추상적이므로 실제로 인스턴스를 만들 수 없기 때문에 (비즈니스 객체 가기 때문에 이상 할 수 있기 때문에) 개인 생성자가 있으므로 클라이언트 코드가이를 도출 할 수 없습니다.

예방되지 않은 것은 클라이언트 코드입니다 또한 비즈니스 객체 클래스에서 파생되고 보호 된 (그러나 검증되지 않은) 정적 구성 방법을 호출합니다. 또는 더 나쁜 것은 보호 된 기본 생성자를 호출하여 공장 클래스를 처음에 컴파일하기 위해 추가해야했습니다. (우연히 공장 클래스를 비즈니스 객체 클래스와 분리하는 모든 패턴에 문제가 될 수 있습니다.)

나는 올바른 마음을 가진 사람이 이런 일을해야한다고 제안하려고하지는 않지만 흥미로운 운동이었습니다. 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()
    {
    }
}

이 솔루션에 대한 몇 가지 생각을 듣는 것에 감사 할 것입니다. '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);
            }
        }
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top