c# ابنتها - لماذا لا ترث من القائمة<T>؟




يرث الابناء (21)

I think I don't agree with your generalization. A team isn't just a collection of players. A team has so much more information about it - name, emblem, collection of management/admin staff, collection of coaching crew, then collection of players. So properly, your FootballTeam class should have 3 collections and not itself be a collection; if it is to properly model the real world.

You could consider a PlayerCollection class which like the Specialized StringCollection offers some other facilities - like validation and checks before objects are added to or removed from the internal store.

Perhaps, the notion of a PlayerCollection betters suits your preferred approach?

public class PlayerCollection : Collection<Player> 
{ 
}

And then the FootballTeam can look like this:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}

عند التخطيط لبرامجي ، غالباً ما أبدأ بسلسلة من الأفكار مثل:

فريق كرة القدم هو مجرد قائمة لاعبي كرة القدم. لذلك ، يجب عليّ تمثيلها مع:

var football_team = new List<FootballPlayer>();

يمثل ترتيب هذه القائمة الترتيب الذي يتم فيه إدراج اللاعبين في القائمة.

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

إذن أعتقد:

حسناً ، إن فريق كرة القدم هو تماماً مثل قائمة اللاعبين ، ولكن بالإضافة إلى ذلك ، فإنه يحتوي على اسم ( string ) ومجموع كلي من الدرجات ( int ). لا توفر .NET فئة لتخزين فرق كرة القدم ، لذا سأقدم دروسي الخاص. إن البنية القائمة الأكثر تشابهاً وذات صلة هي List<FootballPlayer> ، لذا سأرثها:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

ولكن اتضح أن هناك مبدأ توجيهي يجب ألا ترثه من List<T> . أنا مرتبك تماما من هذا المبدأ التوجيهي في ناحيتين.

لما لا؟

List ما يبدو هو الأمثل للأداء بطريقة أو بأخرى . كيف ذلك؟ ما مشاكل الأداء التي سأسببها في حالة توسيع List ؟ ما سوف يكسر بالضبط؟

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

لماذا لا يهم حتى؟ القائمة هي قائمة. ما الذي يمكن أن يتغير؟ ما الذي يمكن أن أرغب في تغييره؟

وأخيرًا ، إذا لم تكن مايكروسوفت تريد مني أن أرث من List ، فلماذا لم تجعل الطبقة sealed ؟

ماذا أنا من المفترض أن تستخدم؟

على ما يبدو ، بالنسبة للمجموعات المخصصة ، قامت Microsoft بتوفير فئة Collection يجب تمديدها بدلاً من List . لكن هذه الطبقة عارية للغاية ، وليس لديها العديد من الأشياء المفيدة ، مثل AddRange ، على سبيل المثال. توفر إجابة jvitor83 أساسًا منطقيًا للأداء لهذا الأسلوب المعين ، ولكن كيف يكون معدل AddRange بطيئًا وليس أفضل من AddRange ؟

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

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

وأخيرًا ، يقترح البعض التفاف List في شيء ما:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

هناك مشكلتان مع هذا:

  1. يجعل كود بلادي مطول بلا داع. يجب أن أتصل الآن my_team.Players.Count بدلاً من my_team.Count فقط. الحمد لله ، مع C # يمكنني تحديد مفهرسات لجعل الفهرسة شفافة ، وإلى الأمام جميع أساليب القائمة الداخلية ... ولكن هذا كثير من التعليمات البرمجية! ماذا أحصل على كل هذا العمل؟

  2. انها مجرد لا معنى له. لا يملك فريق كرة القدم "قائمة" باللاعبين. إنها قائمة اللاعبين. لا تقول "انضم جون ماكفوت بولر إلى لاعبي SomeTeam". أنت تقول "جون قد انضم إلى SomeTeam". لا تضيف حرفًا إلى "أحرف السلسلة" ، فأنت تضيف حرفًا إلى سلسلة. لا تضيف كتابًا إلى كتب المكتبة ، بل تضيف كتابًا إلى المكتبة.

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

