Чи існує закономірність для більш "природного" способу додавання предметів до колекцій? [зачинено]


13

Я думаю, що найпоширеніший спосіб додати щось до колекції - це використовувати якийсь Addметод, який надає колекція:

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

і насправді в цьому немає нічого незвичайного.

Цікаво, але чому б ми не зробили це так:

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

здається, це якось природніше, ніж перший метод. Це буде мати таку перевагу, що коли Itemклас має властивість на зразок Parent:

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

ви можете зробити сетер приватним. У цьому випадку, звичайно, ви не можете використовувати метод розширення.

Але, можливо, я помиляюся, і я раніше ніколи не бачив цього шаблону, тому що це так рідко? Чи знаєте ви, чи існує така модель?

У C#розширенні метод був би корисний для цього

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

Як щодо інших мов? Я думаю, що в більшості з них Itemклас повинен був надати ICollectionItemінтерфейс, який називатимемо його .


Оновлення-1

Я думав про це трохи більше, і ця картина була б дуже корисною, наприклад, якщо ви не хочете, щоб предмет додався до кількох колекцій.

тестовий ICollectableінтерфейс:

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

та вибір вибір:

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
}

використання:

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)припустимо, у вас мова без методів розширення: природна чи ні, щоб підтримувати addTo кожен тип потребує цього методу та надає його для кожного типу колекції, що підтримує додавання. Це як найкращий приклад введення залежностей між усім, що я коли-небудь чув: P - Я думаю, що помилкова передумова тут намагається моделювати деяку абстракцію програмування до «реального» життя. Це часто йде не так.
stijn

1
@ t3chb0t Hehe, хороший момент. Мені було цікаво, чи впливає ваша перша розмовна мова на те, що конструкція здається більш природною. Для мене єдиним, який здається природним, було б add(item, collection), але це не гарний стиль ОО.
Кіліан Фот

3
@ t3chb0t Розмовною мовою ви можете читати речі нормально або рефлексивно. "Джон, будь ласка, додай це яблуко до того, що ти несеш". vs "Яблуко слід додати до того, що ти несеш, Джон". У світі ООП рефлексивне не має особливого сенсу. Ви не просите, щоб на щось впливав інший об'єкт, взагалі кажучи. Найбільш близьким до цього в OOP є модель відвідувачів . Є причина, що це не норма.
Ніл

3
Це покладати гарний лук навколо поганої ідеї .
Теластин

3
@ t3chb0t Ви можете виявити, що Королівство іменників цікаво читає.
Поль

Відповіді:


51

Ні, item.AddTo(items)це не природніше. Я думаю, ви змішуєте це з наступним:

t3chb0t.Add(item).To(items)

Ви маєте рацію в тому, що items.Add(item)не дуже близька до природної англійської мови. Але ви також не чуєте item.AddTo(items)на природній англійській мові, чи не так? Зазвичай є хтось, хто повинен додати елемент до списку. Будь то під час роботи в супермаркеті або під час готування та додавання інгредієнтів.

Що стосується мов програмування, ми створили так, що список робить і те, і інше: зберігання його елементів та відповідальність за додавання їх до себе.

Проблема вашого підходу полягає в тому, що елемент повинен усвідомлювати, що список існує. Але елемент може існувати, навіть якщо списків взагалі не було, правда? Це показує, що вона не повинна знати про списки. Списки взагалі не повинні виникати в його коді.

Проте списки не існують без предметів (принаймні, вони були б марними). Тому добре, якщо вони знають про свої предмети - принаймні в загальній формі.


4

@valenterry цілком правильно стосується проблеми розділення проблем. Класи не повинні нічого знати про різні колекції, які можуть містити їх. Вам не хотілося б змінювати клас предметів щоразу, коли ви створюєте новий вид колекції.

Це сказало ...

1. Не Java

Java вам тут не допомагає, але деякі мови мають більш гнучкий розширюваний синтаксис.

У Scala, items.add (item) виглядає так:

  item :: items

Де :: - оператор, який додає елементи до списків. Це здається дуже тим, що ви хочете. Це насправді синтаксичний цукор для

  items.::(item)

де :: - метод списку Scala, який ефективно еквівалентний Java's list.add . Тому, хоча в першій версії це виглядає так, ніби елемент додає себе до елементів , але насправді елементи роблять додавання. Обидві версії дійсні Scala

Цей трюк виконується в два етапи:

  1. У Scala оператори справді просто методи. Якщо ви імпортуєте списки Java в Scala, то items.add (item) також можуть бути записані елементи додавання елемента . У Scala 1 + 2 - це фактично 1. + (2) . У версії з пробілами, а не крапками і дужками, Scala бачить перший об'єкт, шукає метод, що відповідає наступному слову, і (якщо метод приймає параметри) передає наступний предмет (або речі) цьому методу.
  2. У Scala оператори, які закінчуються двокрапкою, мають право-асоціативний характер, а не лівий. Тож коли Scala бачить метод, він шукає власника методу праворуч і подає значення зліва.

