[c#] सूची <टी> से विरासत क्यों नहीं?



11 Answers

अंत में, कुछ सूची में कुछ लपेटने का सुझाव देते हैं:

यह सही तरीका है। "अनावश्यक शब्द" यह देखने का एक बुरा तरीका है। जब आप my_team.Players.Count लिखते हैं तो my_team.Players.Count स्पष्ट अर्थ होता है। आप खिलाड़ियों को गिनना चाहते हैं।

my_team.Count

..मतलब कुछ नहीं। क्या गिनती है?

एक टीम एक सूची नहीं है - इसमें खिलाड़ियों की एक सूची से अधिक शामिल है। एक टीम खिलाड़ियों का मालिक है, इसलिए खिलाड़ियों का हिस्सा होना चाहिए (एक सदस्य)।

यदि आप वास्तव में अत्यधिक वर्बोज़ होने के बारे में चिंतित हैं, तो आप हमेशा टीम से गुणों का पर्दाफाश कर सकते हैं:

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

.. जो बनता है:

my_team.PlayerCount

इसका अर्थ अब है और डेमेटर के कानून का पालन ​​करता है।

आपको समग्र पुन: उपयोग सिद्धांत का पालन करने पर भी विचार करना चाहिए। List<T> से विरासत में, आप कह रहे हैं कि एक टीम खिलाड़ियों की एक सूची है और इसके बाहर की जरूरतहीन विधियों को उजागर कर रही है। यह गलत है - जैसा कि आपने कहा है, एक टीम खिलाड़ियों की एक सूची से अधिक है: इसमें एक नाम, प्रबंधक, बोर्ड सदस्य, प्रशिक्षकों, चिकित्सा कर्मचारी, वेतन कैप्स इत्यादि हैं। आपकी टीम कक्षा में खिलाड़ियों की एक सूची है, आप कह रहे हैं "एक टीम के पास खिलाड़ियों की एक सूची है", लेकिन इसमें अन्य चीजें भी हो सकती हैं।

Question

मेरे कार्यक्रमों की योजना बनाते समय, मैं अक्सर इस तरह की विचारों की श्रृंखला से शुरू होता हूं:

एक फुटबॉल टीम सिर्फ फुटबॉल खिलाड़ियों की एक सूची है। इसलिए, मुझे इसका प्रतिनिधित्व करना चाहिए:

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 माइक्रोसॉफ्ट द्वारा प्रदान की जाती है, और मेरे पास इसका कोई नियंत्रण नहीं है, इसलिए मैं इसे "सार्वजनिक एपीआई" खोलने के बाद बाद में नहीं बदल सकता । लेकिन मैं इसे समझने के लिए संघर्ष करता हूं। एक सार्वजनिक एपीआई क्या है और मुझे क्यों परवाह करना चाहिए? अगर मेरी वर्तमान परियोजना में कभी भी इस सार्वजनिक एपीआई की संभावना नहीं है, तो क्या मैं इस दिशानिर्देश को सुरक्षित रूप से अनदेखा कर सकता हूं? अगर मैं List से प्राप्त करता हूं और यह पता चला है कि मुझे एक सार्वजनिक एपीआई चाहिए, तो मुझे क्या कठिनाइयों का सामना करना पड़ेगा?

इससे कोई फर्क क्यों पड़ता है? एक सूची एक सूची है। संभवतः क्या बदल सकता है? मैं संभवतः क्या बदलना चाहता हूं?

और आखिरकार, अगर माइक्रोसॉफ्ट नहीं चाहता था कि मैं List से उत्तराधिकारी हूं, तो उन्होंने वर्ग को sealed क्यों नहीं किया?

मुझे और क्या उपयोग करना चाहिए?

जाहिर है, कस्टम संग्रह के लिए, माइक्रोसॉफ्ट ने एक 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 बजाय कॉल करना होगा। शुक्र है, सी # के साथ मैं सूचकांक को पारदर्शी बनाने और आंतरिक List सभी तरीकों को आगे बढ़ाने के लिए इंडेक्सर्स को परिभाषित कर सकता हूं ... लेकिन यह बहुत कोड है! मैं उस काम के लिए क्या प्राप्त करूं?

  2. यह सिर्फ सादा कोई समझ नहीं आता है। एक फुटबॉल टीम के पास खिलाड़ियों की सूची नहीं है। यह खिलाड़ियों की सूची है। आप यह नहीं कहते कि "जॉन मैकफूटबॉलर कुछ टीम के खिलाड़ियों में शामिल हो गया है"। आप कहते हैं "जॉन कुछ टीम में शामिल हो गया है"। आप "स्ट्रिंग के पात्रों" को एक अक्षर नहीं जोड़ते हैं, आप एक स्ट्रिंग में एक अक्षर जोड़ते हैं। आप पुस्तकालय की किताबों में कोई पुस्तक नहीं जोड़ते हैं, आप एक पुस्तकालय में एक पुस्तक जोड़ते हैं।

मुझे एहसास है कि "हुड के तहत" क्या होता है "एक्स को वाई की आंतरिक सूची जोड़ना" कहा जा सकता है, लेकिन यह दुनिया के बारे में सोचने के बहुत ही सहज ज्ञान युक्त तरीके की तरह लगता है।

मेरा प्रश्न (संक्षेप में)

डेटा संरचना का प्रतिनिधित्व करने का सही सी # तरीका क्या है, जो "तर्कसंगत" (जिसका अर्थ है, "मानव दिमाग में") कुछ घंटियों और सीटी के साथ things की एक list है?

List<T> से वंशानुगत है हमेशा अस्वीकार्य? यह कब स्वीकार्य है? क्यों नहीं? List<T> या नहीं से प्राप्त करने का निर्णय लेने पर प्रोग्रामर को क्या विचार करना चाहिए?




While I don't have a complex comparison as most of these answers do, I would like to share my method for handling this situation. By extending IEnumerable<T> , you can allow your Team class to support Linq query extensions, without publicly exposing all the methods and properties of List<T> .

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}



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.




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.




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 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.