سؤالي (ملخص)

ما هي الطريقة الصحيحة C # لتمثيل بنية البيانات ، والتي ، "منطقياً" (أي "للعقل البشري") هي مجرد list things مع القليل من الأجراس والصفارات؟

هل الموروث من List<T> دائمًا غير مقبول؟ متى يكون مقبولا؟ لماذا لماذا لا؟ ماذا يجب أن يفكر المبرمج ، عند تحديد ما إذا كان سيرث من List<T> أم لا؟


My dirty secret: I don't care what people say, and I do it. .NET Framework is spread with "XxxxCollection" (UIElementCollection for top of my head example).

So what stops me saying:

team.Players.ByName("Nicolas")

When I find it better than

team.ByName("Nicolas")

Moreover, my PlayerCollection might be used by other class, like "Club" without any code duplication.

club.Players.ByName("Nicolas")

Best practices of yesterday, might not be the one of tomorrow. There is no reason behind most best practices, most are only wide agreement among the community. Instead of asking the community if it will blame you when you do that ask yourself, what is more readable and maintainable?

team.Players.ByName("Nicolas") 

أو

team.ByName("Nicolas")

هل حقا. Do you have any doubt? Now maybe you need to play with other technical constraints that prevent you to use List<T> in your real use case. But don't add a constraint that should not exist. If Microsoft did not document the why, then it is surely a "best practice" coming from nowhere.


وأخيرًا ، يقترح البعض التفاف القائمة في شيء ما:

هذه هي الطريقة الصحيحة. "كلمة بلا معنى" هي طريقة سيئة للنظر إلى هذا. لها معنى واضح عند كتابة my_team.Players.Count . تريد عد اللاعبين.

my_team.Count

..لا يعني شيئا. عد ماذا؟

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

إذا كنت قلقًا حقًا من أن تكون مطوّلًا بشكل مفرط ، فيمكنك دومًا عرض الخصائص من الفريق:

public int PlayerCount {
    get {
        return Players.Count;
    }
}

.. الذي يصبح:

my_team.PlayerCount

هذا له معنى الآن ويلتزم قانون ديميتر .

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


Does allowing people to say

myTeam.subList(3, 5);

make any sense at all? If not then it shouldn't be a List.


When they say List<T> is "optimized" I think they want to mean that it doesn't have features like virtual methods which are bit more expensive. So the problem is that once you expose List<T> in your public API , you loose ability to enforce business rules or customize its functionality later. But if you are using this inherited class as internal within your project (as opposed to potentially exposed to thousands of your customers/partners/other teams as API) then it may be OK if it saves your time and it is the functionality you want to duplicate. The advantage of inheriting from List<T> is that you eliminate lot of dumb wrapper code that is just never going to be customized in foreseeable future. Also if you want your class to explicitly have exact same semantics as List<T> for the life of your APIs then also it may be OK.

I often see lot of people doing tons of extra work just because of FxCop rule says so or someone's blog says it's a "bad" practice. Many times, this turns code in to design pattern palooza weirdness. As with lot of guideline, treat it as guideline that can have exceptions.


It depends on the context

When you consider your team as a list of players, you are projecting the "idea" of a foot ball team down to one aspect: You reduce the "team" to the people you see on the field. This projection is only correct in a certain context. In a different context, this might be completely wrong. Imagine you want to become a sponsor of the team. So you have to talk to the managers of the team. In this context the team is projected to the list of its managers. And these two lists usually don't overlap very much. Other contexts are the current versus the former players, etc.

Unclear semantics

So the problem with considering a team as a list of its players is that its semantic depends on the context and that it cannot be extended when the context changes. Additionally it is hard to express, which context you are using.

Classes are extensible

When you using a class with only one member (eg IList activePlayers ), you can use the name of the member (and additionally its comment) to make the context clear. When there are additional contexts, you just add an additional member.

