Programing

C #의 팩토리 패턴 : 팩토리 클래스에서만 개체 인스턴스를 만들 수 있는지 확인하는 방법은 무엇입니까?

lottogame 2020. 9. 15. 19:08
반응형

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 인스턴스를 직접 만들 수 있다는 것을 기억할 때까지는 괜찮습니다. 나는 그 기술적 가능성을 완전히 배제하고 싶습니다.

그렇다면 커뮤니티는 이것에 대해 어떻게 생각합니까?


개체를 만들기 전에 일부 비즈니스 논리를 실행하려는 것 같습니다. 따라서 모든 더러운 "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()
    {
    }
}

수년이 지난 후이 질문을 받았으며 내가 보는 모든 대답은 불행히도 직접적인 대답을 제공하는 대신 코드를 수행해야하는 방법을 알려줍니다. 당신이 찾던 실제 대답은 당신의 클래스를 private 생성자이지만 public 인스턴스화자가있는 것입니다. 즉, 팩토리에서만 사용할 수있는 다른 기존 인스턴스에서만 새 인스턴스를 만들 수 있습니다.

수업을위한 인터페이스 :

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()이니셜 라이저 를 호출하려면 구체적인 비즈니스 팩토리에 대한 참조가 필요합니다 . 그러나 필요한 참조를 가진 유일한 사람은 비즈니스 공장 자체입니다.

우리가 원하는 것을 얻었습니다 . 만들 수있는 유일한 사람 BusinessObjectBusinessFactory.


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

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

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 개의 클래스 만 생성 할 수 있습니다. 그 시점에서 당신은 Create방법과 개인 배우를 사용하는 것이 좋습니다 .
  • 상속과 보호 된 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);
            }
        }
    }
}

참고 URL : https://stackoverflow.com/questions/515269/factory-pattern-in-c-how-to-ensure-an-object-instance-can-only-be-created-by-a

반응형