c# شرح - واجهة تعريف توقيع منشئ؟




abstract class (14)

من الغريب أن هذه هي المرة الأولى التي أصطدم فيها بهذه المشكلة ، ولكن:

كيف تحدد منشئ في واجهة C #؟

تصحيح
بعض الناس أرادوا مثالاً (إنه مشروع وقت حر ، لذلك نعم ، إنها لعبة)

IDrawable
+ تحديث
+ رسم

لتكون قادرة على تحديث (تحقق من حافة الشاشة وما إلى ذلك) ورسم نفسها ستحتاج دائما إلى GraphicsDeviceManager . لذلك أريد التأكد من أن الكائن يحتوي على إشارة إليه. هذا من شأنه أن ينتمي إلى المنشئ.

الآن بعد أن كتبت هذا لأسفل أعتقد أن ما أقوم بتنفيذه هنا هو IObservable و GraphicsDeviceManager يجب أن يأخذ IDrawable ... يبدو أنني لا أحصل على إطار XNA ، أو الإطار غير مدروس بشكل جيد.

تصحيح
يبدو أن هناك بعض الالتباس حول تعريفي للمنشئ في سياق الواجهة. واجهة لا يمكن في الواقع أن يتم إنشاء مثيل لذلك لا يحتاج إلى منشئ. ما أردت تعريفه هو التوقيع على منشئ. تماما مثل واجهة يمكن تحديد التوقيع على طريقة معينة ، يمكن للواجهة تعريف توقيع منشئ.


Answers

ليس من الممكن إنشاء واجهة تعيّن المنشئات ، ولكن من الممكن تعريف واجهة تفرض نوعًا ما على أن يكون لها مُنشئ شبه جامعي ، على الرغم من أنها بنية قبيحة جدًا تستخدم الأدوية الجنسية ... أنا في الواقع لست متأكدًا من ذلك إنه حقا نمط ترميز جيد.

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

مساهمة متأخرة جداً تظهر مشكلة أخرى مع المنشئات المتداخلة. (اخترت هذا السؤال لأنه يحتوي على أوضح تعبير للمشكلة). لنفترض أننا يمكن أن يكون لدينا:

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

كما لاحظت بالفعل ، لا يمكن أن يكون لديك constructors على واجهة. ولكن نظرًا لأن هذه النتيجة عالية الجودة في Google بعد مرور 7 سنوات ، فكرت في أن أقوم بتشكيلها هنا - لا سيما لإظهار كيف يمكنك استخدام فئة أساسية مجردة جنبًا إلى جنب مع واجهة القائمة الحالية وربما خفض كمية إعادة البناء. هناك حاجة في المستقبل لحالات مماثلة. لقد تم التلميح بالفعل إلى هذا المفهوم في بعض التعليقات ، لكنني أعتقد أنه سيكون من المفيد إظهار كيفية القيام بذلك بالفعل.

إذاً لديك الواجهة الرئيسية التي تشبه هذا حتى الآن:

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

الشيء الرائع هنا هو أن الطبقة القابلة للدروس الجديدة التي أنشأناها لا تزال تتصرف تمامًا مثل ما نتوقعه من عدد غير معروف.

إذا كنت بحاجة إلى تمرير أكثر من معلمة واحدة إلى منشئ MustInitialize ، فيمكنك إنشاء فئة تحدد خصائص جميع الحقول التي ستحتاج إلى تمريرها.


لا يزال نهج المصنع العام يبدو مثاليا. ستعرف أن المصنع يتطلب معلمة ، وسيحدث فقط أن يتم تمرير هذه المعلمات إلى منشئ الكائن الذي يتم إنشاء مثيل.

ملاحظة ، هذا هو مجرد رمز زائف التحقق من بناء الجملة ، قد يكون هناك تحذير في وقت التشغيل وأنا في عداد المفقودين هنا:

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 منطقيًا ؛ التحديث () يمكن أن "يسأل" حاوية IoC لكائن GraphicsDeviceManager جديد.


لا يمكنك.

تحدد الواجهات العقود التي تنفذها الكائنات الأخرى ، وبالتالي لا يوجد بها حالة تحتاج إلى التهيئة.

