development

생성자 서명을 정의하는 인터페이스?

big-blog 2020. 2. 11. 22:32
반응형

생성자 서명을 정의하는 인터페이스?


이것이 처음 으로이 문제에 부딪힌 것은 이상한 일이지만,

C # 인터페이스에서 생성자를 어떻게 정의합니까?

편집
일부 사람들은 예를 원했습니다 (자유 시간 프로젝트이므로 게임입니다).

IDrawable
+ 업데이트
+ 그리기

업데이트 (화면 가장자리 확인 등)하고 자체를 그릴 수 있으려면 항상가 필요합니다 GraphicsDeviceManager. 따라서 객체에 대한 참조가 있는지 확인하고 싶습니다. 이것은 생성자에 속합니다.

이제 이것을 작성 했으므로 여기에서 구현 IObservable하고있는 것이 있다고 생각 GraphicsDeviceManager합니다 IDrawable... XNA 프레임 워크를 얻지 못하거나 프레임 워크가 잘 생각되지 않는 것 같습니다.

편집
인터페이스의 맥락에서 생성자에 대한 정의에 혼란이있는 것 같습니다. 인터페이스는 실제로 인스턴스화 할 수 없으므로 생성자가 필요하지 않습니다. 내가 정의하고 싶은 것은 생성자의 서명이었습니다. 인터페이스가 특정 메소드의 서명을 정의 할 수있는 것처럼 인터페이스는 생성자의 서명을 정의 할 수 있습니다.


이미 언급했듯이 인터페이스에 생성자를 가질 수 없습니다. 그러나 이것은 약 7 년 후 Google에서 매우 높은 순위의 결과이므로 여기에 칩을 넣을 것이라고 생각했습니다. 특히 기존 인터페이스와 함께 추상 기본 클래스를 사용하고 리팩토링 양을 줄일 수있는 방법을 보여줍니다. 비슷한 상황에서 앞으로 필요할 것입니다. 이 개념은 일부 의견에서 이미 암시되었지만 실제로 실제로 수행하는 방법을 보여줄 가치가 있다고 생각했습니다.

따라서 지금까지 다음과 같은 기본 인터페이스가 있습니다.

public interface IDrawable
{
    void Update();
    void Draw();
}

이제 생성하려는 생성자로 추상 클래스를 만듭니다. 실제로, 원래 질문을 작성한 시점부터 사용할 수 있기 때문에 여기서는 약간의 공상을 얻고이 상황에서 제네릭을 사용하여 동일한 기능이 필요하지만 생성자 요구 사항이 다른 다른 인터페이스에 적용 할 수 있습니다.

public abstract class MustInitialize<T>
{
    public MustInitialize(T parameters)
    {

    }
}

이제 IDrawable 인터페이스와 MustInitialize 추상 클래스에서 상속하는 새 클래스를 작성해야합니다.

public class Drawable : MustInitialize<GraphicsDeviceManager>, IDrawable
{
    GraphicsDeviceManager _graphicsDeviceManager;

    public Drawable(GraphicsDeviceManager graphicsDeviceManager)
        : base (graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }

    public void Update()
    {
        //use _graphicsDeviceManager here to do whatever
    }

    public void Draw()
    {
        //use _graphicsDeviceManager here to do whatever
    }
}

그런 다음 Drawable 인스턴스를 생성하면 좋습니다.

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

여기서 멋진 점은 우리가 만든 새로운 Drawable 클래스가 여전히 IDrawable에서 기대하는 것과 같이 작동한다는 것입니다.

MustInitialize 생성자에 둘 이상의 매개 변수를 전달해야하는 경우 전달해야하는 모든 필드의 속성을 정의하는 클래스를 만들 수 있습니다.


당신은 할 수 없습니다. 때때로 고통 스럽지만 어쨌든 정상적인 기술을 사용하여 호출 할 수는 없습니다.

블로그 게시물 에서 일반 유형 제약 조건에서만 사용할 수 있지만 실제로는 편리한 IMO 정적 인터페이스제안했습니다 .

인터페이스 내에서 생성자를 정의 있는지에 대한 한 가지 점은 클래스를 파생시키는 데 문제가있는 것입니다.

public class Foo : IParameterlessConstructor
{
    public Foo() // As per the interface
    {
    }
}

public class Bar : Foo
{
    // Yikes! We now don't have a parameterless constructor...
    public Bar(int x)
    {
    }
}

인터페이스 생성자에 또 다른 문제가 있음을 보여주는 매우 늦은 기여. (문제에 대한 명확한 표현이 있기 때문에이 질문을 선택합니다). 우리가 가질 수 있다고 가정하십시오 :

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