Classes are more complex

In some cases it might be overkill to create a extra class. Each class definition must be loaded through the classloader and will be cached by the virtual machine. This costs you runtime performance and memory. When you have a very specific context it might be OK to consider a football team as a list of players. But in this case, you should really just use a IList , not a class derived from it.

Conclusion / Considerations

When you have a very specific context, it is OK to consider a team as a list of players. For example inside a method it is completely OK to write

IList<Player> footballTeam = ...

When using F#, it can even be OK to create a type abbreviation

type FootballTeam = IList<Player>

But when the context is broader or even unclear, you should not do this. This is especially the case, when you create a new class, where it is not clear in which context it may be used in the future. A warning sign is when you start to add additional attributes to your class (name of the team, coach, etc.). This is a clear sign that the context where the class will be used is not fixed and will change in the future. In this case you cannot consider the team as a list of players, but you should model the list of the (currently active, not injured, etc.) players as an attribute of the team.


A football team is not a list of football players. A football team is composed of a list of football players!

This is logically wrong:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

and this is correct:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}

بادئ ذي بدء ، فإنه يجب القيام به مع سهولة الاستخدام. إذا كنت تستخدم الوراثة ، فستقوم فئة Team بكشف السلوك (الطرق) المصممة تمامًا لمعالجة الكائن. على سبيل المثال ، AsReadOnly() أو CopyTo(obj) لا معنى لـ كائن الفريق. بدلاً من أسلوب AddRange(items) قد ترغب على الأرجح في أسلوب AddPlayers(players) أكثر AddPlayers(players) .

إذا كنت ترغب في استخدام LINQ ، فإن تنفيذ واجهة عامة مثل ICollection<T> أو IEnumerable<T> سيكون أكثر منطقية.

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


What is the correct C# way of representing a data structure...

Remeber, "All models are wrong, but some are useful." - George EP Box

There is no a "correct way", only a useful one.

Choose one that is useful to you and/your users. هذا هو. Develop economically, don't over-engineer. The less code you write, the less code you will need to debug. (read the following editions).

-- Edited

My best answer would be... it depends. Inheriting from a List would expose the clients of this class to methods that may be should not be exposed, primarily because FootballTeam looks like a business entity.

-- Edition 2

I sincerely don't remember to what I was referring on the “don't over-engineer” comment. While I believe the KISS mindset is a good guide, I want to emphasize that inheriting a business class from List would create more problems than it resolves, due abstraction leakage .

On the other hand, I believe there are a limited number of cases where simply to inherit from List is useful. As I wrote in the previous edition, it depends. The answer to each case is heavily influenced by both knowledge, experience and personal preferences.

Thanks to @kai for helping me to think more precisely about the answer.


نجاح باهر ، مشاركتك لديها مجموعة كاملة من الأسئلة والنقاط. معظم المنطق الذي تحصل عليه من Microsoft هو بالضبط على النقطة. لنبدأ بكل شيء حول List<T>

  • List<T> هي الأمثل للغاية. هو الاستخدام الرئيسي لاستخدامه كعضو خاص في كائن.
  • لم تقم Microsoft بإغلاقه لأنه قد تحتاج في بعض الأحيان إلى إنشاء فئة لها اسم أكثر صداقة: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } . الآن الأمر سهل مثل القيام var list = new MyList<int, string>(); .
  • CA1002: لا تعرض القوائم العامة : بشكل أساسي ، حتى إذا كنت تخطط لاستخدام هذا التطبيق كمطور وحيد ، فمن المفيد تطويره مع ممارسات ترميز جيدة ، بحيث يتم غرسها فيك والطبيعة الثانية. لا يزال مسموحًا لك بالكشف عن القائمة باعتبارها IList<T> إذا كنت بحاجة إلى أي مستهلك للحصول على قائمة مفهرسة. هذا يتيح لك تغيير التنفيذ داخل فئة لاحقًا.
  • جعلت Microsoft Collection<T> عامة جدا لأنها مفهوم عام ... الاسم يقول كل شيء. انها مجرد مجموعة. هناك إصدارات أكثر دقة مثل SortedCollection<T> ، ObservableCollection<T> ، ReadOnlyCollection<T> ، إلخ. كل منها تنفذ IList<T> ولكن ليست List<T> .
  • يسمح Collection<T> للأعضاء (مثل ، إضافة ، إزالة ، إلخ) أن يتم تجاوزها لأنها ظاهرية. List<T> لا.
  • الجزء الأخير من سؤالك هو على الفور. يعد فريق كرة القدم أكثر من مجرد قائمة من اللاعبين ، لذا يجب أن يكون صنفًا يحتوي على قائمة اللاعبين هذه. اعتقد التركيب مقابل الوراثة . فريق كرة القدم لديه قائمة باللاعبين (قائمة) ، إنه ليس قائمة باللاعبين.

