Tại sao không kế thừa từ Danh sách <T>?

1459
Superbest 2014-02-11 17:01.

Khi lên kế hoạch cho các chương trình của mình, tôi thường bắt đầu với một chuỗi suy nghĩ như sau:

Một đội bóng đá chỉ là một danh sách các cầu thủ bóng đá. Do đó, tôi nên đại diện cho nó bằng:

var football_team = new List<FootballPlayer>();

Thứ tự của danh sách này đại diện cho thứ tự mà các cầu thủ được liệt kê trong danh sách.

Nhưng sau này tôi nhận ra rằng các đội cũng có những tài sản khác, ngoài danh sách cầu thủ đơn thuần, cần phải được ghi lại. Ví dụ: tổng số điểm đang chạy trong mùa giải này, ngân sách hiện tại, màu sắc đồng phục, stringđại diện cho tên của đội, v.v.

Vì vậy, sau đó tôi nghĩ:

Được rồi, một đội bóng đá giống như một danh sách các cầu thủ, nhưng ngoài ra, nó có tên (a string) và tổng số điểm (an int). .NET không cung cấp lớp lưu trữ các đội bóng đá, vì vậy tôi sẽ tạo lớp của riêng mình. Cấu trúc hiện tại tương tự và có liên quan nhất là List<FootballPlayer>, vì vậy tôi sẽ kế thừa từ nó:

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

Nhưng nó chỉ ra rằng một hướng dẫn nói rằng bạn không nên kế thừa từList<T> . Tôi hoàn toàn bối rối trước hướng dẫn này ở hai khía cạnh.

Tại sao không?

Rõ ràng Listlà bằng cách nào đó được tối ưu hóa cho hiệu suất . Làm sao vậy? Tôi sẽ gây ra vấn đề gì về hiệu suất nếu gia hạn List? Những gì chính xác sẽ phá vỡ?

Một lý do khác mà tôi đã thấy là Listdo Microsoft cung cấp và tôi không có quyền kiểm soát nó, vì vậy Kế thừa từ Danh sách <T> . Nhưng tôi đấu tranh để hiểu điều này. API công khai là gì và tại sao tôi nên quan tâm? Nếu dự án hiện tại của tôi không và có khả năng không bao giờ có API công khai này, tôi có thể bỏ qua hướng dẫn này một cách an toàn không? Nếu tôi kế thừa từ List hóa ra tôi cần một API công khai, tôi sẽ gặp khó khăn gì?

Tại sao nó thậm chí còn quan trọng? Một danh sách là một danh sách. Điều gì có thể thay đổi? Tôi có thể muốn thay đổi điều gì?

Và cuối cùng, nếu Microsoft không muốn tôi kế thừa List, tại sao họ không tạo ra lớp này sealed?

Tôi phải sử dụng cái gì khác?

Rõ ràng, đối với các bộ sưu tập tùy chỉnh, Microsoft đã cung cấp một Collectionlớp nên được mở rộng thay vì List. Nhưng lớp này rất trống rỗng, và không có nhiều thứ hữu ích, AddRange vào bộ sưu tập . Câu trả lời của jvitor83 cung cấp cơ sở lý luận về hiệu suất cho phương pháp cụ thể đó, nhưng chậm AddRangekhông tốt hơn không AddRangenhư thế nào?

Kế thừa từ Collectionlà công việc nhiều hơn kế thừa từ List, và tôi không thấy lợi ích gì. Chắc chắn Microsoft sẽ không bảo tôi làm thêm mà không có lý do, vì vậy tôi không thể tránh khỏi cảm giác như mình đang hiểu nhầm điều gì đó và kế thừa Collectionthực sự không phải là giải pháp phù hợp cho vấn đề của tôi.

Tôi đã thấy các đề xuất như triển khai IList. Không. Đây là hàng tá dòng mã viết sẵn mà tôi chẳng giúp được gì.

Cuối cùng, một số đề xuất gói Listtrong một cái gì đó:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Có hai vấn đề này:

  1. Nó làm cho mã của tôi dài dòng một cách không cần thiết. Bây giờ tôi phải gọi my_team.Players.Countthay vì chỉ my_team.Count. Rất may, với C #, tôi có thể xác định các trình chỉ mục để làm cho việc lập chỉ mục trở nên minh bạch và chuyển tiếp tất cả các phương thức của nội bộ List... Nhưng đó là rất nhiều mã! Tôi nhận được gì cho tất cả công việc đó?

  2. Nó chỉ đơn giản là không có ý nghĩa gì cả. Một đội bóng không "có" danh sách các cầu thủ. Đó danh sách các cầu thủ. Bạn không nói "John McFootballer đã tham gia cùng người chơi của SomeTeam". Bạn nói "John đã tham gia SomeTeam". Bạn không thêm một chữ cái vào "các ký tự của một chuỗi", bạn thêm một chữ cái vào một chuỗi. Bạn không thêm sách vào sách của thư viện, bạn thêm sách vào thư viện.

Tôi nhận ra rằng những gì xảy ra "chui" có thể được cho là "thêm X vào danh sách nội bộ của Y", nhưng đây có vẻ như là một cách nghĩ rất phản trực giác về thế giới.

Câu hỏi của tôi (tóm tắt)

Cách chính xác của C # để biểu diễn một cấu trúc dữ liệu, mà "về mặt logic" (có nghĩa là "đối với tâm trí con người") chỉ là một listtrong thingsvài tiếng chuông và tiếng còi?

Kế thừa từ List<T>luôn luôn không được chấp nhận? Khi nào thì chấp nhận được? Tại sao tại sao không? Một lập trình viên phải cân nhắc điều gì khi quyết định có kế thừa từ List<T>hay không?

27 answers

1625
Eric Lippert 2014-02-11 19:43.

Có một số câu trả lời tốt ở đây. Tôi sẽ bổ sung cho họ những điểm sau đây.

Cách chính xác của C # để biểu diễn một cấu trúc dữ liệu, mà "về mặt logic" (có nghĩa là "đối với tâm trí con người") chỉ là một danh sách những thứ với một vài tiếng chuông và tiếng còi?

Yêu cầu mười người không phải là lập trình viên máy tính quen thuộc với sự tồn tại của bóng đá điền vào chỗ trống:

Một đội bóng đá là một loại _____ cụ thể

ai nói "danh sách các cầu thủ bóng đá với một vài hồi chuông và còi", hoặc tất cả họ đều nói "đội thể thao" hoặc "câu lạc bộ" hoặc "tổ chức"? Khái niệm của bạn rằng một đội bóng đá là một loại danh sách các cầu thủ cụ thể nằm trong tâm trí con người của bạn và con người của bạn.

List<T>là một cơ chế . Đội bóng đá là một đối tượng kinh doanh - tức là một đối tượng đại diện cho một số khái niệm thuộc lĩnh vực kinh doanh của chương trình. Đừng trộn chúng! Một đội bóng đá là một loại đội; nó có một danh sách, một danh sách là một danh sách các cầu thủ . Một danh sách không phải là một loại danh sách các cầu thủ cụ thể . Một danh sách một danh sách các cầu thủ. Vì vậy, hãy tạo một thuộc tính được gọi Rosterlà a List<Player>. Và hãy thực hiện nó ReadOnlyList<Player>khi bạn đang ở đó, trừ khi bạn tin rằng tất cả những người biết về một đội bóng đá đều có thể xóa cầu thủ khỏi danh sách.

Kế thừa từ List<T>luôn luôn không được chấp nhận?

Không thể chấp nhận được với ai? Tôi? Không.

Khi nào thì chấp nhận được?

Khi bạn đang xây dựng một cơ chế mở rộng List<T>cơ chế .

Một lập trình viên phải cân nhắc điều gì khi quyết định có kế thừa từ List<T>hay không?

Tôi đang xây dựng một cơ chế hay một đối tượng kinh doanh ?

Nhưng đó là rất nhiều mã! Tôi nhận được gì cho tất cả công việc đó?