जैसा कि सभी ने इंगित किया है, खिलाड़ियों की एक टीम खिलाड़ियों की एक सूची नहीं है। यह गलती हर जगह कई लोगों द्वारा बनाई गई है, शायद विशेषज्ञता के विभिन्न स्तरों पर। अक्सर इस मामले में समस्या सूक्ष्म और कभी-कभी बहुत सकल होती है। ऐसे डिज़ाइन खराब हैं क्योंकि ये लिस्कोव प्रतिस्थापन सिद्धांत का उल्लंघन करते हैं। इस अवधारणा को समझाने वाले इंटरनेट में कई अच्छे लेख हैं, उदाहरण के लिए, http://en.wikipedia.org/wiki/Liskov_substitution_principle

संक्षेप में, कक्षाओं के बीच अभिभावक / बाल संबंध में दो नियम संरक्षित किए जाने हैं:

  • एक बच्चे को पूरी तरह से माता-पिता को परिभाषित करने से कम कोई विशेषता नहीं होनी चाहिए।
  • बच्चे को पूरी तरह से परिभाषित करने के अलावा माता-पिता को कोई विशेषता नहीं चाहिए।

दूसरे शब्दों में, एक माता-पिता एक बच्चे की एक आवश्यक परिभाषा है, और एक बच्चा माता-पिता की पर्याप्त परिभाषा है।

यहां समाधान समाधान के माध्यम से सोचने का एक तरीका है और उपर्युक्त सिद्धांत लागू करें जो किसी को ऐसी गलती से बचने में मदद करे। किसी को यह सत्यापित करके किसी परिकल्पना का परीक्षण करना चाहिए कि क्या माता-पिता वर्ग के सभी संचालन व्युत्पन्न वर्ग के लिए संरचनात्मक रूप से और अर्थात् दोनों के लिए मान्य हैं।

  • क्या फुटबॉल टीम फुटबॉल खिलाड़ियों की एक सूची है? (एक सूची के सभी गुण एक ही अर्थ में एक टीम पर लागू होते हैं)
    • क्या एक टीम समरूप संस्थाओं का संग्रह है? हां, टीम खिलाड़ियों का संग्रह है
    • टीम के राज्य के वर्णनात्मक खिलाड़ियों को शामिल करने का आदेश है और क्या टीम यह सुनिश्चित करती है कि अनुक्रम सुरक्षित रूप से परिवर्तित होने तक संरक्षित है? नहीं, और नहीं
    • खिलाड़ियों को टीम में अपनी अनुक्रमिक स्थिति के आधार पर शामिल / गिराए जाने की उम्मीद है? नहीं

जैसा कि आप देखते हैं, केवल एक सूची की पहली विशेषता एक टीम के लिए लागू होती है। इसलिए एक टीम एक सूची नहीं है। एक सूची एक कार्यान्वयन विस्तार होगी कि आप अपनी टीम को कैसे प्रबंधित करते हैं, इसलिए इसका उपयोग केवल प्लेयर ऑब्जेक्ट्स को स्टोर करने और टीम क्लास के तरीकों से छेड़छाड़ करने के लिए किया जाना चाहिए।

इस बिंदु पर मैं टिप्पणी करना चाहता हूं कि एक टीम वर्ग को मेरी राय में, सूची का उपयोग करके भी लागू नहीं किया जाना चाहिए; इसे ज्यादातर मामलों में एक सेट डेटा संरचना (उदाहरण के लिए हैशसेट) का उपयोग करके कार्यान्वित किया जाना चाहिए।




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) विधि चाहते हैं।

यदि आप LINQ का उपयोग करना चाहते हैं, तो एक सामान्य इंटरफेस को लागू करना जैसे कि ICollection<T> या IEnumerable<T> अधिक समझ में आता है।

जैसा कि बताया गया है, संरचना इसके बारे में जाने का सही तरीका है। बस एक निजी चर के रूप में खिलाड़ियों की एक सूची लागू करें।




It depends on the behaviour of your "team" object. If it behaves just like a collection, it might be OK to represent it first with a plain List. Then you might start to notice that you keep duplicating code that iterates on the list; at this point you have the option of creating a FootballTeam object that wraps the list of players. The FootballTeam class becomes the home for all the code that iterates on the list of players.

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count. Thankfully, with C# I can define indexers to make indexing transparent, and forward all the methods of the internal List... But that's a lot of code! What do I get for all that work?

Encapsulation. Your clients need not know what goes on inside of FootballTeam. For all your clients know, it might be implemented by looking the list of players up in a database. They don't need to know, and this improves your design.

It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam". You don't add a letter to "a string's characters", you add a letter to a string. You don't add a book to a library's books, you add a book to a library.

Exactly :) you will say footballTeam.Add(john), not footballTeam.List.Add(john). The internal list will not be visible.




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.




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.




Related