سؤال

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

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

تحرير
بعض الناس يريد مثال (ليس في الوقت المشروع ، لذا نعم, انها لعبة)

IDrawable
+تحديث
+رسم

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

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

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

هل كانت مفيدة؟

المحلول

كما سبق يلاحظ جيدا, لا يمكن أن يكون المنشئات على واجهة.ولكن لأن هذا هو مثل هذه المرتبة العالية نتيجة في جوجل 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);

الشيء الجميل هنا هو أن الجديد Drawable الدرجة أنشأنا لا يزال يتصرف تماما مثل ما كنا نتوقع من IDrawable.

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

نصائح أخرى

لا يمكنك.إنه أحيانا الألم, ولكنك لن تكون قادرا على الاتصال باستخدام العادي التقنيات على أي حال.

في بلوق وظيفة لقد اقترح ثابت واجهات والتي ستكون قابلة للاستخدام في عام نوع من القيود - ولكن يمكن أن يكون مفيد حقا, المنظمة البحرية الدولية.

نقطة واحدة عن إذا كنت يمكن أن تعريف منشئ داخل واجهة, سيكون لديك صعوبة في استخلاص دروس:

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
}

من ناحية أخرى, إذا كنت ترغب في اختبار إذا كان نوع لديه paramerterless منشئ, يمكنك أن تفعل ذلك باستخدام التفكير:

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

ويساعد هذا الأمل.

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

إذا قمت بإنشاء قاعدة الطبقة مع منشئ على أن هناك تقبل المعلمات تحتاج كل فئة derrives من أنه يحتاج إلى إمدادات لهم.

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

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

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 هل معنى له ؛ تحديث() أن "تسأل" اللجنة الأولمبية الدولية حاوية جديدة GraphicsDeviceManager الكائن.

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

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

الخاص بك 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 باستخدام انعكاس وتسجيل امدا التعبير هو مبين أعلاه في القاموس الذي يمكنك إما تخزين في موقع مشترك أو لتمرير مزيد من المكالمات وظيفة.

هل يمكن أن تفعل هذا مع الأدوية خدعة, لكنه لا يزال عرضة ماذا جون السكيت كتب:

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

الفئة التي تطبق هذه الواجهة يجب أن يكون parameterless المنشئ:

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

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

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

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

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

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

  • استخدام التركيبية وجوه المنحى البرمجة إطار لتمرير 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 في واجهة التي يمكن بعد ذلك يعني أن تنفيذ الطبقة يجب أن يكون أسلوب مثالي منشئ لديك قيمة مجموعة (privately) من أجل ذلك.

بينما لا يمكن تحديد منشئ التوقيع في واجهة, أشعر أنه من الجدير بالذكر أن هذا قد يكون مكانا للنظر في فئة مجردة.فئات مجردة يمكن تعريف غير منفذة (مجردة) طريقة التوقيعات في نفس الطريقة كما واجهة, ولكن يمكن أن يكون أيضا تنفيذ (الخرسانة) الطرق والمنشئات.

الجانب السلبي هو أن, لأنه هو نوع من فئة لا يمكن استخدامها في أي من وراثة متعددة نوع السيناريوهات التي واجهة يمكن.

يمكنني استخدام النمط التالي لجعلها مضادة للرصاص.

  • المطور الذي يستمد صفه من القاعدة لا يمكن قصد إنشاء الجمهور منشئ
  • النهائي الدرجة المطور يضطرون إلى الذهاب من خلال المشتركة إنشاء طريقة
  • كل شيء هو نوع آمن لا المسبوكات المطلوب
  • انها 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());
    }

إذا فهمت العملية بشكل صحيح, نحن نريد أن ينفذ العقد حيث GraphicsDeviceManager دائما initialised من خلال تنفيذ الطبقات.لقد حصلت لي نفس المشكلة وكنت ابحث عن حل أفضل, ولكن هذا هو أفضل ما يمكنني التفكير:

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top