Có một mô hình nào cho cách thêm các mục tự nhiên vào các bộ sưu tập khác không? [đóng cửa]


13

Tôi nghĩ rằng cách phổ biến nhất để thêm một cái gì đó vào bộ sưu tập là sử dụng một số Addphương thức mà bộ sưu tập cung cấp:

class Item {}    
var items = new List<Item>();
items.Add(new Item());

và thực sự không có gì bất thường về điều đó.

Tuy nhiên, tôi tự hỏi tại sao chúng ta không làm theo cách này:

var item = new Item();
item.AddTo(items);

phương pháp đầu tiên có vẻ tự nhiên hơn . Điều này sẽ có và bổ trợ rằng khi Itemlớp có một thuộc tính như Parent:

class Item 
{
    public object Parent { get; private set; }
} 

bạn có thể đặt setter riêng tư. Trong trường hợp này tất nhiên bạn không thể sử dụng một phương pháp mở rộng.

Nhưng có lẽ tôi đã sai và tôi chưa bao giờ thấy mẫu này trước đây vì nó không phổ biến? Bạn có biết liệu có bất kỳ mô hình như vậy?

Trong C#một phương thức mở rộng sẽ hữu ích cho điều đó

public static T AddTo(this T item, IList<T> list)
{
    list.Add(item);
    return item;
}

Còn các ngôn ngữ khác thì sao? Tôi đoán trong hầu hết chúng, Itemlớp phải cung cấp một ICollectionItemgiao diện.


Cập nhật-1

Tôi đã suy nghĩ về nó nhiều hơn một chút và mẫu này sẽ thực sự hữu ích, ví dụ nếu bạn không muốn một mục được thêm vào nhiều bộ sưu tập.

ICollectablegiao diện kiểm tra :

interface ICollectable<T>
{
    // Gets a value indicating whether the item can be in multiple collections.
    bool CanBeInMultipleCollections { get; }

    // Gets a list of item's owners.
    List<ICollection<T>> Owners { get; }

    // Adds the item to a collection.
    ICollectable<T> AddTo(ICollection<T> collection);

    // Removes the item from a collection.
    ICollectable<T> RemoveFrom(ICollection<T> collection);

    // Checks if the item is in a collection.
    bool IsIn(ICollection<T> collection);
}

và triển khai mẫu:

class NodeList : List<NodeList>, ICollectable<NodeList>
{
    #region ICollectable implementation.

    List<ICollection<NodeList>> owners = new List<ICollection<NodeList>>();

    public bool CanBeInMultipleCollections
    {
        get { return false; }
    }

    public ICollectable<NodeList> AddTo(ICollection<NodeList> collection)
    {
        if (IsIn(collection))
        {
            throw new InvalidOperationException("Item already added.");
        }

        if (!CanBeInMultipleCollections)
        {
            bool isInAnotherCollection = owners.Count > 0;
            if (isInAnotherCollection)
            {
                throw new InvalidOperationException("Item is already in another collection.");
            }
        }
        collection.Add(this);
        owners.Add(collection);
        return this;
    }

    public ICollectable<NodeList> RemoveFrom(ICollection<NodeList> collection)
    {
        owners.Remove(collection);
        collection.Remove(this);
        return this;
    }

    public List<ICollection<NodeList>> Owners
    {
        get { return owners; }
    }

    public bool IsIn(ICollection<NodeList> collection)
    {
        return collection.Contains(this);
    }

    #endregion
}

sử dụng:

var rootNodeList1 = new NodeList();
var rootNodeList2 = new NodeList();

var subNodeList4 = new NodeList().AddTo(rootNodeList1);

// Let's move it to the other root node:
subNodeList4.RemoveFrom(rootNodeList1).AddTo(rootNodeList2);

// Let's try to add it to the first root node again... 
// and it will throw an exception because it can be in only one collection at the same time.
subNodeList4.AddTo(rootNodeList1);

13
item.AddTo(items)giả sử bạn có ngôn ngữ không có phương thức mở rộng: tự nhiên hoặc không, để hỗ trợ addTo mọi loại sẽ cần phương thức này và cung cấp ngôn ngữ đó cho mọi loại bộ sưu tập hỗ trợ nối thêm. Đó giống như ví dụ tốt nhất về việc giới thiệu sự phụ thuộc giữa mọi thứ tôi từng nghe: P - Tôi nghĩ tiền đề sai ở đây là cố gắng mô hình hóa một số trừu tượng lập trình với cuộc sống 'thực'. Điều đó thường đi sai.
Stijn

