C# - هل يمكن إخفاء الأساليب الموروثة علنًا (على سبيل المثالجعل خاصًا للفئة المشتقة)

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

  •  01-07-2019
  •  | 
  •  

سؤال

لنفترض أن لدي BaseClass بالطرق العامة A وB، وقمت بإنشاء DerivedClass من خلال الميراث.

على سبيل المثال

public DerivedClass : BaseClass {}

الآن أريد تطوير طريقة C في DerivedClass التي تستخدم A وB.هل هناك طريقة يمكنني من خلالها تجاوز الطريقتين A وB لتكونا خاصتين في DerivedClass بحيث يتم عرض الطريقة C فقط لشخص يريد استخدام DerivedClass الخاص بي؟

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

المحلول

ليس من الممكن، لماذا؟

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

بدلاً من استخدام العلاقة "is-a"، سيتعين عليك استخدام العلاقة "has-a".

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

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

لذا فهم لا يسمحون لها بحمايتك من التسلسلات الهرمية السيئة للميراث.

ماذا يجب أن تفعل بدلا من ذلك؟

بدلا من ذلك يجب عليك تنفيذ واجهات.وهذا يترك لك الحرية في الحصول على وظيفة باستخدام العلاقة "لديك".

لغات اخرى:

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

الكود المعاد هيكلته:

interface I
{
    void C();
}

class BaseClass
{
    public void A() { MessageBox.Show("A"); }
    public void B() { MessageBox.Show("B"); }
}

class Derived : I
{
    public void C()
    {
        b.A();
        b.B();
    }

    private BaseClass b;
}

أفهم أن أسماء الفئات المذكورة أعلاه موضع نقاش قليلاً :)

اقتراحات أخرى:

اقترح آخرون جعل A() وB() عامًا ورمي الاستثناءات.لكن هذا لا يشكل فصلًا دراسيًا ودودًا ليستخدمه الأشخاص وهو غير منطقي حقًا.

نصائح أخرى

عندما تحاول، على سبيل المثال، أن ترث من أ List<object>, ، وتريد إخفاء المباشر Add(object _ob) عضو:

// the only way to hide
[Obsolete("This is not supported in this class.", true)]
public new void Add(object _ob)
{
    throw NotImplementedException("Don't use!!");
}

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

خطأ CS0619:"TestConsole.TestClass.Add(TestConsole.TestObject)" قديم:"هذا غير معتمد في هذه الفئة."

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

إذا كنت لا تريد أن يتمكن مستهلكو DerivedClass من الوصول إلى الأساليب DerivedClass.A() وDerivedClass.B() أود أن أقترح أن DerivedClass يجب أن يطبق بعض الواجهة العامة IWhateverMethodCIsAbout ويجب أن يتحدث مستهلكو DerivedClass فعليًا إلى IWhateverMethodCIsAbout ويعرفوه لا شيء عن تنفيذ BaseClass أو DerivedClass على الإطلاق.

ما تحتاجه هو التكوين وليس الميراث.

class Plane
{
  public Fly() { .. }
  public string GetPilot() {...}
}

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

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

لنفس السبب، لا يمكنك تقليل رؤية أساليب الفئة الأساسية عند التجاوز.. يمكنك تجاوز الطريقة الخاصة الأساسية وجعلها عامة في الاشتقاق ولكن ليس العكس.على سبيل المثالفي هذا المثال، إذا قمت باشتقاق نوع الطائرة "BadPlane" ثم قمت بتجاوز و"إخفاء" GetPilot() - فاجعله خاصًا؛ستعمل طريقة العميل LogFlight(Plane p) مع معظم الطائرات ولكنها ستنفجر لـ "BadPlane" إذا كان تنفيذ LogFlight بحاجة إلى/استدعاء GetPilot(). نظرًا لأنه من المتوقع أن تكون جميع اشتقاقات الفئة الأساسية "قابلة للاستبدال" أينما كان من المتوقع وجود معلمات للفئة الأساسية، فيجب عدم السماح بذلك.