class Person : IPerson, ICustomer
{
    Person(string name) { }
    Person(DateTime registrationDate) { }
}

관례 적으로 "인터페이스 생성자"의 구현은 유형 이름으로 대체됩니다.

이제 인스턴스를 만듭니다.

ICustomer a = new Person("Ernie");

계약 ICustomer을 준수 한다고 말할 수 있습니까?

그리고 이것에 대해서는 :

interface ICustomer
{
    ICustomer(string address);
}

당신은 할 수 없습니다.

인터페이스는 다른 개체가 구현하는 계약을 정의하므로 초기화해야 할 상태가 없습니다.

초기화해야 할 상태가있는 경우 대신 추상 기본 클래스 사용을 고려해야합니다.


이 인터페이스가 정의하는 생성자를 만들 수 없습니다, 그러나 이다 나는 그렇게 확신 실제로 아니다 ...하지만 종류가 paramerterless 생성자를 강제한다는 인터페이스를 정의가 매우 추한 구문이 사용 제네릭이 될 수 정말 좋은 코딩 패턴입니다.

public interface IFoo<T> where T : new()
{
  void SomeMethod();
}

public class Foo : IFoo<Foo>
{
  // This will not compile
  public Foo(int x)
  {

  }

  #region ITest<Test> Members

  public void SomeMethod()
  {
    throw new NotImplementedException();
  }

  #endregion
}

반면에 유형에 매개 변수가없는 생성자가 있는지 테스트하려는 경우 리플렉션을 사용하여 수행 할 수 있습니다.

public static class TypeHelper
{
  public static bool HasParameterlessConstructor(Object o)
  {
    return HasParameterlessConstructor(o.GetType());
  }

  public static bool HasParameterlessConstructor(Type t)
  {
    // Usage: HasParameterlessConstructor(typeof(SomeType))
    return t.GetConstructor(new Type[0]) != null;
  }
}

도움이 되었기를 바랍니다.


나는이 질문을 되돌아 보았고 나 자신에게 생각했을 것입니다. 아마도 우리는이 문제를 잘못된 길로 이끌고 있습니다. 인터페이스는 특정 매개 변수로 생성자를 정의하는 데 도움이되지 않을 수도 있지만 (추상적 인) 기본 클래스입니다.

필요한 매개 변수를 허용하는 생성자를 사용하여 기본 클래스를 만드는 경우이 클래스에서 파생 된 모든 클래스는이를 제공해야합니다.

public abstract class Foo
{
  protected Foo(SomeParameter x)
  {
    this.X = x;
  }

  public SomeParameter X { get; private set }
}

public class Bar : Foo // Bar inherits from Foo
{
  public Bar() 
    : base(new SomeParameter("etc...")) // Bar will need to supply the constructor param
  {
  }
}

내가 찾은이 문제를 해결하는 한 가지 방법은 별도의 공장으로 건축을 분리하는 것입니다. 예를 들어 IQueueItem이라는 추상 클래스가 있으며 해당 객체를 다른 객체 (CloudQueueMessage)와 상호 변환하는 방법이 필요합니다. 따라서 IQueueItem 인터페이스에는 다음이 있습니다.

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

이제 실제 큐 클래스가 CloudQueueMessage를 다시 IQueueItem으로 변환하는 방법이 필요합니다. 즉, IQueueItem objMessage = ItemType.FromMessage와 같은 정적 구성이 필요합니다. 대신 다른 인터페이스 IQueueFactory를 정의했습니다.

public interface IQueueItemFactory<T> where T : IQueueItem
{
    T FromMessage(CloudQueueMessage objMessage);
}

이제 마지막으로 new () 제약없이 일반 대기열 클래스를 작성할 수 있습니다. 제 경우에는 주요 문제였습니다.

public class AzureQueue<T> where T : IQueueItem
{
    private IQueueItemFactory<T> _objFactory;
    public AzureQueue(IQueueItemFactory<T> objItemFactory)
    {
        _objFactory = objItemFactory;
    }


    public T GetNextItem(TimeSpan tsLease)
    {
        CloudQueueMessage objQueueMessage = _objQueue.GetMessage(tsLease);
        T objItem = _objFactory.FromMessage(objQueueMessage);
        return objItem;
    }
}

이제 기준을 충족하는 인스턴스를 만들 수 있습니다.

 AzureQueue<Job> objJobQueue = new JobQueue(new JobItemFactory())