1
@ t3chb0t Hehe, điểm tốt. Tôi đã tự hỏi liệu ngôn ngữ nói đầu tiên của bạn có ảnh hưởng đến những gì xây dựng có vẻ tự nhiên hơn. Đối với tôi, người duy nhất có vẻ tự nhiên hoàn toàn sẽ là add(item, collection), nhưng đó không phải là phong cách OO tốt.
Kilian Foth

3
@ t3chb0t Trong ngôn ngữ nói, bạn có thể đọc mọi thứ bình thường hoặc theo phản xạ. "John, xin vui lòng thêm quả táo này vào những gì bạn đang mang." vs "Quả táo nên được thêm vào những gì bạn đang mang, John." Trong thế giới của OOP, phản xạ không có nhiều ý nghĩa. Bạn không yêu cầu một cái gì đó bị ảnh hưởng bởi một đối tượng khác nói chung. Gần nhất với điều này trong OOP là mẫu khách truy cập . Có một lý do mà điều này không phải là tiêu chuẩn mặc dù.
Neil

3
Đây là đặt một cái cung đẹp xung quanh một ý tưởng tồi .
Telastyn

3
@ t3chb0t Bạn có thể tìm thấy Vương quốc Danh từ một bài đọc thú vị.
paul

Câu trả lời:


51

Không, item.AddTo(items)nó không tự nhiên hơn. Tôi nghĩ bạn trộn cái này với những thứ sau:

t3chb0t.Add(item).To(items)

Bạn nói đúng items.Add(item)là không gần với ngôn ngữ tiếng Anh tự nhiên. Nhưng bạn cũng không nghe item.AddTo(items)bằng ngôn ngữ tiếng Anh tự nhiên, phải không? Thông thường có một người có nghĩa vụ phải thêm các mục vào danh sách. Có thể là khi làm việc tại một siêu thị hoặc trong khi nấu ăn và thêm các thành phần.

Trong trường hợp ngôn ngữ lập trình, chúng tôi đã lập danh sách để cả hai thực hiện: lưu trữ các mục của nó chịu trách nhiệm thêm chúng vào chính nó.

Vấn đề với cách tiếp cận của bạn là một mục phải được biết rằng một danh sách tồn tại. Nhưng một mục có thể tồn tại ngay cả khi không có danh sách nào cả, phải không? Điều này cho thấy rằng nó không nên nhận thức được danh sách. Danh sách không nên xảy ra trong mã của nó theo bất kỳ cách nào cả.

Danh sách tuy nhiên không tồn tại mà không có vật phẩm (ít nhất là chúng sẽ vô dụng). Do đó, sẽ ổn nếu họ biết về các mặt hàng của mình - ít nhất là ở dạng chung.


4

@valenterry hoàn toàn đúng về vấn đề tách mối quan tâm. Các lớp không nên biết gì về các bộ sưu tập khác nhau có thể chứa chúng. Bạn sẽ không muốn phải thay đổi lớp vật phẩm mỗi khi bạn tạo một loại bộ sưu tập mới.

Mà nói...

1. Không phải Java

Java không giúp ích gì cho bạn ở đây, nhưng một số ngôn ngữ có cú pháp mở rộng linh hoạt hơn.

Trong Scala, items.add (item) trông như thế này:

  item :: items

Trong đó :: là một toán tử thêm các mục vào danh sách. Điều đó dường như rất nhiều những gì bạn muốn. Nó thực sự là cú pháp đường cho

  items.::(item)

trong đó :: là một phương thức danh sách Scala tương đương hiệu quả với list.add của Java . Vì vậy, trong khi nó trông trong phiên bản đầu tiên như thể vật phẩm đang tự thêm vào vật phẩm , thì trong các vật phẩm thật đang thực hiện việc thêm. Cả hai phiên bản đều hợp lệ Scala

Thủ thuật này được thực hiện theo hai bước:

  1. Trong Scala, các toán tử thực sự chỉ là phương thức. Nếu bạn nhập danh sách Java vào Scala, thì items.add (item) cũng có thể được ghi các mục thêm mục . Trong Scala, 1 + 2 thực sự là 1. + (2) . Trong phiên bản có khoảng trắng thay vì dấu chấm và dấu ngoặc, Scala nhìn thấy đối tượng đầu tiên, tìm kiếm một phương thức khớp với từ tiếp theo và (nếu phương thức lấy tham số) chuyển điều tiếp theo (hoặc điều) sang phương thức đó.
  2. Trong Scala, các toán tử kết thúc bằng dấu hai chấm là liên kết phải chứ không phải trái. Vì vậy, khi Scala nhìn thấy phương thức, nó sẽ tìm chủ sở hữu phương thức ở bên phải và cung cấp giá trị ở bên trái.