Bạn đã dành nhiều thời gian hơn để gõ câu hỏi của mình và bạn sẽ mất List<T>hơn 50 lần để viết phương thức chuyển tiếp cho các thành viên có liên quan . Rõ ràng là bạn không sợ dài dòng, và chúng ta đang nói về một lượng rất nhỏ mã ở đây; đây là một vài phút làm việc.

CẬP NHẬT

Tôi đã suy nghĩ kỹ hơn và có một lý do khác để không mô hình hóa một đội bóng đá như một danh sách các cầu thủ. Trên thực tế, có thể là một ý tưởng tồi nếu mô hình một đội bóng đá cũng một danh sách các cầu thủ. Vấn đề với một đội khi có / có danh sách người chơi là những gì bạn có là ảnh chụp nhanh của đội tại một thời điểm . Tôi không biết trường hợp kinh doanh của bạn cho lớp này là gì, nhưng nếu tôi có một lớp đại diện cho một đội bóng đá, tôi muốn hỏi nó những câu hỏi như "có bao nhiêu cầu thủ Seahawks bỏ lỡ trận đấu do chấn thương từ năm 2003 đến năm 2013?" hoặc "Cầu thủ Denver nào trước đây chơi cho một đội khác có số sân tăng nhiều nhất hàng năm?" hoặc " Piggers đã đi hết con đường trong năm nay? "

Đó là, với tôi, một đội bóng dường như được mô phỏng rất tốt như một tập hợp các dữ kiện lịch sử như thời điểm một cầu thủ được tuyển dụng, chấn thương, giải nghệ, v.v. Rõ ràng danh sách cầu thủ hiện tại là một thực tế quan trọng có lẽ nên được xem xét trước và- trung tâm, nhưng có thể có những điều thú vị khác mà bạn muốn làm với đối tượng này đòi hỏi quan điểm lịch sử hơn.

169
myermian 2014-02-11 17:26.

Chà, bài đăng của bạn có vô số câu hỏi và điểm. Hầu hết các lý do bạn nhận được từ Microsoft là chính xác. Hãy bắt đầu với mọi thứ vềList<T>

  • List<T> được tối ưu hóa cao. Cách sử dụng chính của nó là được sử dụng như một thành viên riêng của một đối tượng.
  • Microsoft không con dấu nó bởi vì đôi khi bạn có thể muốn tạo ra một lớp mà có một cái tên thân thiện: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. Bây giờ nó dễ dàng như làm var list = new MyList<int, string>();.
  • CA1002: Không để lộ danh sách chung chung : Về cơ bản, ngay cả khi bạn định sử dụng ứng dụng này với tư cách là nhà phát triển duy nhất, bạn nên phát triển bằng các phương pháp viết mã tốt, để chúng trở nên thấm nhuần với bạn và bản chất thứ hai. Bạn vẫn được phép hiển thị danh sách IList<T>nếu bạn cần bất kỳ người tiêu dùng nào có danh sách được lập chỉ mục. Điều này cho phép bạn thay đổi việc triển khai trong một lớp sau này.
  • Microsoft đã tạo ra Collection<T>rất chung chung bởi vì nó là một khái niệm chung chung ... cái tên nói lên tất cả; nó chỉ là một bộ sưu tập. Có phiên bản chính xác hơn ví dụ như SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>vv mỗi trong số đó thực hiện IList<T>nhưng không List<T> .
  • Collection<T>cho phép các thành viên (tức là Thêm, Xóa, v.v.) bị ghi đè vì chúng là ảo. List<T>không làm.
  • Phần cuối cùng của câu hỏi của bạn là đúng. Một đội bóng đá không chỉ là một danh sách các cầu thủ, vì vậy nó phải là một lớp chứa danh sách các cầu thủ đó. Suy nghĩ thành phần và kế thừa . Một đội bóng đá một danh sách các cầu thủ (một đội hình), nó không phải là một danh sách các cầu thủ.

Nếu tôi đang viết mã này, lớp có thể sẽ trông giống như vậy:

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.
}
155
Nean Der Thal 2014-02-12 05:45.
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

Mã trước đây có nghĩa là: một nhóm người trên phố chơi bóng, và họ tình cờ có tên. Cái gì đó như:

Dù sao, mã này (từ câu trả lời của tôi)

public class FootballTeam
{
    // A team's name
    public string TeamName; 

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

Có nghĩa là: đây là một đội bóng đá có quản lý, cầu thủ, quản trị viên, v.v. Đại loại như:

Đây là cách logic của bạn được trình bày trong hình ảnh…

117
Tim B 2014-02-12 00:58.

Đây là một ví dụ cổ điển về thành phần so với kế thừa .

Trong trường hợp cụ thể này:

Đội có phải là danh sách các cầu thủ có thêm hành vi không

hoặc là

Đội có phải là một đối tượng của riêng nó mà tình cờ chứa một danh sách các cầu thủ.

Bằng cách mở rộng Danh sách, bạn đang tự giới hạn mình theo một số cách:

  1. Bạn không thể hạn chế quyền truy cập (ví dụ: ngăn mọi người thay đổi danh sách). Bạn nhận được tất cả các phương thức Danh sách cho dù bạn có cần / muốn tất cả hay không.

  2. Điều gì xảy ra nếu bạn cũng muốn có danh sách những thứ khác. Ví dụ: các đội có huấn luyện viên, người quản lý, người hâm mộ, thiết bị, v.v. Một số trong số đó có thể là danh sách theo đúng nghĩa của họ.

  3. Bạn giới hạn các tùy chọn của mình để thừa kế. Ví dụ: bạn có thể muốn tạo một đối tượng Đội chung, và sau đó có BaseballTeam, FootballTeam, v.v. kế thừa từ đó. Để kế thừa từ Danh sách, bạn cần thực hiện kế thừa từ Nhóm, nhưng điều đó có nghĩa là tất cả các loại nhóm khác nhau buộc phải có cùng một cách triển khai danh sách đó.

Thành phần - bao gồm một đối tượng đưa ra hành vi bạn muốn bên trong đối tượng của mình.

Thừa kế - đối tượng của bạn trở thành một thể hiện của đối tượng có hành vi mà bạn muốn.

Cả hai đều có công dụng của chúng, nhưng đây là một trường hợp rõ ràng mà thành phần được ưu tiên hơn.

67
Satyan Raina 2014-02-12 06:56.

Như mọi người đã chỉ ra, một đội tuyển thủ không phải là danh sách các cầu thủ. Sai lầm này được thực hiện bởi nhiều người ở khắp mọi nơi, có lẽ ở nhiều cấp độ chuyên môn khác nhau. Thường thì vấn đề là tế nhị và đôi khi rất thô thiển, như trong trường hợp này. Những thiết kế như vậy là xấu vì chúng vi phạm Nguyên tắc thay thế Liskov . Internet có nhiều bài viết hay giải thích khái niệm này, ví dụ: http://en.wikipedia.org/wiki/Liskov_substitution_principle

Tóm lại, có hai quy tắc cần được duy trì trong mối quan hệ Cha / Con giữa các lớp:

  • một Con không được yêu cầu đặc điểm nào ít hơn những gì hoàn toàn xác định Cha mẹ.
  • Cha mẹ không được yêu cầu đặc điểm nào ngoài những gì hoàn toàn xác định Con.

Nói cách khác, Cha mẹ là một định nghĩa cần thiết về một đứa trẻ, và một đứa trẻ là một định nghĩa đầy đủ về Cha mẹ.

Dưới đây là một cách để suy nghĩ về các giải pháp và áp dụng nguyên tắc trên sẽ giúp bạn tránh được sai lầm như vậy. Người ta nên kiểm tra giả thuyết bằng cách xác minh xem tất cả các hoạt động của lớp cha có hợp lệ cho lớp dẫn xuất cả về mặt cấu trúc và ngữ nghĩa hay không.