Якщо вам не зручно з великою кількістю операторів і хочете отримати ваш додаток , Scala пропонує ще один варіант: неявні перетворення. Для цього потрібно два кроки

  1. Створіть клас обгортки під назвою, скажімо, ListItem, який приймає елемент у свій конструктор і має метод addTo, який може додати цей елемент до заданого списку.
  2. Створіть функцію, яка приймає елемент як параметр і повертає ListItem, що містить цей елемент . Позначте це як неявне .

Якщо включені неявні конверсії і Scala бачить

  item addTo items

або

  item.addTo(items)

і елемент не має addTo , він буде шукати поточну область для будь-яких неявних функцій перетворення. Якщо якісь - або з функцій перетворення повертає тип , який робить є AddTo метод, це відбувається:

  ListItem.(item).addTo(items)

Тепер ви можете знайти маршрут неявних перетворень більше на свій смак, але це додає небезпеки, оскільки код може робити несподівані речі, якщо ви чітко не знаєте про всі неявні перетворення в заданому контексті. Ось чому ця функція більше не включена за замовчуванням у Scala. (Однак, хоча ви можете вибрати, чи ввімкнути це у власному коді, ви нічого не можете робити щодо його статусу в чужих бібліотеках. Тут будуть дракони).

Оператори, що закінчуються двокрапкою, є більш брудними, але не становлять загрози.

Обидва підходи зберігають принцип розділення проблем. Ви можете зробити це виглядати , як ніби річ знає про колекцію, але робота насправді зроблена де - то в іншому місці. Будь-яка інша мова, яка хоче надати вам цю функцію, повинна бути принаймні такою ж обережною.

2. Java

У Java, де синтаксис не розширюється вами, найкраще ви можете створити якийсь клас обгортки / декоратора, який знає про списки. У домені, де ваші предмети розміщені у списках, ви можете їх зафіксувати. Коли вони покинуть цей домен, вийміть їх. Можливо, шаблон відвідувачів найближчий.

3. тл; д-р

Скала, як і деякі інші мови, може допомогти вам з більш природним синтаксисом. Java може запропонувати вам лише потворні, барокові візерунки.


2

За допомогою методу розширення вам все одно потрібно зателефонувати Add (), щоб він не додав нічого корисного у ваш код. Якщо ваш метод addto не здійснив якусь іншу обробку, немає причин для його існування. І написання коду, що не має функціонального призначення, погано для читабельності та ремонтопридатності.

Якщо залишити це не загальним, проблеми стануть ще більш очевидними. Тепер у вас є предмет, який повинен знати про різні типи колекцій, в яких він може знаходитися. Це порушує принцип єдиної відповідальності. Мало того, що предмет є власником своїх даних, він також маніпулює колекціями. Ось чому ми залишаємо відповідальність за додавання предметів до колекцій до того самого коду, який споживає їх обох.


Якщо рамки повинні розпізнавати різні типи посилань на об'єкти (наприклад, розрізняти ті, які інкапсулюють право власності, та ті, які не мають), можливо, має сенс мати засоби, за допомогою яких посилання на інкапсуляцію власності може відмовитись від власності на колекцію, яка була здатний її отримати. Якщо кожна річ обмежується наявністю одного власника, передача права власності через thing.giveTo(collection)[та вимагає collectionвпровадити інтерфейс "acceptOwnership"] має більше сенсу, ніж collectionвключати метод, який конфіскував право власності. Звичайно ...
supercat

... у більшості речей на Java немає поняття власності, і посилання на об'єкт може зберігатися у колекції без залучення до цього ідентифікованого об'єкта.
supercat

1

Я думаю, що ти одержимий словом "додати". Спробуйте шукати альтернативне слово, наприклад, "збирати", яке дасть вам синтаксис, сприятливіший англійській мові, без зміни шаблону:

items.collect(item);

Вищеописаний метод точно такий же, як items.add(item);, з тією лише різницею, що це вибір різних слів, і дуже красиво та "природно" протікає англійською мовою.

Іноді, лише перейменування змінних, методів, класів тощо запобігає переформуванню схеми оподаткування.


collectіноді використовується як назва для методів, що зводять колекцію предметів до якогось єдиного результату (який також може бути колекцією). Ця конвенція була прийнята Java Steam API , яка зробила використання цього імені в цьому контексті надзвичайно популярним. Я ніколи не бачив слова, вжитого в контексті, який ви згадуєте у своїй відповіді. Я вважаю за краще дотримуватися addабоput
toniedzwiedz

@toniedzwiedz, Тож розглянемо pushабо enqueue. Справа не в назві конкретного прикладу, а в тому, що інша назва може бути ближче до прагнення ОП до граматики англійською мовою.
Брайан S

1

Хоча для загального випадку такої структури немає (як пояснюють інші), контекст, в якому подібна закономірність має свої достоїнства, є двосторонньою асоціацією для багатьох в ORM.