Nếu bạn không thoải mái với nhiều toán tử và muốn addTo của mình , Scala cung cấp tùy chọn khác: chuyển đổi ngầm định. Điều này đòi hỏi hai bước

  1. Tạo một lớp bao bọc được gọi là ListItem , lấy một mục trong hàm tạo của nó và có một phương thức addTo có thể thêm mục đó vào một danh sách đã cho.
  2. Tạo một hàm lấy một mục làm tham số và trả về ListItem chứa mục đó . Đánh dấu nó là ẩn .

Nếu chuyển đổi ngầm được bật và Scala thấy

  item addTo items

hoặc là

  item.addTo(items)

và mục không có addTo , nó sẽ tìm kiếm phạm vi hiện tại cho bất kỳ chức năng chuyển đổi ngầm định nào. Nếu bất kỳ chức năng chuyển đổi trả về một kiểu mà không có một addTo phương pháp, điều này xảy ra:

  ListItem.(item).addTo(items)

Bây giờ, bạn có thể tìm thấy lộ trình chuyển đổi ngầm theo sở thích của mình, nhưng nó gây thêm nguy hiểm, bởi vì mã có thể làm những điều không mong muốn nếu bạn không nhận thức rõ ràng về tất cả các chuyển đổi ngầm định trong ngữ cảnh cụ thể. Đây là lý do tại sao tính năng này không còn được bật theo mặc định trong Scala. (Tuy nhiên, trong khi bạn có thể chọn bật hay không kích hoạt mã trong chính mã của mình, bạn không thể làm gì về trạng thái của nó trong thư viện của người khác. Ở đây là rồng).

Các nhà khai thác chấm dứt đại tràng là xấu hơn nhưng không gây ra mối đe dọa.

Cả hai phương pháp đều bảo tồn nguyên tắc tách mối quan tâm. Bạn có thể làm cho nó trông như thể vật phẩm biết về bộ sưu tập, nhưng công việc thực sự được thực hiện ở một nơi khác. Bất kỳ ngôn ngữ nào khác muốn cung cấp cho bạn tính năng này ít nhất phải cẩn thận.

2. Java

Trong Java, nơi bạn không thể mở rộng cú pháp, cách tốt nhất bạn có thể làm là tạo một loại lớp trình bao bọc / trang trí nào biết về danh sách. Trong miền nơi các mục của bạn được đặt trong danh sách, bạn có thể gói chúng trong đó. Khi họ rời khỏi miền đó, đưa họ ra ngoài. Có lẽ mô hình khách truy cập là gần nhất.

3. tl; dr

Scala, giống như một số ngôn ngữ khác, có thể giúp bạn có cú pháp tự nhiên hơn. Java chỉ có thể cung cấp cho bạn các mẫu baroque xấu xí.


2

Với phương thức tiện ích mở rộng của bạn, bạn vẫn phải gọi Add () để nó không thêm bất cứ điều gì hữu ích vào mã của bạn. Trừ khi phương thức addto của bạn thực hiện một số xử lý khác, không có lý do nào để nó tồn tại. Và viết mã không có mục đích chức năng là xấu cho khả năng đọc và bảo trì.

Nếu bạn để nó không chung chung, các vấn đề sẽ trở nên rõ ràng hơn. Bây giờ bạn có một mục cần biết về các loại bộ sưu tập khác nhau mà nó có thể có. Điều đó vi phạm nguyên tắc trách nhiệm duy nhất. Không chỉ là một vật phẩm giữ dữ liệu của nó, nó còn thao túng các bộ sưu tập. Đó là lý do tại sao chúng tôi để lại trách nhiệm thêm các mục vào các bộ sưu tập cho đến đoạn mã đang tiêu thụ cả hai.