  • Một đội bóng đá có phải là một danh sách các cầu thủ bóng đá không? (Tất cả các thuộc tính của danh sách có áp dụng cho một nhóm theo cùng một ý nghĩa không)
    • Một nhóm có phải là một tập hợp các thực thể đồng nhất? Có, đội là một tập hợp những Người chơi
    • Thứ tự bao gồm các cầu thủ có mô tả trạng thái của đội không và đội có đảm bảo rằng trình tự được giữ nguyên trừ khi thay đổi rõ ràng không? Không và không
    • Người chơi có được dự kiến ​​sẽ được đưa vào / loại bỏ dựa trên vị trí thứ tự của họ trong đội không? Không

Như bạn thấy, chỉ đặc điểm đầu tiên của danh sách có thể áp dụng cho một nhóm. Do đó, một đội không phải là một danh sách. Danh sách sẽ là chi tiết triển khai về cách bạn quản lý nhóm của mình, vì vậy nó chỉ nên được sử dụng để lưu trữ các đối tượng người chơi và được thao tác bằng các phương thức của lớp Nhóm.

Tại thời điểm này, tôi muốn lưu ý rằng một lớp Nhóm, theo ý kiến ​​của tôi, thậm chí không nên được triển khai bằng cách sử dụng Danh sách; nó nên được triển khai bằng cách sử dụng cấu trúc dữ liệu Set (ví dụ: HashSet) trong hầu hết các trường hợp.

50
Sam Leach 2014-02-13 01:41.

Điều gì sẽ xảy ra nếu FootballTeamcó một đội dự bị cùng với đội chính?

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

Làm thế nào bạn sẽ mô hình đó với?

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

Mối quan hệ rõ ràng là có a và không là a .

hoặc RetiredPlayers?

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

Theo quy tắc chung, nếu bạn muốn kế thừa từ một tập hợp, hãy đặt tên cho lớp SomethingCollection.

SomethingCollectionNgữ nghĩa của bạn có ý nghĩa không? Chỉ làm điều này nếu loại của bạn là một bộ sưu tập Something.

Trong trường hợp của FootballTeamnó âm thanh không đúng. A Teamnhiều hơn a Collection. A Teamcó thể có huấn luyện viên, người đào tạo, v.v. như các câu trả lời khác đã chỉ ra.

FootballCollectionâm thanh giống như một bộ sưu tập bóng đá hoặc có thể là một bộ sưu tập đồ dùng bóng đá. TeamCollection, một tập hợp các đội.

FootballPlayerCollectionnghe giống như một tập hợp các trình phát sẽ là tên hợp lệ cho một lớp kế thừa List<FootballPlayer>nếu bạn thực sự muốn làm điều đó.

Thực sự List<FootballPlayer>là một loại hoàn toàn tốt để đối phó. Có thể IList<FootballPlayer>nếu bạn đang trả về nó từ một phương thức.

Tóm tắt

Tự hỏi mình đi

  1. X một Y? hoặc X một Y?

  2. Tên lớp của tôi có nghĩa là gì không?

39
El Zorko 2014-02-12 12:27.

Thiết kế> Thực hiện

Những phương pháp và thuộc tính nào bạn thể hiện là một quyết định thiết kế. Lớp cơ sở nào bạn kế thừa là một chi tiết triển khai. Tôi cảm thấy thật đáng để lùi lại một bước trước đây.

Đối tượng là một tập hợp dữ liệu và hành vi.

Vì vậy, câu hỏi đầu tiên của bạn nên là:

  • Đối tượng này bao gồm dữ liệu nào trong mô hình tôi đang tạo?
  • Đối tượng này thể hiện hành vi nào trong mô hình đó?
  • Điều này có thể thay đổi như thế nào trong tương lai?

Hãy nhớ rằng kế thừa ngụ ý mối quan hệ "isa" (là một), trong khi thành phần ngụ ý mối quan hệ "có" (hasa). Hãy chọn một cái phù hợp với tình huống của bạn theo quan điểm của bạn, lưu ý rằng mọi thứ có thể đi đến đâu khi ứng dụng của bạn phát triển.

Hãy cân nhắc việc suy nghĩ theo các giao diện trước khi bạn suy nghĩ theo kiểu cụ thể, vì một số người cảm thấy dễ dàng hơn khi đặt bộ não của họ vào "chế độ thiết kế" theo cách đó.

Đây không phải là điều mà mọi người đều làm một cách có ý thức ở cấp độ này trong việc viết mã hàng ngày. Nhưng nếu bạn đang nghiền ngẫm loại chủ đề này, bạn đang bước vào vùng nước thiết kế. Nhận thức được nó có thể được giải phóng.

Xem xét các chi tiết thiết kế cụ thể

Hãy xem Danh sách <T> và IList <T> trên MSDN hoặc Visual Studio. Xem những phương thức và thuộc tính nào mà chúng thể hiện. Có phải tất cả các phương pháp này đều giống như một điều gì đó mà ai đó muốn làm với FootballTeam theo quan điểm của bạn?

FootballTeam.Reverse () có hợp lý với bạn không? FootballTeam.ConvertAll <TOutput> () có giống thứ bạn muốn không?

Đây không phải là một câu hỏi mẹo; câu trả lời thực sự có thể là "có". Nếu bạn triển khai / kế thừa Danh sách <Trình phát> hoặc IList <Trình phát>, bạn bị mắc kẹt với chúng; nếu đó là lý tưởng cho mô hình của bạn, hãy làm điều đó.

Nếu bạn quyết định có, điều đó hợp lý và bạn muốn đối tượng của mình có thể được coi là một tập hợp / danh sách các trình phát (hành vi), và do đó bạn muốn triển khai ICollection hoặc IList, bằng mọi cách. Tùy ý:

class FootballTeam : ... ICollection<Player>
{
    ...
}

Nếu bạn muốn đối tượng của mình chứa một bộ sưu tập / danh sách người chơi (dữ liệu), và do đó bạn muốn bộ sưu tập hoặc danh sách là một tài sản hoặc thành viên, hãy làm như vậy. Tùy ý:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

Bạn có thể cảm thấy rằng bạn muốn mọi người chỉ có thể liệt kê nhóm người chơi, thay vì đếm họ, thêm vào hoặc xóa họ. IEnumerable <Player> là một lựa chọn hoàn toàn hợp lệ để xem xét.

Bạn có thể cảm thấy rằng không có giao diện nào trong số này hữu ích trong mô hình của bạn cả. Điều này ít xảy ra hơn (IEnumerable <T> hữu ích trong nhiều trường hợp) nhưng vẫn có thể.

Bất cứ ai cố gắng nói với bạn rằng một trong những điều này là sai rõ ràng và chắc chắn trong mọi trường hợp đều là sai lầm. Bất cứ ai cố gắng nói với bạn điều đó là rõ ràng và chắc chắn đúng trong mọi trường hợp đều là sai lầm.

Chuyển sang Triển khai

Khi bạn đã quyết định về dữ liệu và hành vi, bạn có thể đưa ra quyết định về việc triển khai. Điều này bao gồm các lớp cụ thể mà bạn phụ thuộc vào thông qua kế thừa hoặc thành phần.

Đây có thể không phải là một bước tiến lớn và mọi người thường nhầm lẫn giữa thiết kế và triển khai vì bạn hoàn toàn có thể lướt qua tất cả trong đầu trong một hoặc hai giây và bắt đầu gõ đi.

Một thử nghiệm suy nghĩ

Một ví dụ nhân tạo: như những người khác đã đề cập, một đội không phải lúc nào cũng "chỉ là" một tập hợp các cầu thủ. Bạn có duy trì một bộ sưu tập điểm số trận đấu cho đội không? Đội có thể hoán đổi cho nhau với câu lạc bộ, trong mô hình của bạn không? Nếu vậy, và nếu đội của bạn là một tập hợp các cầu thủ, có lẽ đó cũng là một tập hợp các nhân viên và / hoặc một tập hợp các điểm số. Sau đó, bạn kết thúc với:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

Mặc dù thiết kế, tại thời điểm này trong C #, bạn sẽ không thể thực hiện tất cả những điều này bằng cách kế thừa từ List <T>, vì C # "only" hỗ trợ kế thừa đơn. (Nếu bạn đã thử điều này trong C ++, bạn có thể coi đây là Điều tốt.) Việc triển khai một bộ sưu tập thông qua kế thừa và một bộ sưu tập thông qua thành phần có thể cảm thấy bẩn. Và các thuộc tính như Count trở nên khó hiểu với người dùng trừ khi bạn triển khai ILIst <Player> .Count và IList <StaffMember> .Count, v.v. một cách rõ ràng, và sau đó chúng chỉ gây đau đớn thay vì khó hiểu. Bạn có thể thấy điều này sẽ đi đến đâu; Cảm giác cồn cào trong khi suy nghĩ về con đường này có thể cho bạn biết cảm giác sai lầm khi đi theo hướng này (và đúng hay sai, đồng nghiệp của bạn cũng có thể xảy ra nếu bạn thực hiện theo cách này!)

Câu trả lời ngắn (Quá muộn)

Hướng dẫn về việc không kế thừa từ các lớp tập hợp không dành riêng cho C #, bạn sẽ tìm thấy nó trong nhiều ngôn ngữ lập trình. Nó nhận được sự khôn ngoan không phải là một luật. Một lý do là trong thực tế, thành phần được coi là thường giành chiến thắng hơn tính kế thừa về tính dễ hiểu, khả năng thực hiện và khả năng bảo trì. Đối với các đối tượng trong thế giới thực / miền phổ biến hơn để tìm các mối quan hệ "băm" hữu ích và nhất quán hơn các mối quan hệ "isa" hữu ích và nhất quán trừ khi bạn đi sâu vào phần tóm tắt, đặc biệt là khi thời gian trôi qua và dữ liệu chính xác và hành vi của các đối tượng trong mã những thay đổi. Điều này không khiến bạn luôn loại trừ việc kế thừa từ các lớp tập hợp; nhưng nó có thể gợi ý.

34
Dmitry S. 2014-02-11 17:25.

Trước hết, nó liên quan đến khả năng sử dụng. Nếu bạn sử dụng kế thừa, Teamlớp sẽ hiển thị hành vi (phương thức) được thiết kế hoàn toàn cho thao tác đối tượng. Ví dụ, AsReadOnly()hoặc CopyTo(obj)các phương thức không có ý nghĩa đối với đối tượng nhóm. Thay vì AddRange(items)phương pháp, bạn có thể muốn một AddPlayers(players)phương pháp mô tả hơn .

Nếu bạn muốn sử dụng LINQ, việc triển khai một giao diện chung như ICollection<T>hoặc IEnumerable<T>sẽ có ý nghĩa hơn.

Như đã đề cập, sáng tác là cách thích hợp để thực hiện nó. Chỉ cần triển khai danh sách người chơi dưới dạng biến riêng.

29
Mauro Sampietro 2014-02-12 06:25.

Một đội bóng đá không phải là một danh sách các cầu thủ bóng đá. Một đội bóng đá bao gồm một danh sách các cầu thủ bóng đá!

Điều này là sai về mặt logic:

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

và điều này là chính xác:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}
29
user1852503 2014-02-16 21:56.