الطريقة الوحيدة التي أعرفها للقيام بذلك هي استخدام علاقة Has-A وتنفيذ الوظائف التي تريد الكشف عنها فقط.

@ بريان ر.أشارني بوندي إلى مقال مثير للاهتمام حول الاختباء من خلال الميراث و جديد الكلمة الرئيسية.

http://msdn.microsoft.com/en-us/library/aa691135(VS.71).aspx

لذا كحل بديل أود أن أقترح:

class BaseClass
{
    public void A()
    {
        Console.WriteLine("BaseClass.A");
    }

    public void B()
    {
        Console.WriteLine("BaseClass.B");
    }
}

class DerivedClass : BaseClass
{
    new public void A()
    {
        throw new NotSupportedException();
    }

    new public void B()
    {
        throw new NotSupportedException();
    }

    public void C()
    {
        base.A();
        base.B();
    }
}

بهذه الطريقة رمز مثل هذا سوف يرمي NotSupportedException:

    DerivedClass d = new DerivedClass();
    d.A();

الاختباء منحدر زلق جدًا.القضايا الرئيسية، المنظمة البحرية الدولية، هي:

  • إنه يعتمد على نوع إعلان وقت التصميم للمثال ، وهذا يعني إذا كنت تفعل شيئًا مثل BaseClass OBJ = فئة فرعية جديدة () ، ثم استدعاء OBJ.A () ، تم هزيمة الاختباء.سيتم تنفيذ BaseClass.A().

  • يمكن للاختباء أن يحجب السلوك بسهولة (أو تغييرات السلوك) في النوع الأساسي.من الواضح أن هذا أقل اهتمامًا عندما تملك جانبي المعادلة ، أو إذا كان استدعاء "base.xxx" جزءًا من عضوك الفرعي.

  • إذا كنت فعلا يفعل إذا امتلكت طرفي المعادلة الأساسية/الفئة الفرعية، فيجب أن تكون قادرًا على ابتكار حل أكثر قابلية للإدارة من الإخفاء/التظليل المؤسسي.

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

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

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

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

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

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

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

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

يحرر:خورخي فيريرا على حق.

في حين أن الإجابة على السؤال هي "لا"، هناك نصيحة واحدة أود أن أشير إليها للآخرين الذين يصلون إلى هنا (نظرًا لأن البروتوكول الاختياري كان نوعًا ما يلمح إلى وصول أطراف ثالثة إلى التجميع).عندما يشير الآخرون إلى تجميع، يجب أن يحترم Visual Studio السمة التالية حتى لا تظهر في التحسس (مخفية، ولكن لا يزال من الممكن استدعاؤها، لذا احذر):

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

إذا لم يكن لديك خيار آخر، فيجب أن تكون قادرًا على استخدامه new على الطريقة التي تخفي طريقة الكتابة الأساسية، ارجع => throw new NotSupportedException();, ، ودمجها مع السمة أعلاه.

تعتمد الحيلة الأخرى على عدم الوراثة من فئة أساسية إن أمكن، حيث تحتوي القاعدة على واجهة مقابلة (مثل IList<T> ل List<T>).سيؤدي تنفيذ الواجهات "بشكل صريح" أيضًا إلى إخفاء هذه الأساليب من التحسس الذكي على نوع الفئة.على سبيل المثال:

public class GoodForNothing: IDisposable
{
    void IDisposable.Dispose() { ... }
}

في حالة var obj = new GoodForNothing(), ، ال Dispose() الطريقة لن تكون متاحة على obj.ومع ذلك، سيكون متاحًا لأي شخص يقوم بالكتابة بشكل صريح obj ل IDisposable.

بالإضافة إلى ذلك، يمكنك أيضًا تغليف نوع أساسي بدلًا من توريثه، ثم إخفاء بعض الطرق:

public class MyList<T> : IList<T>
{
    List<T> _Items = new List<T>();
    public T this[int index] => _Items[index];
    public int Count => _Items.Count;
    public void Add(T item) => _Items.Add(item);
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    void ICollection<T>.Clear() => throw new InvalidOperationException("No you may not!"); // (hidden)
    /*...etc...*/
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top