Nếu một khung công nhận các loại tham chiếu đối tượng khác nhau (ví dụ: phân biệt giữa các tham chiếu đóng gói quyền sở hữu và không tham gia), thì có nghĩa là có một phương tiện để tham chiếu đóng quyền sở hữu có thể từ bỏ quyền sở hữu đối với một bộ sưu tập có khả năng tiếp nhận nó. Nếu mỗi điều được giới hạn trong việc có một chủ sở hữu, việc chuyển quyền sở hữu thông qua thing.giveTo(collection)[và yêu cầu collectionthực hiện giao diện "acceptOwnership"] sẽ có ý nghĩa hơn là collectionbao gồm một phương thức chiếm quyền sở hữu. Dĩ nhiên ...
supercat

... hầu hết mọi thứ trong Java không có khái niệm về quyền sở hữu và một tham chiếu đối tượng có thể được lưu trữ vào một bộ sưu tập mà không liên quan đến đối tượng được xác định qua đó.
supercat

1

Tôi nghĩ bạn đang bị ám ảnh bởi từ "thêm". Thay vào đó, hãy thử tìm một từ thay thế, chẳng hạn như "thu thập", từ này sẽ cung cấp cho bạn một cú pháp thân thiện hơn với tiếng Anh mà không thay đổi về mẫu:

items.collect(item);

Phương pháp trên hoàn toàn giống với items.add(item);, với sự khác biệt duy nhất là lựa chọn từ khác nhau, và chảy rất độc đáo và "tự nhiên" trong tiếng Anh.

Đôi khi, chỉ đơn thuần đặt lại tên biến, phương thức, lớp, v.v. sẽ ngăn chặn việc đánh thuế lại thiết kế của mẫu.


collectđôi khi được sử dụng làm tên cho các phương thức làm giảm một bộ sưu tập các mục thành một loại kết quả số ít (cũng có thể là một bộ sưu tập). Quy ước này đã được Java Steam API thông qua , khiến cho việc sử dụng tên trong bối cảnh này trở nên vô cùng phổ biến. Tôi chưa bao giờ thấy từ được sử dụng trong ngữ cảnh mà bạn đề cập trong câu trả lời của bạn. Tôi thà gắn bó với addhoặcput
toniedzwiedz

@toniedzwiedz, nên xem xét pushhay enqueue. Vấn đề không phải là tên ví dụ cụ thể, nhưng thực tế là một tên khác có thể gần với mong muốn của OP hơn về ngữ pháp tiếng Anh.
Brian S

1

Mặc dù không có mô hình như vậy cho trường hợp chung (như được giải thích bởi những người khác), bối cảnh trong đó một mô hình tương tự có giá trị của nó là một hiệp hội một-nhiều-hai trong ORM.

Trong bối cảnh này, bạn có hai thực thể, hãy nói ParentChild. Một cha mẹ có thể có nhiều con, nhưng mỗi đứa trẻ thuộc về một cha mẹ. Nếu ứng dụng yêu cầu liên kết có thể điều hướng từ cả hai đầu (hai chiều), bạn sẽ có một List<Child> childrenlớp trong lớp cha và một Parent parentlớp con.

Các hiệp hội nên luôn luôn có đi có lại. Các giải pháp ORM thường thực thi điều này trên các thực thể mà chúng tải. Nhưng khi ứng dụng đang thao túng một thực thể (tồn tại hoặc mới), nó phải thực thi các quy tắc tương tự. Theo kinh nghiệm của tôi, bạn thường tìm thấy một Parent.addChild(child)phương thức gọi Child.setParent(parent), phương thức này không dành cho công chúng. Sau này, bạn thường tìm thấy các kiểm tra giống như bạn đã thực hiện trong ví dụ của mình. Các tuyến khác tuy nhiên cũng có thể được thực hiện: Child.addTo(parent)mà gọi Parent.addChild(child). Tuyến đường nào là tốt nhất khác nhau từ tình huống để tình huống.


1

Trong Java, một bộ sưu tập điển hình không chứa các thứ - nó chứa các tham chiếu đến các thứ hoặc các bản sao chính xác hơn của các tham chiếu đến các thứ. Ngược lại, cú pháp thành viên chấm thường được sử dụng để hành động theo sự việc được xác định bởi một tham chiếu, thay vì dựa trên chính tham chiếu đó.

Trong khi đặt một quả táo vào một cái bát sẽ làm thay đổi một số khía cạnh của quả táo (ví dụ như vị trí của nó), đặt một tờ giấy có ghi "đối tượng # 29521" vào một cái bát sẽ không làm thay đổi táo theo bất kỳ cách nào ngay cả khi nó xảy ra đối tượng # 29521. Hơn nữa, vì các bộ sưu tập chứa các bản sao của tài liệu tham khảo, không có Java tương đương với việc đặt một tờ giấy ghi "đối tượng # 29521" vào một cái bát. Thay vào đó, người ta sao chép số # 29521 sang một tờ giấy mới và hiển thị (không đưa) cho cái bát, họ sẽ tự tạo bản sao của mình, để lại bản gốc không bị ảnh hưởng.