희망적으로 이것은 언젠가 다른 누군가를 도울 수 있기를 바랍니다. 문제와 해결책을 보여주기 위해 많은 내부 코드가 제거되었습니다.


일반적인 공장 접근 방식은 여전히 ​​이상적입니다. 팩토리에는 매개 변수가 필요하다는 것을 알았을 때 해당 매개 변수가 인스턴스화되는 객체의 생성자에게 전달됩니다.

참고, 이것은 구문 검증 된 의사 코드이며, 여기에 누락 된 런타임 경고가있을 수 있습니다.

public interface IDrawableFactory
{
    TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
              where TDrawable: class, IDrawable, new();
}

public class DrawableFactory : IDrawableFactory
{
    public TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
                     where TDrawable : class, IDrawable, new()
    {
        return (TDrawable) Activator
                .CreateInstance(typeof(TDrawable), 
                                graphicsDeviceManager);
    }

}

public class Draw : IDrawable
{
 //stub
}

public class Update : IDrawable {
    private readonly GraphicsDeviceManager _graphicsDeviceManager;

    public Update() { throw new NotImplementedException(); }

    public Update(GraphicsDeviceManager graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }
}

public interface IDrawable
{
    //stub
}
public class GraphicsDeviceManager
{
    //stub
}

가능한 사용법의 예 :

    public void DoSomething()
    {
        var myUpdateObject = GetDrawingObject<Update>(new GraphicsDeviceManager());
        var myDrawObject = GetDrawingObject<Draw>(null);
    }

물론, 팩토리를 통한 인스턴스 생성만으로 항상 적절하게 초기화 된 객체를 보장 할 수 있습니다. 아마도 AutoFac 과 같은 의존성 주입 프레임 워크를 사용하는 것이 합리적 일 것입니다. Update ()는 새 GraphicsDeviceManager 객체에 대해 IoC 컨테이너를 "요청"할 수 있습니다.


이 문제를 해결하는 한 가지 방법은 제네릭과 new () 제약 조건을 활용하는 것입니다.

생성자를 메서드 / 함수로 표현하는 대신 팩토리 클래스 / 인터페이스로 표현할 수 있습니다. 클래스의 객체를 만들어야하는 모든 호출 사이트에 new () 일반 제약 조건을 지정하면 생성자 인수를 적절하게 전달할 수 있습니다.

IDrawable 예제의 경우 :

public interface IDrawable
{
    void Update();
    void Draw();
}

public interface IDrawableConstructor<T> where T : IDrawable
{
    T Construct(GraphicsDeviceManager manager);
}


public class Triangle : IDrawable
{
    public GraphicsDeviceManager Manager { get; set; }
    public void Draw() { ... }
    public void Update() { ... }
    public Triangle(GraphicsDeviceManager manager)
    {
        Manager = manager;
    }
}


public TriangleConstructor : IDrawableConstructor<Triangle>
{
    public Triangle Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 
}

이제 당신이 그것을 사용할 때 :

public void SomeMethod<TBuilder>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<Triangle>, new()
{
    // If we need to create a triangle
    Triangle triangle = new TBuilder().Construct(manager);

    // Do whatever with triangle
}

명시 적 인터페이스 구현을 사용하여 모든 생성 방법을 단일 클래스로 집중할 수도 있습니다.

public DrawableConstructor : IDrawableConstructor<Triangle>,
                             IDrawableConstructor<Square>,
                             IDrawableConstructor<Circle>
{
    Triangle IDrawableConstructor<Triangle>.Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 

    Square IDrawableConstructor<Square>.Construct(GraphicsDeviceManager manager)
    {
        return new Square(manager);
    } 

    Circle IDrawableConstructor<Circle>.Construct(GraphicsDeviceManager manager)
    {
        return new Circle(manager);
    } 
}

그것을 사용하려면 :

public void SomeMethod<TBuilder, TShape>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<TShape>, new()
{
    // If we need to create an arbitrary shape
    TShape shape = new TBuilder().Construct(manager);

    // Do whatever with the shape
}

다른 방법은 람다 식을 초기화 자로 사용하는 것입니다. 호출 계층의 초기에 어느 시점에 인스턴스화해야하는지 (즉, GraphicsDeviceManager 객체에 대한 참조를 만들거나 가져올 때) 알 수 있습니다. 가져가 자마자 람다를 통과하십시오.

() => new Triangle(manager) 

