شرح - sealed classes c#




واجهات C#. التنفيذ الضمني مقابل التطبيق الصريح (8)

السبب رقم 1

أميل إلى استخدام واجهة تطبيق صريحة عندما أريد تثبيط "البرمجة إلى تطبيق" ( مبادئ التصميم من أنماط التصميم ).

على سبيل المثال ، في تطبيق ويب يستند إلى MVP :

public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

والآن ، من غير المرجح أن تعتمد فئة أخرى (مثل presenter ) على تطبيق StandardNavigator ومن الأرجح أن تعتمد على واجهة INAVigator (بما أن التنفيذ سيحتاج إلى إرساله إلى واجهة لاستخدام طريقة Redirect).

السبب رقم 2

سبب آخر قد أذهب مع تطبيق واجهة صريح هو الحفاظ على منظف واجهة "افتراضي" للطبقة. على سبيل المثال ، إذا كنت أقوم بتطوير عنصر تحكم خادم ASP.NET ، فقد أحتاج إلى واجهتين:

  1. الواجهة الأساسية للفصل ، والتي يستخدمها مطورو صفحات الويب ؛ و
  2. واجهة "مخفية" يستخدمها المقدم الذي أقوم بتطويره للتعامل مع منطق التحكم

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

public sealed class CustomerComboBox : ComboBox, ICustomerComboBox {
    private readonly CustomerComboBoxPresenter presenter;

    public CustomerComboBox() {
        presenter = new CustomerComboBoxPresenter(this);
    }

    protected override void OnLoad() {
        if (!Page.IsPostBack) presenter.HandleFirstLoad();
    }

    // Primary interface used by web page developers
    public Guid ClientId {
        get { return new Guid(SelectedItem.Value); }
        set { SelectedItem.Value = value.ToString(); }
    }

    // "Hidden" interface used by presenter
    IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}

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

لكنها ليست لعبة مدفع فضية

لا أوصي دائمًا باستخدام تطبيقات واجهة صريحة. هذه مجرد مثالين يمكن أن يكونا مفيدًا.

ما الاختلافات في تنفيذ الواجهات بشكل ضمني وصريح في C #؟

متى يجب عليك استخدام الضمني ومتى يجب عليك استخدام صريح؟

هل هناك أي إيجابيات و / أو سلبيات لأحد أو لآخر؟

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

أعتقد أن هذا المبدأ التوجيهي صالح جدًا في وقت ما قبل IoC ، عندما لا تمرر الأشياء في شكل واجهات.

يمكن لأي شخص أن يتطرق إلى هذا الجانب أيضا؟


أحد الاستخدامات المهمة لتنفيذ الواجهة الصريحة هو عند الحاجة إلى تنفيذ واجهات مع رؤية مختلطة .

يتم شرح المشكلة والحل بشكل جيد في المقالة C # Interface Interface .

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


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

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

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

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

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


بالإضافة إلى الأسباب الأخرى التي سبق ذكرها ، هذا هو الوضع الذي تقوم فيه الطبقة بتنفيذ واجهاتين مختلفتين لهما خاصية / طريقة بنفس الاسم والتوقيع.

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    public string Title
    {
        get
        {
            string personTitle = "Mr";
            string bookTitle = "The Hitchhikers Guide to the Galaxy";

            // What do we do here?
            return null;
        }
    }

    #region IPerson Members

    public string Forename
    {
        get { return "Lee"; }
    }

    public string Surname
    {
        get { return "Oades"; }
    }

    #endregion

    #region IBook Members

    public string ISBN
    {
        get { return "1-904048-46-3"; }
    }

    #endregion
}

يجمع هذا الرمز ويدير "موافق" ، ولكن تتم مشاركة خاصية "العنوان".

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

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

لاحظ أنه يتم الاستدلال على تعريفات الواجهة الصريحة لتكون عامة - وبالتالي لا يمكنك أن تعلن أنها عامة (أو غير ذلك) بشكل صريح.