Thật vậy, vì các tham chiếu đối tượng ("các mẩu giấy") trong Java có thể được quan sát một cách thụ động, cả các tham chiếu cũng như các đối tượng được xác định qua đó, không có cách nào để biết những gì đang được thực hiện với chúng. Có một số loại bộ sưu tập, thay vì chỉ chứa một loạt các tham chiếu đến các đối tượng có thể không biết gì về sự tồn tại của bộ sưu tập, thay vào đó thiết lập mối quan hệ hai chiều với các đối tượng được gói gọn trong đó. Với một số bộ sưu tập như vậy, có thể có ý nghĩa khi yêu cầu một phương thức tự thêm vào bộ sưu tập (đặc biệt nếu một đối tượng chỉ có khả năng nằm trong một bộ sưu tập như vậy tại một thời điểm). Tuy nhiên, nói chung, hầu hết các bộ sưu tập không phù hợp với mẫu đó và có thể chấp nhận các tham chiếu đến các đối tượng mà không có bất kỳ sự liên quan nào đến một phần của các đối tượng được xác định bởi các tham chiếu đó.


bài này khá khó đọc (tường văn bản). Bạn có phiền chỉnh sửa ing nó thành một hình dạng tốt hơn?
gnat

@gnat: Có tốt hơn không?
17/1/2015

1
@gnat: Xin lỗi - một trục trặc mạng khiến nỗ lực đăng bài chỉnh sửa của tôi không thành công. Bạn có thích nó tốt hơn bây giờ?
supercat

-2

item.AddTo(items)không được sử dụng vì nó thụ động. Cf "Mục này được thêm vào danh sách" và "căn phòng được sơn bởi người trang trí".

Các công trình thụ động là tốt, nhưng, ít nhất là bằng tiếng Anh, chúng ta có xu hướng thích các công trình hoạt động.

Trong lập trình OO, pardigm mang lại sự nổi bật cho các diễn viên, chứ không phải những người hành động, vì vậy chúng tôi có items.Add(item). Danh sách này là thứ đang làm một cái gì đó. Đó là diễn viên, vì vậy đây là cách tự nhiên hơn.


Nó phụ thuộc vào nơi bạn đang đứng ;-) - lý thuyết tương đối - bạn có thể nói tương tự về list.Add(item) một mặt hàng đang được thêm vào danh sách hoặc danh sách thêm một mặt hàng hoặc về item.AddTo(list) mặt hàng tự thêm vào danh sách hoặc tôi thêm mặt hàng đó vào danh sách hoặc như bạn đã nói mục được thêm vào danh sách - tất cả chúng đều đúng. Vì vậy, câu hỏi là ai là diễn viên và tại sao? Một mục cũng là một diễn viên và nó muốn tham gia một nhóm (danh sách) chứ không phải nhóm muốn có mục này ;-) mục đó hoạt động tích cực trong một nhóm.
t3chb0t

Diễn viên là điều làm điều tích cực. "Muốn" là một điều thụ động. Trong lập trình, đó là danh sách thay đổi khi một mục được thêm vào nó, mục đó không nên thay đổi.
Matt Ellen

... mặc dù nó thường thích khi nó có Parenttài sản. Phải thừa nhận rằng tiếng Anh không phải là ngôn ngữ mẹ đẻ nhưng theo như tôi biết thì muốn không bị động trừ khi tôi muốn hoặc anh ấy muốn tôi làm gì đó ; -]
t3chb0t

Hành động nào bạn thực hiện khi bạn muốn một cái gì đó? Đó là quan điểm của tôi. Nó không nhất thiết phải thụ động về mặt ngữ pháp nhưng về mặt ngữ nghĩa. Thuộc tính cha mẹ WRT, chắc chắn đó là một ngoại lệ, nhưng tất cả các heuristic đều có chúng.
Matt Ellen

Nhưng List<T>(ít nhất là một trong những tìm thấy trong System.Collections.Generic) không cập nhật bất kỳ Parenttài sản nào cả. Làm thế nào nó có thể, nếu nó được coi là chung chung? Thông thường trách nhiệm của container là thêm nội dung của nó.
Arturo Torres Sánchez
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.