이후의 방법으로 삼각형을 만드는 방법을 알 수 있습니다. 필요한 모든 가능한 메소드를 결정할 수없는 경우 리플렉션을 사용하여 IDrawable을 구현하는 유형의 사전을 항상 작성하고 공유 위치에 저장하거나 전달할 수있는 사전에 위에 표시된 람다 표현식을 등록 할 수 있습니다. 추가 함수 호출.


제네릭 트릭 으로이 작업을 수행 할 수 있지만 Jon Skeet이 작성한 것에 여전히 취약합니다.

public interface IHasDefaultConstructor<T> where T : IHasDefaultConstructor<T>, new()
{
}

이 인터페이스를 구현하는 클래스에는 매개 변수가없는 생성자가 있어야합니다.

public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
    public A(int a) { } //compile time error
}

인터페이스의 목적은 특정 객체 서명을 적용하는 것입니다. 객체가 내부적으로 어떻게 작동하는지는 명시 적으로 염려해서는 안됩니다. 따라서 인터페이스의 생성자는 개념적인 관점에서 실제로 의미가 없습니다.

그래도 몇 가지 대안이 있습니다.

  • 최소 기본 구현으로 작동하는 추상 클래스를 작성하십시오. 해당 클래스에는 클래스 구현에 필요한 생성자가 있어야합니다.

  • 오버 킬에 신경 쓰지 않으면 AbstractFactory 패턴을 사용하고 필요한 서명이있는 팩토리 클래스 인터페이스에서 메소드를 선언하십시오.

  • 전달 GraphicsDeviceManager받는 매개 변수로 UpdateDraw방법.

  • 컴포지션 객체 지향 프로그래밍 프레임 워크를 사용하여 GraphicsDeviceManager필요한 객체 부분 으로 전달하십시오 . 이것은 제 생각에는 꽤 실험적인 해결책입니다.

설명하는 상황은 일반적으로 다루기가 쉽지 않습니다. 유사한 경우는 데이터베이스에 액세스해야하는 비즈니스 응용 프로그램의 엔터티입니다.


당신은하지 않습니다.

생성자는 인터페이스를 구현할 수있는 클래스의 일부입니다. 인터페이스는 클래스가 구현해야하는 메소드의 계약일뿐입니다.


인터페이스에서 생성자를 정의 할 수 있으면 매우 유용합니다.

인터페이스는 지정된 방식으로 사용해야하는 계약입니다. 다음 시나리오는 일부 시나리오에서 실행 가능한 대안 일 수 있습니다.

public interface IFoo {

    /// <summary>
    /// Initialize foo.
    /// </summary>
    /// <remarks>
    /// Classes that implement this interface must invoke this method from
    /// each of their constructors.
    /// </remarks>
    /// <exception cref="InvalidOperationException">
    /// Thrown when instance has already been initialized.
    /// </exception>
    void Initialize(int a);

}

public class ConcreteFoo : IFoo {

    private bool _init = false;

    public int b;

    // Obviously in this case a default value could be used for the
    // constructor argument; using overloads for purpose of example

    public ConcreteFoo() {
        Initialize(42);
    }

    public ConcreteFoo(int a) {
        Initialize(a);
    }

    public void Initialize(int a) {
        if (_init)
            throw new InvalidOperationException();
        _init = true;

        b = a;
    }

}

일종의 생성자를 강요하는 한 가지 방법 Getters은 인터페이스 에서만 선언 private하는 것이므로 구현 클래스에는 값을 설정하기 위해 이상적인 생성자가있는 메소드가 있어야 함을 의미 할 수 있습니다.


인터페이스에서 생성자 서명을 정의 할 수는 없지만 추상 클래스를 고려할 때 주목할 가치가 있다고 생각합니다. 추상 클래스는 구현되지 않은 (추상적 인) 메소드 시그니처를 인터페이스와 동일한 방식으로 정의 할 수 있지만 구현 된 (구체적인) 메소드와 생성자를 가질 수도 있습니다.

단점은 클래스 유형이기 때문에 인터페이스가 할 수있는 다중 상속 유형 시나리오에 사용할 수 없다는 것입니다.