إذا كان لديك بعض الحالات التي تحتاج إلى التهيئة ، فعليك التفكير في استخدام فئة أساسية مجردة بدلاً من ذلك.


طريقة واحدة لحل هذه المشكلة التي وجدتها هي فصل البناء إلى مصنع منفصل. على سبيل المثال ، لديّ فئة تجريدية تسمى 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);
}

الآن أستطيع أخيرا أن أكتب صف طابورتي العامة بدون القيد () الجديد الذي كان في قضيتي هو القضية الرئيسية.

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

نأمل أن يساعد هذا شخصًا آخر في يوم من الأيام ، ومن الواضح أنه تمت إزالة الكثير من الشفرات الداخلية لمحاولة إظهار المشكلة والحل


طريقة واحدة لحل هذه المشكلة هي الاستفادة من الأدوية الجنيسة والقيود الجديدة ().

بدلاً من التعبير عن المنشئ كطريقة / وظيفة ، يمكنك التعبير عنه على أنه فئة / واجهة مصنع. إذا قمت بتحديد القيد العام الجديد () على كل موقع مكالمة يحتاج إلى إنشاء كائن من فصلك ، فستتمكن من تمرير وسيطات المنشئ وفقًا لذلك.

للمثال الخاص بك 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) 

إلى الطرق اللاحقة حتى يعرفوا كيفية إنشاء مثلث من ذلك الحين. إذا لم تتمكن من تحديد جميع الطرق الممكنة التي ستحتاج إليها ، فيمكنك دائمًا إنشاء قاموس لأنواع تقوم بتطبيق معرف قابل للسحب باستخدام الانعكاس وتسجيل تعبير lambda الموضح أعلاه في قاموس يمكنك إما تخزينه في موقع مشترك أو المرور إلى مزيد من المكالمات وظيفة.


الغرض من الواجهة هو فرض توقيع كائن معين. يجب أن لا تكون مهتمة بشكل صريح بكيفية عمل كائن داخليًا. لذلك ، المنشئ في واجهة لا معنى له من الناحية المفاهيمية.

هناك بعض البدائل على الرغم من:

  • قم بإنشاء فئة مجردة تعمل كتطبيق افتراضي بسيط. يجب أن يكون لدى هذا الفصل الصانعين الذين تتوقع أن يكون لديهم فصول تنفيذية.

  • إذا كنت لا تمانع في الإفراط في استخدام ، استخدم نمط AbstractFactory وإعلان طريقة في واجهة فئة المصنع التي لديها التوقيعات المطلوبة.

  • قم بتمرير GraphicsDeviceManager كمعلمة إلى أساليب Update and Draw .

  • استخدم إطار عمل البرمجة Object Oriented Programming لتمرير 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;
    }

}

إذا فهمت OP بشكل صحيح ، فإننا نرغب في فرض عقد حيث يتم دائمًا تهيئة GraphicsDeviceManager من خلال تطبيق الفئات. واجهت مشكلة مماثلة وكنت أبحث عن حل أفضل ، ولكن هذا هو أفضل ما يمكنني التفكير فيه:

إضافة SetGraphicsDeviceManager (GraphicsDeviceManager gdo) إلى الواجهة ، وبهذه الطريقة سيتم فرض فئات التنفيذ كتابة منطق والذي سيتطلب استدعاء من مُنشئ.


لا يمكنك. إنه ألم في بعض الأحيان ، ولكنك لن تتمكن من الاتصال به باستخدام تقنيات عادية على أي حال.

في إحدى مشاركات المدونة ، اقترحت واجهات ثابتة لا يمكن استخدامها إلا في قيود النوع العامة - ولكنها قد تكون مفيدة جدًا ، وهي 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)
    {
    }
}

لا ، لا يمكنك استدعاء مُنشئ واحد من آخر في C ++ 03 (يسمى مُنشئ تفويض).

هذا تغير في C ++ 11 (المعروف أيضا بـ C ++ 0x) ، والذي أضاف دعمًا للبناء التالي:
(مثال مأخوذ من Wikipedia )

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};




c# interface constructor