Hãy để tôi viết lại câu hỏi của bạn. vì vậy bạn có thể nhìn đối tượng từ một góc độ khác.

Khi tôi cần đại diện cho một đội bóng đá, tôi hiểu rằng về cơ bản đó là một cái tên. Như: "The Eagles"

string team = new string();

Sau đó, tôi nhận ra các đội cũng có người chơi.

Tại sao tôi không thể mở rộng loại chuỗi để nó cũng chứa danh sách người chơi?

Quan điểm của bạn vào vấn đề là tùy ý. Hãy thử nghĩ xem một đội (thuộc tính) gì chứ không phải nó gì .

Sau khi làm điều đó, bạn có thể xem nó có chia sẻ thuộc tính với các lớp khác hay không. Và hãy nghĩ về sự kế thừa.

28
stefan.schwetschke 2014-02-12 00:10.

Nó phụ thuộc vào ngữ cảnh

Khi bạn coi đội của mình như một danh sách các cầu thủ, bạn đang phóng chiếu "ý tưởng" về một đội bóng bằng chân xuống một khía cạnh: Bạn giảm bớt "đội" cho những người bạn thấy trên sân. Phép chiếu này chỉ đúng trong một ngữ cảnh nhất định. Trong một bối cảnh khác, điều này có thể hoàn toàn sai. Hãy tưởng tượng bạn muốn trở thành nhà tài trợ của đội. Vì vậy, bạn phải nói chuyện với những người quản lý của đội. Trong bối cảnh này, nhóm dự kiến ​​sẽ có danh sách những người quản lý của mình. Và hai danh sách này thường không trùng lặp nhiều. Các bối cảnh khác là hiện tại so với các cầu thủ cũ, v.v.

Ngữ nghĩa không rõ ràng

Vì vậy, vấn đề khi coi một đội như một danh sách các cầu thủ của nó là ngữ nghĩa của nó phụ thuộc vào ngữ cảnh và nó không thể mở rộng khi bối cảnh thay đổi. Ngoài ra, rất khó để diễn đạt, bạn đang sử dụng ngữ cảnh nào.

Các lớp có thể mở rộng

Khi bạn sử dụng một lớp chỉ có một thành viên (ví dụ IList activePlayers), bạn có thể sử dụng tên của thành viên (và thêm vào đó là chú thích của nó) để làm rõ ngữ cảnh. Khi có thêm ngữ cảnh, bạn chỉ cần thêm một thành viên bổ sung.

Các lớp học phức tạp hơn

Trong một số trường hợp, việc tạo thêm một lớp học có thể là quá mức cần thiết. Mỗi định nghĩa lớp phải được tải thông qua trình nạp lớp và sẽ được lưu vào bộ nhớ đệm của máy ảo. Điều này làm bạn tốn hiệu suất thời gian chạy và bộ nhớ. Khi bạn có một bối cảnh rất cụ thể, bạn có thể coi một đội bóng đá như một danh sách các cầu thủ. Nhưng trong trường hợp này, bạn thực sự chỉ nên sử dụng a IList , không phải một lớp dẫn xuất từ ​​nó.

Kết luận / Cân nhắc

Khi bạn có một bối cảnh rất cụ thể, bạn có thể coi một đội như một danh sách các cầu thủ. Ví dụ bên trong một phương thức, bạn hoàn toàn có thể viết:

IList<Player> footballTeam = ...

Khi sử dụng F #, bạn có thể tạo một kiểu viết tắt:

type FootballTeam = IList<Player>

Nhưng khi bối cảnh rộng hơn hoặc thậm chí không rõ ràng, bạn không nên làm điều này. Điều này đặc biệt xảy ra khi bạn tạo một lớp mới mà ngữ cảnh mà nó có thể được sử dụng trong tương lai không rõ ràng. Dấu hiệu cảnh báo là khi bạn bắt đầu thêm các thuộc tính bổ sung vào lớp của mình (tên đội, huấn luyện viên, v.v.). Đây là một dấu hiệu rõ ràng rằng bối cảnh nơi lớp sẽ được sử dụng là không cố định và sẽ thay đổi trong tương lai. Trong trường hợp này, bạn không thể coi đội như một danh sách người chơi, nhưng bạn nên lập mô hình danh sách những người chơi (hiện đang hoạt động, không bị thương, v.v.) như một thuộc tính của đội.

20
Mark Brackett 2015-04-07 11:35.

Chỉ bởi vì tôi nghĩ rằng các câu trả lời khác khá nhiều liên quan đến việc liệu một đội bóng đá "is-a" List<FootballPlayer>hay "has-a" List<FootballPlayer>, điều này thực sự không trả lời câu hỏi này như đã viết.

OP chủ yếu yêu cầu làm rõ về các nguyên tắc kế thừa từ List<T>:

Một hướng dẫn nói rằng bạn không nên kế thừa từ List<T>. Tại sao không?

