متى يجب أن أعود الواجهة وعندما تكون فئة ملموسة؟

StackOverflow https://stackoverflow.com/questions/932452

  •  06-09-2019
  •  | 
  •  

سؤال

عند البرمجة في Java أنا دائما دائما، فقط من العادة، اكتب شيئا مثل هذا:

public List<String> foo() {
    return new ArrayList<String>();
}

معظم الوقت دون التفكير في الأمر. الآن، والسؤال هو: هل يجب علي دائما حدد الواجهة كنوع الإرجاع؟ أم أنه من المستحسن استخدام التنفيذ الفعلي للواجهة، وإذا كان الأمر كذلك، في ظل أي ظروف؟

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

List bar = foo();
List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar;

لكن هذا يبدو فظيعا وربما أن زملاء العمل قد ينشقني في الكافتيريا. وبقامة ذلك.

ماذا تظنون يا جماعة؟ ما هي المبادئ التوجيهية الخاصة بك، متى تميل نحو الحل التجريدي، ومتى تكشف عن تفاصيل تنفيذك للحصول على مكاسب الأداء المحتملة؟

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

المحلول

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

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

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

ومع ذلك، فإن مثالك reks الخاص بك من الأمثل المبكر.

إذا كانت الطريقة أو يمكن أن تكون حاسمة، فقد تذكر تفاصيل التنفيذ في الوثائق.

نصائح أخرى

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

في البرمجة OO، نريد التغلب على البيانات قدر الإمكان. إخفاء أكبر قدر ممكن من التنفيذ الفعلي، تجريد الأنواع عالية قدر الإمكان.

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

  • إذا كان الجواب بالنفي، ما عليك سوى استخدام واجهة أعلى مستوى. إنه أكثر مرونة، ويسمح لك بتغيير الخلفية
  • إذا كانت الإجابة بنعم، اسأل نفسك: لا يمكنني إعادة تكوين التعليمات البرمجية لإرجاع واجهة أعلى مستوى؟ :)

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

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

باختصار: الرمز نبذة مختصرة, ، لكن صراحة :)

دون أن تكون قادرا على تبريرها مع مقاتلات CS (أنا تدرس نفسي)، لقد ذهبت دائما من تعويذة "قبول أقل مشتقات، وأرجع أكثر المشتقات"، عند تصميم الطبقات ووفاتني بشكل جيد السنوات.

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

-

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

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

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

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

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

قد أثبت استخدام واجهات بهذه الطريقة طريقة موثوقة للغاية لكتابة رمز قابلة لإعادة الاستخدام.

تم الرد على السؤال الرئيسي بالفعل ويجب عليك دائما استخدام الواجهة. ومع ذلك، أود فقط التعليق على

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

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

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

لا يهم كل ما إذا كانت طريقة API ترجع واجهة أو فئة ملموسة؛ على الرغم من ما يقوله الجميع هنا، فإنك تقريبا لا تغير فئة التطبيقات بمجرد كتابة التعليمات البرمجية.

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

عندما ترجع طريقة API ArrayList, ، ليس لدي أي مؤهلات مع ذلك تماما، ولكن عندما يطالب ArrayList (أو، كل شيء شائع، Vector) المعلمة، أنا أعتبر الصيد أسفل المبرمج وأيضاه، لأنه يعني أنه لا يمكنني استخدام Arrays.asList(), Collections.singletonList() أو Collections.EMPTY_LIST.

آسف لا أوافق، لكنني أعتقد أن القاعدة الأساسية هي كما يلي:

  • بالنسبة إدخال الوسائط تستخدم أكثر نوعي.
  • بالنسبة انتاج القيم، أكثر محدد.

لذلك، في هذه الحالة تريد إعلان التنفيذ على النحو التالي:

public ArrayList<String> foo() {
  return new ArrayList<String>();
}

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

مثال 1: العميل يريد الحصول على العنصر الخامس:

  • إرجاع المجموعة: يجب تكرارها حتى قائمة عودة العنصر الخامس
  • قائمة العودة: list.get(4)

مثال 2: العميل يريد إزالة العنصر الخامس:

  • قائمة العودة: يجب إنشاء قائمة جديدة دون العنصر المحدد (list.remove() هو اختياري).
  • إرجاع الصورة: arrayList.remove(4)

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

لذلك، مرة أخرى، يمكن ذكر القاعدة على النحو التالي:

  • كن مرنا لما تقدمه.
  • كن مفيدا مع ما تقدمه.

لذلك، في المرة القادمة، يرجى إعادة التنفيذ.

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

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

للعمل مع الواجهات التي ستقوم بإثباتها مثل هذا:

IParser parser = new Parser();

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

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

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

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

ستجد (أو وجدت) أنه عند إرجاع واجهات، تتخلل من خلال التعليمات البرمجية الخاصة بك. على سبيل المثال، يمكنك إرجاع واجهة من الطريقة A وأنت يملك ثم اجتياز واجهة لطريقة B.

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

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

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

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