У цьому контексті у вас було б дві сутності, скажімо так, Parentі Child. У батьків може бути багато дітей, але кожна дитина належить до одного з батьків. Якщо програма вимагає, щоб асоціація переходила з обох кінців (двонаправлена), ви матимете List<Child> childrenбатьківський клас та Parent parentдочірній клас.

Асоціація завжди повинна бути взаємною. Рішення ORM зазвичай застосовують це до об'єктів, які вони завантажують. Але коли програма маніпулює сутністю (збереженою чи новою), вона повинна застосовувати ті самі правила. На мій досвід, ви найчастіше знаходите Parent.addChild(child)метод Child.setParent(parent), який використовує, але не для публічного використання. В останньому ви зазвичай знаходите ті самі чеки, що й у вашому прикладі. Однак може бути реалізований і інший маршрут: Child.addTo(parent)який викликає Parent.addChild(child). Який маршрут найкраще відрізняється від ситуації до ситуації.


1

В Java, типовий набір не тримати речі --Оно тримають посилання на речі, точніше копію з посилань на речі. Синтаксис точкового члена, навпаки, зазвичай використовується для дії на річ, ідентифіковану посиланням, а не на саму посилання.

Якщо помістити яблуко в миску, це змінило б деякі аспекти яблука (наприклад, його розташування), поміщення в чашу паперового листа, на якому написано "об'єкт №29521", жодним чином не змінить яблуко, навіть якщо це станеться об’єкт № 29521. Крім того, оскільки колекції містять копії посилань, немає Java-рівномірності розміщенню в чашу аркуша паперу з написом "object # 29521" Натомість, один копіює номер # 29521 на новий аркуш паперу, і показує (не надає) це в миску, яка зробить їх власну копію, залишивши оригінал без змін.

Дійсно, оскільки посилання на об'єкти ("папірці") на Java можна пасивно спостерігати, ні посилання, ні об'єкти, ідентифіковані цим, не мають жодного способу знати, що з ними робиться. Існують деякі види колекцій, які, замість того, щоб просто містити купу посилань на об'єкти, які, можливо, нічого не знають про існування колекції, натомість встановлюють двосторонній зв'язок із об'єктами, закладеними в них. З деякими такими колекціями може бути доцільним запит методу додати себе до колекції (особливо, якщо об’єкт здатний знаходитись лише в одній такій колекції за раз). Однак, як правило, більшість колекцій не відповідає цьому шаблону і може приймати посилання на об'єкти без будь-якої участі з боку об'єктів, ідентифікованих цими посиланнями.


цю публікацію досить важко читати (стіна тексту). Ви б не хотіли відредагувати його в кращій формі?
гнат

@gnat: Це краще?
supercat

1
@gnat: Вибачте - мережева іконка спричинила невдачу моєї спроби опублікувати редагування. Вам це більше подобається зараз?
supercat

-2

item.AddTo(items)не використовується, тому що є пасивним. Cf "Елемент додається до списку" та "Кімната розписана декоратором".

Пасивні конструкції прекрасні, але, принаймні англійською мовою, ми віддаємо перевагу активним конструкціям.

У програмуванні OO парадигма надає визначну роль акторам, а не тим, на кого діяли, як у нас items.Add(item). Список - це те, що щось робить. Це актор, тому це більш природний шлях.


Це залежить від того, де ти стоїш ;-) - теорія відносності - ти можеш сказати те саме про те, list.Add(item) що предмет додається до списку або список додає предмет або про item.AddTo(list) те, що додає сам до списку, або я додаю предмет до списку або, як ви сказали, елемент додано до списку - усі вони є правильними. Тож питання в тому, хто актор і чому? Елемент також є актором, і він хоче приєднатися до групи (списку), а не в групі хоче, щоб цей елемент був ;-) елемент активно діє, щоб бути у групі.
t3chb0t

Актор - це те, що робить активну річ. "Бажання" - це пасивна річ. У програмуванні - це список, який змінюється, коли елемент додається до нього, елемент взагалі не повинен змінюватися.
Метт Еллен

... хоча це часто подобається, коли має Parentвластивість. Дійсно, англійська мова не є рідною мовою, але, наскільки я знаю, бажання не є пасивним, якщо мене не хочуть, або він хоче, щоб я щось зробив ; -]
t3chb0t

Яку дію ви виконуєте, коли чогось хочете? Це моя думка. Це не обов'язково граматично пасивно, але семантично це. WRT батьківські атрибути, впевнені, що це виняток, але всі евристики мають їх.
Метт Еллен

Але List<T>(принаймні той, який знайдено в ньому System.Collections.Generic) взагалі не оновлює жодне Parentвластивість. Як це могло бути, якщо воно повинно бути загальним? Зазвичай контейнер несе відповідальність за додавання його вмісту.
Артуро Торрес Санчес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.