Bởi vì List<T>không có phương pháp ảo. Đây ít vấn đề hơn trong mã của riêng bạn, vì bạn thường có thể chuyển đổi việc triển khai mà tương đối ít đau - nhưng có thể là một vấn đề lớn hơn nhiều trong một API công khai.

API công khai là gì và tại sao tôi nên quan tâm?

API công khai là một giao diện mà bạn tiếp xúc với các lập trình viên bên thứ ba. Hãy suy nghĩ mã khung. Và hãy nhớ rằng các nguyên tắc đang được tham chiếu là " Nguyên tắc thiết kế .NET Framework " chứ không phải " Nguyên tắc thiết kế ứng dụng .NET ". Có một sự khác biệt và - nói chung - thiết kế API công khai nghiêm ngặt hơn rất nhiều.

Nếu dự án hiện tại của tôi không và có khả năng không bao giờ có API công khai này, tôi có thể bỏ qua hướng dẫn này một cách an toàn không? Nếu tôi kế thừa từ List và hóa ra tôi cần một API công khai, tôi sẽ gặp khó khăn gì?

Khá nhiều, vâng. Bạn có thể muốn xem xét lý do đằng sau nó để xem liệu nó có áp dụng cho tình huống của bạn hay không, nhưng nếu bạn không xây dựng một API công khai thì bạn không cần phải lo lắng về các mối quan tâm của API như lập phiên bản (trong đó, đây là một tập hợp con).

Nếu bạn thêm một API công khai trong tương lai, bạn sẽ cần phải loại bỏ API của mình khỏi quá trình triển khai của mình (bằng cách không tiết lộ List<T>trực tiếp của bạn ) hoặc vi phạm các nguyên tắc có thể xảy ra trong tương lai.

Tại sao nó thậm chí còn quan trọng? Một danh sách là một danh sách. Điều gì có thể thay đổi? Tôi có thể muốn thay đổi điều gì?

Tùy thuộc vào bối cảnh, nhưng vì chúng tôi đang sử dụng FootballTeamlàm ví dụ - hãy tưởng tượng rằng bạn không thể thêm một FootballPlayernếu điều đó sẽ khiến nhóm vượt quá giới hạn tiền lương. Một cách có thể để thêm điều đó sẽ giống như:

 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!");
        }
     }
 }

À ... nhưng bạn không thể override Addvì nó không phải virtual(vì lý do hiệu suất).

Nếu bạn đang ở trong một ứng dụng (về cơ bản, có nghĩa là bạn và tất cả những người gọi của bạn được biên dịch cùng nhau) thì bây giờ bạn có thể chuyển sang sử dụng IList<T>và sửa mọi lỗi biên dịch:

 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 */
 }

nhưng, nếu bạn đã tiếp xúc công khai với bên thứ ba, bạn vừa thực hiện một thay đổi vi phạm sẽ gây ra lỗi biên dịch và / hoặc thời gian chạy.

TL; DR - nguyên tắc dành cho các API công khai. Đối với các API riêng tư, hãy làm những gì bạn muốn.

18
Cruncher 2014-02-12 11:42.

Có cho phép mọi người nói

myTeam.subList(3, 5);

có ý nghĩa gì không? Nếu không thì nó không phải là một Danh sách.

16
xpmatteo 2014-02-12 06:51.

Nó phụ thuộc vào hành vi của đối tượng "đội" của bạn. Nếu nó hoạt động giống như một bộ sưu tập, thì có thể được phép đại diện nó trước bằng một Danh sách đơn giản. Sau đó, bạn có thể bắt đầu nhận thấy rằng bạn tiếp tục sao chép mã lặp lại trên danh sách; tại thời điểm này, bạn có tùy chọn tạo một đối tượng FootballTeam bao bọc danh sách các cầu thủ. Lớp FootballTeam trở thành nơi lưu trữ tất cả các mã lặp lại trên danh sách các cầu thủ.

Nó làm cho mã của tôi dài dòng một cách không cần thiết. Bây giờ tôi phải gọi my_team.Players.Count thay vì chỉ my_team.Count. Rất may, với C #, tôi có thể định nghĩa các chỉ mục để làm cho việc lập chỉ mục trở nên minh bạch và chuyển tiếp tất cả các phương thức của Danh sách nội bộ ... Nhưng đó là rất nhiều mã! Tôi nhận được gì cho tất cả công việc đó?

Đóng gói. Khách hàng của bạn không cần biết những gì đang diễn ra bên trong FootballTeam. Đối với tất cả những gì khách hàng của bạn biết, nó có thể được triển khai bằng cách tìm kiếm danh sách người chơi trong cơ sở dữ liệu. Họ không cần biết, và điều này cải thiện thiết kế của bạn.

Nó chỉ đơn giản là không có ý nghĩa gì cả. Một đội bóng không "có" danh sách các cầu thủ. Đó là danh sách các cầu thủ. Bạn không nói "John McFootballer đã tham gia cùng người chơi của SomeTeam". Bạn nói "John đã tham gia SomeTeam". Bạn không thêm một chữ cái vào "các ký tự của một chuỗi", bạn thêm một chữ cái vào một chuỗi. Bạn không thêm sách vào sách của thư viện, bạn thêm sách vào thư viện.

Chính xác :) bạn sẽ nói footballTeam.Add (john), không phải footballTeam.List.Add (john). Danh sách nội bộ sẽ không hiển thị.

15
Disillusioned 2014-02-16 10:05.

Có rất nhiều câu trả lời tuyệt vời ở đây, nhưng tôi muốn đề cập đến điều mà tôi chưa thấy đề cập: Thiết kế hướng đối tượng là trao quyền cho các đối tượng .

Bạn muốn đóng gói tất cả các quy tắc, công việc bổ sung và các chi tiết nội bộ bên trong một đối tượng thích hợp. Bằng cách này, các đối tượng khác tương tác với đối tượng này không phải lo lắng về tất cả. Trên thực tế, bạn muốn tiến thêm một bước nữa và chủ động ngăn chặn các đối tượng khác bỏ qua các nội dung này.

Khi bạn kế thừa từ List, tất cả các đối tượng khác có thể xem bạn là một Danh sách. Họ có quyền truy cập trực tiếp vào các phương pháp thêm và xóa người chơi. Và bạn sẽ mất kiểm soát; ví dụ:

Giả sử bạn muốn phân biệt thời điểm một cầu thủ rời đi bằng cách biết họ đã nghỉ hưu, từ chức hay bị sa thải. Bạn có thể triển khai một RemovePlayerphương thức sử dụng một enum đầu vào thích hợp. Tuy nhiên, bằng cách kế thừa từ List, bạn sẽ không thể ngăn chặn truy cập trực tiếp đến Remove, RemoveAllvà thậm chí cả Clear. Kết quả là, bạn đã thực sự rời bỏFootballTeam lớp học của mình .


Suy nghĩ bổ sung về tính đóng gói ... Bạn đã nêu ra mối quan tâm sau:

Nó làm cho mã của tôi dài dòng một cách không cần thiết. Bây giờ tôi phải gọi my_team.Players.Count thay vì chỉ my_team.Count.

Bạn chính xác, điều đó sẽ không cần thiết phải dài dòng cho tất cả khách hàng sử dụng nhóm của bạn. Tuy nhiên, vấn đề đó là rất nhỏ so với việc bạn đã tiếp xúc List Playersvới tất cả và những việc lặt vặt để họ có thể nghịch phá nhóm của bạn mà không cần sự đồng ý của bạn.

Bạn tiếp tục nói:

Nó chỉ đơn giản là không có ý nghĩa gì cả. Một đội bóng không "có" danh sách các cầu thủ. Đó là danh sách các cầu thủ. Bạn không nói "John McFootballer đã tham gia cùng người chơi của SomeTeam". Bạn nói "John đã tham gia SomeTeam".