إذا كنت أكتب هذا الرمز ، فربما كان الفصل يبدو على النحو التالي:

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

If your class users need all the methods and properties** List has, you should derive your class from it. If they don't need them, enclose the List and make wrappers for methods your class users actually need.

This is a strict rule, if you write a public API , or any other code that will be used by many people. You may ignore this rule if you have a tiny app and no more than 2 developers. This will save you some time.

For tiny apps, you may also consider choosing another, less strict language. Ruby, JavaScript - anything that allows you to write less code.


Just because I think the other answers pretty much go off on a tangent of whether a football team "is-a" List<FootballPlayer> or "has-a" List<FootballPlayer> , which really doesn't answer this question as written.

The OP chiefly asks for clarification on guidelines for inheriting from List<T> :

A guideline says that you shouldn't inherit from List<T> . لما لا؟

Because List<T> has no virtual methods. This is less of a problem in your own code, since you can usually switch out the implementation with relatively little pain - but can be a much bigger deal in a public API.

What is a public API and why should I care?

A public API is an interface you expose to 3rd party programmers. Think framework code. And recall that the guidelines being referenced are the ".NET Framework Design Guidelines" and not the ".NET Application Design Guidelines". There is a difference, and - generally speaking - public API design is a lot more strict.

If my current project does not and is not likely to ever have this public API, can I safely ignore this guideline? If I do inherit from List and it turns out I need a public API, what difficulties will I have?

Pretty much, yeah. You may want to consider the rationale behind it to see if it applies to your situation anyway, but if you're not building a public API then you don't particularly need to worry about API concerns like versioning (of which, this is a subset).

If you add a public API in the future, you will either need to abstract out your API from your implementation (by not exposing your List<T> directly) or violate the guidelines with the possible future pain that entails.

Why does it even matter? A list is a list. What could possibly change? What could I possibly want to change?

Depends on the context, but since we're using FootballTeam as an example - imagine that you can't add a FootballPlayer if it would cause the team to go over the salary cap. A possible way of adding that would be something like:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

Ah...but you can't override Add because it's not virtual (for performance reasons).

If you're in an application (which, basically, means that you and all of your callers are compiled together) then you can now change to using IList<T> and fix up any compile errors:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

but, if you've publically exposed to a 3rd party you just made a breaking change that will cause compile and/or runtime errors.

TL;DR - the guidelines are for public APIs. For private APIs, do what you want.


كما أشار الجميع ، فإن فريق من اللاعبين ليس قائمة باللاعبين. يرتكب هذا الخطأ العديد من الناس في كل مكان ، ربما على مستويات مختلفة من الخبرة. غالبًا ما تكون المشكلة دقيقة وأحيانًا شديدة جدًا ، كما هو الحال في هذه الحالة. هذه التصاميم سيئة لأنها تنتهك مبدأ استبدال Liskov . يحتوي الإنترنت على العديد من المقالات الجيدة التي توضح هذا المفهوم ، على سبيل المثال ، http://en.wikipedia.org/wiki/Liskov_substitution_principle