لاحظ أيضًا أنه لا يزال بإمكانك الحصول على إصدار "مشترك" (كما هو موضح أعلاه) ، ولكن في حين أن هذا ممكن ، فإن وجود مثل هذه الخاصية أمر مشكوك فيه. ربما يمكن استخدامه كتنفيذ افتراضي للعنوان - بحيث لا يلزم تعديل الكود الحالي لإدخال Class1 إلى IBook أو IPerson.

إذا لم تقم بتعريف العنوان "المشترك" (الضمني) ، فيجب على مستهلكي Class1 أن يعرضوا أولاً أمثلة من Class1 إلى IBook أو IPerson أولاً - وإلا لن يتم ترجمة التعليمات البرمجية.


تطبيق واجهة ضمني هو حيث يكون لديك طريقة لها نفس التوقيع للواجهة.

تطبيق واجهة واضح هو المكان الذي تعلن فيه بوضوح واجهة التعامل التي تنتمي إليها الطريقة.

interface I1
{
    void implicitExample();
}

interface I2
{
    void explicitExample();
}


class C : I1, I2
{
    void implicitExample()
    {
        Console.WriteLine("I1.implicitExample()");
    }


    void I2.explicitExample()
    {
        Console.WriteLine("I2.explicitExample()");
    }
}

MSDN: تطبيقات واجهة ضمنية وصريحة


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

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

  1. من خلال العمل بشكل مباشر مع الواجهة ، فأنت لا تعترف بها ، وتربط رمزك بالتطبيق الأساسي.
  2. إذا كان لديك بالفعل ، على سبيل المثال ، خاصية عامة اسم في رمزك وتريد تنفيذ واجهة تحتوي على خاصية اسم ، فإن القيام بذلك بشكل صريح سيبقي الاثنين منفصلين. حتى لو كانوا يفعلون نفس الشيء فما زلت أقوم بتفويض الدعوة الصريحة إلى خاصية الاسم. أنت لا تعرف أبدًا ، قد ترغب في تغيير كيفية عمل الاسم للفئة العادية وكيفية عمل الاسم ، الواجهة في وقت لاحق.
  3. إذا قمت بتطبيق واجهة ضمنية ، فإن صفك الآن يكشف عن سلوكيات جديدة قد تكون ذات صلة بعميل الواجهة فقط ، وهذا يعني أنك لا تحافظ على دروسك كافية (رأيي).

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

Public Overridable Function Foo() As Integer Implements IFoo.Foo

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

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo

وفي هذه الحالة ، يُسمح للفصل ومشتقاته بالوصول إلى أحد أعضاء الصف باستخدام الاسم IFoo_Foo ، ولكن العالم الخارجي لن يتمكن من الوصول إلى ذلك العضو المعين إلا من خلال الإرسال إلى IFoo . يكون مثل هذا الأسلوب جيدًا في كثير من الأحيان في الحالات التي يكون فيها أسلوب الواجهة له سلوك محدد في جميع التطبيقات ، ولكن سلوكًا مفيدًا على بعض فقط [على سبيل المثال ، السلوك المحدد لمجموعة IList<T>.Add للقراءة فقط في الأسلوب IList<T>.Add NotSupportedException ]. لسوء الحظ ، فإن الطريقة الصحيحة الوحيدة لتنفيذ الواجهة في C # هي:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }

ليس لطيفا


ضمنيًا هو عندما تحدد واجهتك عبر عضو في صفك. صريح هو عندما تقوم بتعريف طرق داخل صفك على الواجهة. أعلم أن هذه الأصوات محيرة ولكن هنا ما أعنيه: أن IList.CopyTo سينفذ ضمنا على النحو التالي:

public void CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

وصراحة على النحو التالي:

void ICollection.CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

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

MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.

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

أنا متأكد من أن هناك المزيد من الأسباب لاستخدامها / عدم استخدامها أن الآخرين سوف ينشرون.

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





interface