Bạn đã sai ở điểm đầu tiên: Bỏ từ 'danh sách', và thực sự rõ ràng rằng một đội có người chơi.
Tuy nhiên, bạn đã đánh chiếc đinh vào đầu bằng cái thứ hai. Bạn không muốn khách hàng gọi ateam.Players.Add(...). Bạn muốn họ gọi ateam.AddPlayer(...). Và triển khai của bạn sẽ (có thể trong số những thứ khác) gọi Players.Add(...)nội bộ.


Hy vọng rằng bạn có thể thấy đóng gói quan trọng như thế nào đối với mục tiêu trao quyền cho các đối tượng của bạn. Bạn muốn cho phép mỗi lớp thực hiện tốt công việc của mình mà không sợ bị các đối tượng khác can thiệp.

12
ArturoTena 2014-02-12 08:03.

Cách đúng trong C # để biểu diễn cấu trúc dữ liệu là gì ...

Hãy nhớ rằng, "Tất cả các mô hình đều sai, nhưng một số mô hình hữu ích." - Hộp EP George

Không có một "cách đúng", chỉ có một cách hữu ích.

Chọn một cái hữu ích cho bạn và / người dùng của bạn. Đó là nó. Hãy phát triển kinh tế, đừng kỹ sư hóa quá mức. Bạn càng viết ít mã, bạn sẽ cần gỡ lỗi càng ít mã. (đọc các ấn bản sau).

- Đã chỉnh sửa

Câu trả lời tốt nhất của tôi sẽ là ... nó phụ thuộc. Việc kế thừa từ một Danh sách sẽ khiến các khách hàng của lớp này tiếp xúc với các phương thức có thể không được tiết lộ, chủ yếu vì FootballTeam trông giống như một thực thể kinh doanh.

- Phiên bản 2

Tôi thực sự không nhớ những gì tôi đã đề cập đến trong nhận xét "đừng kỹ sư quá mức". Mặc dù tôi tin rằng tư duy KISS là một hướng dẫn tốt, nhưng tôi muốn nhấn mạnh rằng việc kế thừa một lớp kinh doanh từ List sẽ tạo ra nhiều vấn đề hơn là nó giải quyết được, do rò rỉ trừu tượng .

Mặt khác, tôi tin rằng có một số trường hợp hạn chế mà chỉ cần kế thừa từ Danh sách là hữu ích. Như tôi đã viết trong ấn bản trước, nó phụ thuộc. Câu trả lời cho mỗi trường hợp bị ảnh hưởng nhiều bởi cả kiến ​​thức, kinh nghiệm và sở thích cá nhân.

Cảm ơn @kai đã giúp tôi suy nghĩ chính xác hơn về câu trả lời.

11
Paul J Abernathy 2014-02-12 04:56.

Điều này làm tôi nhớ đến sự cân bằng "Là một" so với "có một". Đôi khi việc kế thừa trực tiếp từ một siêu lớp sẽ dễ dàng hơn và có ý nghĩa hơn. Những lần khác, sẽ có ý nghĩa hơn khi tạo một lớp độc lập và bao gồm lớp mà bạn đã kế thừa từ đó làm biến thành viên. Bạn vẫn có thể truy cập chức năng của lớp nhưng không bị ràng buộc với giao diện hoặc bất kỳ ràng buộc nào khác có thể đến từ việc kế thừa từ lớp.

Bạn làm nghề nào? Như với rất nhiều thứ ... nó phụ thuộc vào ngữ cảnh. Hướng dẫn mà tôi sẽ sử dụng là để kế thừa từ một lớp khác, thực sự phải có một mối quan hệ "là một". Vì vậy, nếu bạn viết một lớp gọi là BMW, nó có thể kế thừa từ Xe hơi vì BMW thực sự là một chiếc xe hơi. Lớp Ngựa có thể kế thừa từ lớp Động vật có vú vì ngựa thực sự là động vật có vú trong đời thực và bất kỳ chức năng nào của Động vật có vú đều phải liên quan đến Ngựa. Nhưng bạn có thể nói rằng một đội là một danh sách? Từ những gì tôi có thể nói, có vẻ như một Đội không thực sự là "một" Danh sách. Vì vậy, trong trường hợp này, tôi sẽ có một Danh sách như một biến thành viên.

8
Nicolas Dorier 2014-02-13 03:57.

Bí mật bẩn thỉu của tôi: Tôi không quan tâm người ta nói gì, và tôi làm điều đó. .NET Framework được lan truyền với "XxxxCollection" (UIElementCollection cho ví dụ trên đầu của tôi).

Vì vậy, điều gì ngăn tôi nói:

team.Players.ByName("Nicolas")

Khi tôi thấy nó tốt hơn

team.ByName("Nicolas")

Hơn nữa, PlayerCollection của tôi có thể được sử dụng bởi lớp khác, chẳng hạn như "Club" mà không có bất kỳ sự trùng lặp mã nào.

club.Players.ByName("Nicolas")

Những phương pháp hay nhất của ngày hôm qua, có thể không phải là của ngày mai. Không có lý do gì đằng sau hầu hết các phương pháp hay nhất, hầu hết chỉ được sự đồng tình rộng rãi giữa cộng đồng. Thay vì hỏi cộng đồng nếu họ sẽ đổ lỗi cho bạn khi bạn làm điều đó, hãy tự hỏi mình, điều gì dễ đọc hơn và có thể bảo trì được?

team.Players.ByName("Nicolas") 

hoặc là

team.ByName("Nicolas")

Có thật không. Bạn có nghi ngờ gì không? Bây giờ có thể bạn cần phải xử lý các ràng buộc kỹ thuật khác ngăn bạn sử dụng Danh sách <T> trong trường hợp sử dụng thực của mình. Nhưng đừng thêm một ràng buộc không nên tồn tại. Nếu Microsoft không ghi lại lý do tại sao, thì đó chắc chắn là một "phương pháp hay nhất" đến từ hư không.

8
QuentinUK 2014-02-18 16:53.

Những gì các nguyên tắc nói là API công khai không được tiết lộ quyết định thiết kế nội bộ về việc bạn đang sử dụng danh sách, tập hợp, từ điển, cây hay bất cứ thứ gì. Một "đội" không nhất thiết phải là một danh sách. Bạn có thể triển khai nó dưới dạng danh sách nhưng người dùng API công khai của bạn nên sử dụng lớp của bạn trên cơ sở cần biết. Điều này cho phép bạn thay đổi quyết định của mình và sử dụng cấu trúc dữ liệu khác mà không ảnh hưởng đến giao diện công khai.

7
Shital Shah 2014-02-16 21:50.

Khi họ nói List<T>là "tối ưu hóa", tôi nghĩ họ muốn nói rằng nó không có các tính năng như các phương pháp ảo và đắt hơn một chút. Vì vậy, vấn đề là khi bạn hiển thị List<T>trong API công khai của mình , bạn sẽ mất khả năng thực thi các quy tắc kinh doanh hoặc tùy chỉnh chức năng của nó sau này. Nhưng nếu bạn đang sử dụng lớp kế thừa này làm nội bộ trong dự án của mình (thay vì có khả năng tiếp xúc với hàng nghìn khách hàng / đối tác / nhóm khác của bạn dưới dạng API) thì có thể ổn nếu nó tiết kiệm thời gian của bạn và đó là chức năng bạn muốn bản sao. Lợi thế của việc kế thừa từ List<T>là bạn loại bỏ rất nhiều mã trình bao bọc ngu ngốc mà sẽ không bao giờ được tùy chỉnh trong tương lai gần. Ngoài ra, nếu bạn muốn lớp của mình có ngữ nghĩa chính xác rõ ràng giống như List<T>vòng đời của các API của bạn thì cũng có thể được.

Tôi thường thấy nhiều người làm thêm hàng đống công việc chỉ vì quy tắc FxCop nói như vậy hoặc blog của ai đó nói rằng đó là một cách làm "tồi tệ". Nhiều lần, điều này biến mã thành sự kỳ lạ của mẫu thiết kế. Như với rất nhiều hướng dẫn, hãy coi nó như hướng dẫn có thể có ngoại lệ.