باختصار ، هناك قاعدتان يجب حفظهما في علاقة الوالد / الطفل بين الفئات:

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

وبعبارة أخرى ، فإن الوالدين هو تعريف ضروري للطفل ، والطفل هو تعريف كافٍ لأحد الوالدين.

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

  • هل فريق كرة القدم قائمة لاعبي كرة القدم؟ (هل تنطبق جميع خصائص القائمة على فريق بنفس المعنى)
    • هو فريق مجموعة من الكيانات المتجانسة؟ نعم ، الفريق عبارة عن مجموعة من اللاعبين
    • هل ترتيب إدراج اللاعبين وصفًا لحالة الفريق وهل يضمن الفريق الحفاظ على التسلسل ما لم يتم تغييره صراحة؟ لا و ​​لا
    • هل من المتوقع أن يتم تضمين / إسقاط اللاعبين بناءً على موقعهم المتسلسل في الفريق؟ لا

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

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


هذا هو مثال كلاسيكي على composition مقابل inheritance .

في هذه الحالة المحددة:

هل الفريق قائمة من اللاعبين الذين لديهم سلوك إضافي

أو

هل الفريق كائن خاص به يحدث ليحتوي على قائمة اللاعبين.

من خلال توسيع القائمة ، فإنك تحد من نفسك بعدة طرق:

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

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

  3. أنت تحد من خياراتك في الميراث. على سبيل المثال ، قد ترغب في إنشاء كائن عام عام ، ثم أن يكون لديك BaseballTeam ، و FootballTeam ، وما إلى ذلك التي ترث من ذلك. لكي ترث من القائمة ، تحتاج إلى القيام بالوراثة من الفريق ، ولكن ذلك يعني أن جميع الأنواع المختلفة من الفريق تُجبر على تنفيذ نفس هذه القائمة.

تكوين - بما في ذلك كائن يعطي السلوك الذي تريده داخل الكائن الخاص بك.

الوراثة - يصبح الكائن مثيلًا للكائن الذي يحتوي على السلوك الذي تريده.

كلاهما لهما استخدامات ، لكن هذه حالة واضحة حيث يكون التركيب هو الأفضل.


هناك بعض الإجابات الجيدة هنا. أود أن أضيف لهم النقاط التالية.

ما هي الطريقة الصحيحة C # لتمثيل بنية البيانات ، والتي ، "منطقياً" (أي "للعقل البشري") هي مجرد قائمة بالأشياء مع القليل من الأجراس والصفارات؟

اسأل أي عشرة أشخاص ليس لديهم مبرمج حاسوبي على دراية بوجود كرة القدم لملء الفراغ:

A football team is a particular kind of _____

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

List<T> هي آلية . فريق كرة القدم هو كائن أعمال - أي ، كائن يمثل بعض المفاهيم الموجودة في نطاق عمل البرنامج. لا تخلطها فريق كرة القدم هو نوع من الفريق. لديها قائمة ، قائمة هي قائمة من اللاعبين . قائمة ليست نوعًا خاصًا من قائمة اللاعبين . قائمة هي قائمة اللاعبين. لذلك ، قم بإنشاء خاصية تسمى Roster وهي List<Player> . ReadOnlyList<Player> عندما تكون في ذلك ، إلا إذا كنت تعتقد أن كل من يعرف عن فريق كرة القدم يحصل على حذف اللاعبين من القائمة.

هل الموروث من List<T> دائمًا غير مقبول؟

غير مقبول لمن؟ أنا؟ لا.

متى يكون مقبولا؟

عندما تقوم ببناء آلية تمدد آلية List<T> .

ماذا يجب أن يفكر المبرمج ، عند تحديد ما إذا كان سيرث من List<T> أم لا؟

