سؤال

يتعثر المرء على هذه العبارة عند القراءة حول أنماط التصميم.

لكنني لا أفهم ذلك ، هل يمكن لأحد أن يشرح هذا لي؟

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

المحلول

الواجهات هي مجرد عقود أو توقيعات ولا يعرفون أي شيء عن التطبيقات.

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

إليك مثال أساسي لك.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

alt text

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

تعديل

لقد قمت بتحديث المثال أعلاه وأضفت فئة قاعدة سماعات مجردة. في هذا التحديث ، أضفت ميزة إلى جميع Spakers إلى "Sayhello". كل المتحدث يتحدث "مرحبا العالم". هذه ميزة شائعة مع وظيفة مماثلة. ارجع إلى مخطط الفصل وستجد أن فئة مكبر الصوت مجردة تنفذ واجهة ISPeaker وتُميز SPEAM () على أنها مجردة ، مما يعني أن كل تطبيق المتحدث مسؤول عن تنفيذ طريقة التحدث لأنه يختلف من المتحدث إلى المتحدث. لكن كل المتحدث يقول "مرحبا" بالإجماع. لذلك في فئة السماعات المجردة ، نحدد طريقة تقول "Hello World" وكل تطبيق متحدث سوف يستمد طريقة Sayhello.

ضع في اعتبارك حالة لا يستطيع فيها SpanishSpeaker قول مرحبًا ، لذا في هذه الحالة ، يمكنك تجاوز طريقة Sayhello للمتحدث الإسباني وتجنب الاستثناء المناسب.

يرجى ملاحظة أننا لم نقم بأي تغييرات على ispeaker الواجهة. ويبقى رمز العميل و SpeakerFactory أيضًا غير متأثر. وهذا ما نحققه برمجة إلى واجهة.

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

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //This is your client code.
        ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
        speaker.Speak();
        Console.ReadLine();
    }
}

public interface ISpeaker
{
    void Speak();
}

public abstract class Speaker : ISpeaker
{

    #region ISpeaker Members

    public abstract void Speak();

    public virtual void SayHello()
    {
        Console.WriteLine("Hello world.");
    }

    #endregion
}

public class EnglishSpeaker : Speaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        this.SayHello();
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : Speaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak German.");
        this.SayHello();
    }

    #endregion
}

public class SpanishSpeaker : Speaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    public override void SayHello()
    {
        throw new ApplicationException("I cannot say Hello World.");
    }

    #endregion
}

alt text

نصائح أخرى

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

التطبيقات هي السلوكيات الفعلية. قل على سبيل المثال لديك طريقة الفرز (). يمكنك تنفيذ Quicksort أو Mergesort. لا ينبغي أن يكون هذا مهمًا لفرز استدعاء رمز العميل طالما أن الواجهة لا تتغير.

تستخدم المكتبات مثل Java API و .NET Framework واجهات كبيرة لأن ملايين المبرمجين يستخدمون الكائنات المقدمة. يجب أن يكون منشئو هذه المكتبات حريصين للغاية على عدم تغيير الواجهة إلى الفصول في هذه المكتبات لأنها ستؤثر على جميع المبرمجين الذين يستخدمون المكتبة. من ناحية أخرى ، يمكنهم تغيير التنفيذ بقدر ما يحلو لهم.

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

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

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

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

هذه مجموعة فرعية من Liskov Substitution Principle (LSP) ، L من SOLID مبادئ.

مثال في .NET سيكون رمزًا مع IList بدلاً من List أو Dictionary, ، حتى تتمكن من استخدام أي فئة تنفذها IList بالتبادل في الكود الخاص بك:

// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}

مثال آخر من مكتبة الفئة الأساسية (BCL) هو ProviderBase الفئة التجريدية - يوفر هذا بعض البنية التحتية ، كما يعني أنه يمكن استخدام جميع تطبيقات الموفر بالتبادل إذا قمت بالترميز ضدها.

إذا كنت تريد كتابة فئة سيارة في عصر Creation-Car ، فهناك فرصة كبيرة لتنفيذ Oilchange () كجزء من هذه الفئة. ولكن ، عندما يتم تقديم السيارات الكهربائية ، ستكون في ورطة حيث لا يوجد تغيير للزيت لهذه السيارات ، ولا يوجد تطبيق.

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

class MaintenanceSpecialist {
    public:
        virtual int performMaintenance() = 0;
};

class CombustionEnginedMaintenance : public MaintenanceSpecialist {
    int performMaintenance() { 
        printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
        return 0;
    }
};

class ElectricMaintenance : public MaintenanceSpecialist {
    int performMaintenance() {
        printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
        return 0;
    }
};

class Car {
    public:
        MaintenanceSpecialist *mSpecialist;
        virtual int maintenance() {
            printf("Just wash the car \n");
            return 0;
        };
};

class GasolineCar : public Car {
    public: 
        GasolineCar() {
        mSpecialist = new CombustionEnginedMaintenance();
        }
        int maintenance() {
        mSpecialist->performMaintenance();
        return 0;
        }
};

class ElectricCar : public Car {
    public: 
        ElectricCar() {
             mSpecialist = new ElectricMaintenance();
        }

        int maintenance(){
            mSpecialist->performMaintenance();
            return 0;
        }
};

int _tmain(int argc, _TCHAR* argv[]) {

    Car *myCar; 

    myCar = new GasolineCar();
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */


    myCar = new ElectricCar(); 
    myCar->maintenance(); 

    return 0;
}

شرح إضافي: أنت مالك سيارة يمتلك سيارات متعددة. أنت تنشر الخدمة التي تريد الاستعانة بمصادر خارجية. في حالتنا ، نريد الاستعانة بمصادر خارجية لأعمال الصيانة لجميع السيارات.

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

    نهج بديل.

  4. يمكنك تحديد العمل (يمكن أن يكون واجهة واجهة جديدة) تحمل جيدًا لجميع سياراتك.
  5. أنت تعال مع آلية لتوفير الخدمة. في الأساس ، ستوفر التنفيذ.
  6. أنت تستدعي العمل وتفعل ذلك بنفسك. هنا سوف تقوم بعمل أعمال الصيانة المناسبة.

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

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

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

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

ما يساعد على فهم هذا هو السبب في أنه يجب عليك دائمًا برمجة الواجهة. هناك العديد من الأسباب ، ولكن اثنان من الأسهل لشرح

1) الاختبار.

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

WorkerClass -> dalclass ومع ذلك ، دعنا نضيف واجهة إلى المزيج.

WorkerClass -> idal -> dalclass.

وبالتالي فإن Dalclass تنفذ واجهة Idal ، واتصل فئة العمال فقط من خلال هذا.

الآن إذا أردنا كتابة اختبارات للرمز ، فيمكننا بدلاً من ذلك إنشاء فصل بسيط يتصرف مثل قاعدة البيانات.

WorkerClass -> idal -> ifakedal.

2) إعادة الاستخدام

باتباع المثال أعلاه ، دعنا نقول أننا نريد الانتقال من SQL Server (والذي يستخدمه Dalclass الخرساني) إلى MonogoDB. هذا سيستغرق العمل الكبير ، ولكن ليس إذا قمنا ببرمجة الواجهة. في هذه الحالة ، نكتب فئة DB الجديدة ، وتغيير (عبر المصنع)

WorkerClass -> idal -> dalclass

إلى

WorkerClass -> idal -> mongodbclass

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

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