4
Null511 2018-01-20 16:43.

Mặc dù tôi không có một so sánh phức tạp như hầu hết các câu trả lời này, tôi muốn chia sẻ phương pháp của tôi để xử lý tình huống này. Bằng cách mở rộng IEnumerable<T>, bạn có thể cho phép Teamlớp của mình hỗ trợ các tiện ích mở rộng truy vấn Linq mà không cần tiết lộ công khai tất cả các phương thức và thuộc tính của 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
{
    ...
}
3
Ivan Nikitin 2014-02-16 21:59.

Nếu người dùng lớp của bạn cần tất cả các phương thức và thuộc tính mà ** Danh sách có, bạn nên lấy lớp của mình từ nó. Nếu họ không cần chúng, hãy bao gồm Danh sách và tạo trình bao bọc cho các phương thức mà người dùng lớp của bạn thực sự cần.

Đây là một quy tắc nghiêm ngặt, nếu bạn viết một API công khai hoặc bất kỳ mã nào khác sẽ được nhiều người sử dụng. Bạn có thể bỏ qua quy tắc này nếu bạn có một ứng dụng nhỏ và không quá 2 nhà phát triển. Điều này sẽ giúp bạn tiết kiệm thời gian.

Đối với các ứng dụng nhỏ, bạn cũng có thể cân nhắc chọn một ngôn ngữ khác ít nghiêm ngặt hơn. Ruby, JavaScript - bất cứ thứ gì cho phép bạn viết ít mã hơn.

3
Alexey 2015-04-28 01:35.

Tôi chỉ muốn nói thêm rằng Bertrand Meyer, người phát minh ra Eiffel và thiết kế theo hợp đồng, sẽ Teamthừa hưởng từ đó List<Player>mà không cần phải cắt mí mắt.

Trong cuốn sách của mình, Xây dựng phần mềm hướng đối tượng , ông thảo luận về việc triển khai hệ thống GUI trong đó các cửa sổ hình chữ nhật có thể có các cửa sổ con. Anh ta chỉ đơn giản là có Windowkế thừa từ cả hai RectangleTree<Window>để sử dụng lại việc thực hiện.

Tuy nhiên, C # không phải là Eiffel. Sau này hỗ trợ đa kế thừa và đổi tên các tính năng . Trong C #, khi bạn phân lớp, bạn thừa hưởng cả giao diện và sự áp đặt. Bạn có thể ghi đè việc triển khai, nhưng các quy ước gọi được sao chép trực tiếp từ lớp cha. Tuy nhiên, trong Eiffel, bạn có thể sửa đổi tên của các phương thức công khai, vì vậy bạn có thể đổi tên AddRemovethành HireFiretrong của bạn Team. Nếu một phiên bản của Teamđược upcast trở lại List<Player>, người gọi sẽ sử dụng AddRemovesửa đổi nó, nhưng các phương thức ảo của bạn HireFiresẽ được gọi.

3
marsh-wiggle 2019-07-21 04:55.

Sự cố với tuần tự hóa

Một khía cạnh còn thiếu. Các lớp kế thừa từ Danh sách <> không thể được tuần tự hóa chính xác bằng XmlSerializer . Trong trường hợp đó, DataContractSerializer phải được sử dụng thay thế hoặc cần thực hiện tuần tự hóa riêng.

public class DemoList : List<Demo>
{
    // using XmlSerializer this properties won't be seralized:
    string AnyPropertyInDerivedFromList { get; set; }     
}

public class Demo
{
    // this properties will be seralized
    string AnyPropetyInDemo { get; set; }  
}

Đọc thêm: Khi một lớp được kế thừa từ Danh sách <>, XmlSerializer không tuần tự hóa các thuộc tính khác

2
Eniola 2016-07-10 07:14.

Tôi nghĩ rằng tôi không đồng ý với khái quát của bạn. Một đội không chỉ là một tập hợp các cầu thủ. Một đội có rất nhiều thông tin về nó - tên, biểu tượng, tập hợp nhân viên quản lý / quản trị, tập hợp đội ngũ huấn luyện, sau đó là tập hợp các cầu thủ. Vì vậy, đúng cách, lớp FootballTeam của bạn nên có 3 bộ sưu tập và bản thân nó không phải là một bộ sưu tập; nếu nó là để mô hình đúng thế giới thực.

Bạn có thể xem xét một lớp PlayerCollection giống như Lớp StringCollection chuyên cung cấp một số tiện ích khác - như xác thực và kiểm tra trước khi các đối tượng được thêm vào hoặc xóa khỏi cửa hàng nội bộ.

Có lẽ, khái niệm về người đặt cược PlayerCollection phù hợp với cách tiếp cận ưa thích của bạn?

public class PlayerCollection : Collection<Player> 
{ 
}

Và FootballTeam có thể trông như thế này:

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();
}
2
cdiggins 2018-10-07 19:01.

Ưu tiên giao diện hơn lớp

Các lớp nên tránh xuất phát từ các lớp và thay vào đó triển khai các giao diện tối thiểu cần thiết.

Kế thừa phá vỡ tính đóng gói

Bắt nguồn từ các lớp phá vỡ đóng gói :

  • tiết lộ chi tiết nội bộ về cách bộ sưu tập của bạn được triển khai
  • khai báo một giao diện (tập hợp các chức năng và thuộc tính công khai) có thể không phù hợp

Trong số những thứ khác, điều này làm cho việc cấu trúc lại mã của bạn trở nên khó khăn hơn.

Các lớp học là một chi tiết triển khai

Các lớp là một chi tiết triển khai nên được ẩn khỏi các phần khác của mã của bạn. Tóm lại, a System.Listlà một triển khai cụ thể của một kiểu dữ liệu trừu tượng, có thể phù hợp hoặc không phù hợp hiện tại và tương lai.

Về mặt khái niệm, thực tế rằng System.Listkiểu dữ liệu được gọi là "danh sách" là một chút của một con cá trích đỏ. A System.List<T>là một tập hợp có thứ tự có thể thay đổi hỗ trợ các phép toán O (1) được khấu hao để thêm, chèn và loại bỏ các phần tử và các phép toán O (1) để truy xuất số lượng phần tử hoặc lấy và thiết lập phần tử theo chỉ mục.

Giao diện càng nhỏ thì mã càng linh hoạt

Khi thiết kế cấu trúc dữ liệu, giao diện càng đơn giản thì mã càng linh hoạt. Chỉ cần nhìn vào sức mạnh của LINQ để minh chứng cho điều này.

Cách chọn giao diện

Khi bạn nghĩ "danh sách", bạn nên bắt đầu bằng cách tự nói với chính mình, "Tôi cần đại diện cho một bộ sưu tập các cầu thủ bóng chày". Vì vậy, giả sử bạn quyết định lập mô hình này với một lớp học. Những gì bạn nên làm đầu tiên là quyết định số lượng giao diện tối thiểu mà lớp này sẽ cần để hiển thị.

Một số câu hỏi có thể giúp hướng dẫn quá trình này:

  • Tôi có cần phải đếm không? Nếu không cân nhắc thực hiệnIEnumerable<T>
  • Bộ sưu tập này có thay đổi sau khi nó được khởi chạy không? Nếu không cân nhắc IReadonlyList<T>.
  • Điều quan trọng là tôi có thể truy cập các mục theo chỉ mục không? Xem xétICollection<T>
  • Thứ tự mà tôi thêm các mục vào bộ sưu tập có quan trọng không? Có lẽ nó là một ISet<T>?
  • Nếu bạn thực sự muốn những điều này thì hãy tiếp tục và thực hiện IList<T>.

Bằng cách này, bạn sẽ không kết hợp các phần khác của mã với chi tiết triển khai của bộ sưu tập cầu thủ bóng chày của mình và sẽ được tự do thay đổi cách triển khai nó miễn là bạn tôn trọng giao diện.

Bằng cách tiếp cận này, bạn sẽ thấy rằng mã trở nên dễ đọc, cấu trúc lại và sử dụng lại dễ dàng hơn.