나는 다음 패턴을 사용하여 방탄을 만듭니다.

  • 기본 클래스에서 클래스를 파생시키는 개발자는 실수로 공개 액세스 가능한 생성자를 만들 수 없습니다.
  • 최종 클래스 개발자는 일반적인 create 메소드를 수행해야합니다.
  • 모든 것이 유형 안전하며 캐스팅이 필요하지 않습니다.
  • 100 % 융통성이 있으며 어디에서나 재사용 할 수있어 자신 만의 기본 클래스를 정의 할 수 있습니다.
  • 기본 클래스를 수정하지 않고 중단 할 수는 없습니다 (오류 플래그가 true로 설정되지 않은 폐기 플래그를 정의하는 경우를 제외하고는 경고로 끝납니다)

        public abstract class Base<TSelf, TParameter>
        where TSelf : Base<TSelf, TParameter>, new()
    {
        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TSelf Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);
    
            return me;
        }
    
        [Obsolete(FactoryMessage, true)]
        protected Base()
        {
        }
    
    
    
        protected virtual void Initialize(TParameter parameter)
        {
    
        }
    }
    
    public abstract class BaseWithConfig<TSelf, TConfig>: Base<TSelf, TConfig>
        where TSelf : BaseWithConfig<TSelf, TConfig>, new()
    {
        public TConfig Config { get; private set; }
    
        [Obsolete(FactoryMessage, true)]
        protected BaseWithConfig()
        {
        }
        protected override void Initialize(TConfig parameter)
        {
            this.Config = parameter;
        }
    }
    
    public class MyService : BaseWithConfig<MyService, (string UserName, string Password)>
    {
        [Obsolete(FactoryMessage, true)]
        public MyService()
        {
        }
    }
    
    public class Person : Base<Person, (string FirstName, string LastName)>
    {
        [Obsolete(FactoryMessage,true)]
        public Person()
        {
        }
    
        protected override void Initialize((string FirstName, string LastName) parameter)
        {
            this.FirstName = parameter.FirstName;
            this.LastName = parameter.LastName;
        }
    
        public string LastName { get; private set; }
    
        public string FirstName { get; private set; }
    }
    
    
    
    [Test]
    public void FactoryTest()
    {
        var notInitilaizedPerson = new Person(); // doesn't compile because of the obsolete attribute.
        Person max = Person.Create(("Max", "Mustermann"));
        Assert.AreEqual("Max",max.FirstName);
    
        var service = MyService.Create(("MyUser", "MyPassword"));
        Assert.AreEqual("MyUser", service.Config.UserName);
    }
    

편집 : 여기에 인터페이스 추상화를 적용하는 드로잉 예제를 기반으로 한 예제가 있습니다.

        public abstract class BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithAbstraction<TSelf, TInterface, TParameter>, TInterface, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected BaseWithAbstraction()
        {
        }

        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TInterface Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);

            return me;
        }

        protected virtual void Initialize(TParameter parameter)
        {

        }
    }



    public abstract class BaseWithParameter<TSelf, TInterface, TParameter> : BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithParameter<TSelf, TInterface, TParameter>, TInterface, new()
    {
        protected TParameter Parameter { get; private set; }

        [Obsolete(FactoryMessage, true)]
        protected BaseWithParameter()
        {
        }
        protected sealed override void Initialize(TParameter parameter)
        {
            this.Parameter = parameter;
            this.OnAfterInitialize(parameter);
        }

        protected virtual void OnAfterInitialize(TParameter parameter)
        {
        }
    }


    public class GraphicsDeviceManager
    {

    }
    public interface IDrawable
    {
        void Update();
        void Draw();
    }

    internal abstract class Drawable<TSelf> : BaseWithParameter<TSelf, IDrawable, GraphicsDeviceManager>, IDrawable 
        where TSelf : Drawable<TSelf>, IDrawable, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected Drawable()
        {
        }

        public abstract void Update();
        public abstract void Draw();
    }

    internal class Rectangle : Drawable<Rectangle>
    {
        [Obsolete(FactoryMessage, true)]
        public Rectangle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }
    internal class Circle : Drawable<Circle>
    {
        [Obsolete(FactoryMessage, true)]
        public Circle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }


    [Test]
    public void FactoryTest()
    {
        // doesn't compile because interface abstraction is enforced.
        Rectangle rectangle = Rectangle.Create(new GraphicsDeviceManager());

        // you get only the IDrawable returned.
        IDrawable service = Circle.Create(new GraphicsDeviceManager());
    }

OP를 올바르게 이해하면 클래스를 구현하여 GraphicsDeviceManager가 항상 초기화되는 계약을 시행하려고합니다. 비슷한 문제가 있었고 더 나은 해결책을 찾고 있었지만 이것이 내가 생각할 수있는 최선입니다.

SetGraphicsDeviceManager (GraphicsDeviceManager gdo)를 인터페이스에 추가하면 구현 클래스가 생성자의 호출이 필요한 로직을 작성하도록 강제됩니다.

참고 URL : https://stackoverflow.com/questions/619856/interface-defining-a-constructor-signature



반응형