هل أقوم ببناء آلية أو كائن أعمال ؟

لكن هذا كثير من الكود! ماذا أحصل على كل هذا العمل؟

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

تحديث

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

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


This reminds me of the "Is a" versus "has a" tradeoff. Sometimes it is easier and makesmore sense to inherit directly from a super class. Other times it makes more sense to create a standalone class and include the class you would have inherited from as a member variable. You can still access the functionality of the class but are not bound to the interface or any other constraints that might come from inheriting from the class.

Which do you do? As with a lot of things...it depends on the context. The guide I would use is that in order to inherit from another class there truly should be an "is a" relationship. So if you a writing a class called BMW, it could inherit from Car because a BMW truly is a car. A Horse class can inherit from the Mammal class because a horse actually is a mammal in real life and any Mammal functionality should be relevant to Horse. But can you say that a team is a list? From what I can tell, it does not seem like a Team really "is a" List. So in this case, I would have a List as a member variable.


class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

الرمز السابق يعني: مجموعة من اللاعبين من الشارع يلعبون كرة القدم ، ويحدث لهم اسم. شيء مثل:

على أي حال ، هذا الرمز (من جوابي)

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
    get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

يعني: هذا هو فريق كرة القدم الذي لديه الإدارة ، واللاعبين ، والمشرفين ، الخ. شيء من هذا القبيل:

هذه هي الطريقة التي يقدم بها المنطق في الصور ...


What the guidelines say is that the public API should not reveal the internal design decision of whether you are using a list, a set, a dictionary, a tree or whatever. A "team" is not necessarily a list. You may implement it as a list but users of your public API should use you class on a need to know basis. This allows you to change your decision and use a different data structure without affecting the public interface.


ماذا لو كان لفريق FootballTeam فريق احتياطي إلى جانب الفريق الرئيسي؟

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

كيف يمكنك أن نموذج ذلك؟

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

من الواضح أن العلاقة لها ولا هي .

أو RetiredPlayers ؟

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

كقاعدة عامة ، إذا أردت في أي وقت أن ترث من مجموعة ، فقم بتسمية الفئة SomethingCollection .

هل SomethingCollection الخاص بك معنى منطقي؟ لا تفعل ذلك إلا إذا كان نوعك عبارة عن مجموعة من Something .

في حالة FootballTeam لا يبدو صحيحًا. Team أكثر من Collection . يمكن أن يكون لدى Team مدربين ومدربين ، إلخ ، كما أشارت الإجابات الأخرى.

يبدو أن FootballCollection يمثل مجموعة من كرات القدم أو ربما مجموعة من أدوات كرة القدم. TeamCollection ، مجموعة من الفرق.

يبدو أن FootballPlayerCollection عبارة عن مجموعة من اللاعبين التي من شأنها أن تكون اسمًا صالحًا لصف دراسي يرث من List<FootballPlayer> إذا كنت تريد فعلًا ذلك.

حقا List<FootballPlayer> هو نوع جيد تماما للتعامل معها. ربما IList<FootballPlayer> إذا كنت ستعيده من إحدى الطرق.

باختصار

اسال نفسك

  1. هو X Y ؟ أو لديه X

  2. هل تعني أسماء فصلي ما هي؟


Let me rewrite your question. so you might see the subject from a different perspective.

When I need to represent a football team, I understand that it is basically a name. Like: "The Eagles"

string team = new string();

Then later I realized teams also have players.

Why can't I just extend the string type so that it also holds a list of players?

Your point of entry into the problem is arbitrary. Try to think what does a team have (properties), not what it is .

After you do that, you could see if it shares properties with other classes. And think about inheritance.


خاصة بـ C # ، وجدت "إرشادات تصميم الإطار: الاتفاقيات ، التعابير ، والأنماط للمكتبات القابلة لإعادة الاستخدام. NET" للحصول على الكثير من المعلومات الجيدة عن منطق التسمية.

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





c# .net oop inheritance design