Những lưu ý về việc tránh tấm bảng điện tử

Việc triển khai các giao diện trong một IDE hiện đại phải dễ dàng. Nhấp chuột phải và chọn "Triển khai Giao diện". Sau đó, chuyển tiếp tất cả các triển khai tới một lớp thành viên nếu bạn cần.

Điều đó nói rằng, nếu bạn thấy mình đang viết rất nhiều bảng soạn sẵn, thì có thể là do bạn đang thể hiện nhiều chức năng hơn mức bình thường. Đó cũng là lý do bạn không nên kế thừa từ một lớp.

Bạn cũng có thể thiết kế các giao diện nhỏ hơn phù hợp với ứng dụng của mình và có thể chỉ là một vài chức năng mở rộng trợ giúp để ánh xạ các giao diện đó với bất kỳ giao diện nào khác mà bạn cần. Đây là cách tiếp cận mà tôi đã thực hiện trong IArraygiao diện của riêng mình cho thư viện LinqArray .

0
phoog 2020-05-22 09:48.

Khi nào thì chấp nhận được?

Để trích dẫn Eric Lippert:

Khi bạn đang xây dựng một cơ chế mở rộng List<T>cơ chế.

Ví dụ, bạn cảm thấy mệt mỏi vì không có AddRangephương thức trong IList<T>:

public interface IMoreConvenientListInterface<T> : IList<T>
{
    void AddRange(IEnumerable<T> collection);
}

public class MoreConvenientList<T> : List<T>, IMoreConvenientListInterface<T> { }

Related questions

MORE COOL STUFF

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett đã bất chấp những lời khuyên hẹn hò điển hình khi cô gặp chồng mình.

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Michael Sheen là một diễn viên phi lợi nhuận nhưng chính xác thì điều đó có nghĩa là gì?

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Ngôi sao của Hallmark Colin Egglesfield chia sẻ về những cuộc gặp gỡ với người hâm mộ ly kỳ tại RomaDrama Live! cộng với chương trình INSPIRE của anh ấy tại đại hội.

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Bạn sẽ phải phủi sạch đầu đĩa Blu-ray hoặc DVD để xem tại sao Northern Exposure trở thành một trong những chương trình nổi tiếng nhất của thập niên 90.

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

8 công dụng tuyệt vời của Baking Soda và Giấm

8 công dụng tuyệt vời của Baking Soda và Giấm

Bạn biết đấy, hai sản phẩm này là nguồn điện để làm sạch, riêng chúng. Nhưng cùng với nhau, chúng có một loạt công dụng hoàn toàn khác.

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Thủy điện rất cần thiết cho lưới điện của Hoa Kỳ, nhưng nó chỉ tạo ra năng lượng khi có nước di chuyển. Bao nhiêu nhà máy thủy điện có thể gặp nguy hiểm khi các hồ và sông cạn kiệt?

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Tóc tỉa từ các tiệm và các khoản quyên góp cá nhân có thể được tái sử dụng như những tấm thảm thấm dầu và giúp bảo vệ môi trường.

Một thương hiệu kế thừa khác có được cuộc sống mới khi những người chủ mới của Black Opal có được Hội chợ thời trang

Một thương hiệu kế thừa khác có được cuộc sống mới khi những người chủ mới của Black Opal có được Hội chợ thời trang

Desiree Rogers, trái, và Cheryl Mayberry McKissack Chuyển sang, Fenty Beauty và Pat McGrath, có một đế chế làm đẹp mới do phụ nữ làm chủ đang trỗi dậy. Sau thương vụ mua lại mỹ phẩm Black Opal vào tháng 9, đội ngũ quyền lực của Giám đốc điều hành công ty Desiree Rogers và Chủ tịch Cheryl Mayberry McKissack đã thông báo mua lại thương hiệu làm đẹp tiên phong Fashion Fair từ người sáng lập Johnson Publishing Company (JPC).

Ngoài việc trở nên vô nghĩa, các bên bộc lộ giới tính giờ đây đã trở nên chết người

Ngoài việc trở nên vô nghĩa, các bên bộc lộ giới tính giờ đây đã trở nên chết người

Đối với những người có quá nhiều thời gian, tiệc tiết lộ giới tính là một cách thú vị để không cần áp đặt những định kiến ​​hạn chế lên thai nhi trước một đối tượng bạn bè giả vờ quan tâm một cách lịch sự. Tuy nhiên, việc chỉ dựa vào những chiếc bánh nướng có màu nhân tạo để biểu thị một tiếng hoo-ha hoặc một đứa trẻ đi tè đã trở nên xa xỉ trong các bậc cha mẹ trên Instagram.

Kirstjen Nielsen đang cố gắng đổi mới bản thân với tư cách là một người phụ nữ 'nói ra sự thật cho sức mạnh'

Kirstjen Nielsen đang cố gắng đổi mới bản thân với tư cách là một người phụ nữ 'nói ra sự thật cho sức mạnh'

Hôm thứ Tư, cựu Bộ trưởng An ninh Nội địa Kirstjen Nielsen vì một lý do nào đó đã được đưa ra một diễn đàn để phát biểu tại Hội nghị thượng đỉnh Những người phụ nữ quyền lực nhất của Fortune. Và có thể đoán trước được, người phụ nữ giám sát các nỗ lực (đang diễn ra) của chính quyền Trump nhằm chia cắt các gia đình ở biên giới đã sử dụng thời gian của mình như một cơ hội để tự bảo vệ mình và tự nhận mình là người — và hãy hít thở sâu ở đây — “đã nói sự thật với quyền lực.

Toyota Racing Development sẽ cho ra mắt Supra 3000GT Concept, Massive Wing vào tháng tới

Toyota Racing Development sẽ cho ra mắt Supra 3000GT Concept, Massive Wing vào tháng tới

Vì thế hệ mới nhất của chiếc xe điều chỉnh được yêu thích trên thế giới hiện đã có mặt tại đây, nên chỉ có điều kiện là Toyota Racing Development sẽ đưa phiên bản Supra thế hệ thứ năm sửa đổi của riêng mình đến SEMA vào tháng tới. Toyota đã cho chúng tôi một gợi ý về những gì sẽ xảy ra trong tuần này: một chiếc Concept GR Supra 3000GT hiện đại.

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Rothschild's luggage got lost, but luckily she has an incredible closet to shop: Sister Paris Hilton's!

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa. Từ Hollywood đến New York và mọi nơi ở giữa, hãy xem các ngôi sao yêu thích của bạn đang làm gì!

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

Các nhà điều tra đang xem xét liệu nhóm và nghi phạm có biết nhau trước vụ tấn công hay không

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Vụ kiện, nêu tên một số học khu, lập luận rằng dự luật "Không nói đồng tính" được ban hành gần đây của Florida "có hiệu quả im lặng và xóa bỏ học sinh và gia đình LGBTQ +"

Đường băng hạ cánh

Đường băng hạ cánh

Cuối hè đầu thu là mùa hoài niệm. Những chiếc đèn đường chiếu ánh sáng của chúng qua những con đường đẫm mưa, và những chiếc lá dưới chân - màu đỏ cam tắt trong bóng chạng vạng - là lời nhắc nhở về những ngày đã qua.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Vào năm 2021, tôi khuyến khích bạn suy nghĩ lại mọi thứ bạn biết về khách hàng mà bạn phục vụ và những câu chuyện bạn kể cho họ. Lùi lại.

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Vào ngày sinh nhật thứ 9 của Felix The Cat, tôi nhớ về một trong những mất mát lớn nhất trong cuộc đời trưởng thành của tôi - Sophie của tôi vào năm 2013. Tôi đã viết bài luận này và chia sẻ nó trên nền tảng này một thời gian ngắn vào năm 2013.

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Tôi ghét từ "tàu đắm". Mọi người cảm thấy thoải mái trong la bàn đạo đức của riêng mình, và khi làm như vậy, họ thấy mình vượt qua sự phán xét.

Language