Есть ли шаблон для более «естественного» способа добавления предметов в коллекции? [закрыто]

13

Я думаю, что наиболее распространенным способом добавления чего-либо в коллекцию является использование какого-либо Addметода, который предоставляет коллекция:

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

и в этом нет ничего необычного.

Интересно, однако, почему бы нам не сделать это таким образом:

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

это кажется более естественным, чем первый метод. Это будет иметь andvantange, что, когда у 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интерфейс , назовем его так .


Update-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);
t3chb0t
источник
13
item.AddTo(items)Предположим, у вас есть язык без методов расширения: естественный или нет, для поддержки addTo каждому типу понадобится этот метод и предоставьте его для каждого типа коллекции, поддерживающей добавление. Это как лучший пример введения зависимостей между всем, что я когда-либо слышал: П - Я думаю, что ложная предпосылка здесь - попытка смоделировать некоторую программную абстракцию до «реальной» жизни. Это часто идет не так.
Стийн
1
@ t3chb0t Хе-хе, хорошая мысль. Мне было интересно, влияет ли ваш первый разговорный язык на то, что конструкция кажется более естественной. На мой взгляд, единственное, что кажется естественным add(item, collection), - это не хороший ОО-стиль.
Килиан Фот
3
@ t3chb0t На разговорном языке вы можете читать вещи нормально или рефлексивно. «Джон, пожалуйста, добавь это яблоко к тому, что ты несешь». vs "Яблоко должно быть добавлено к тому, что ты несешь, Джон." В мире ООП рефлексивность не имеет особого смысла. Вы вообще не просите, чтобы на что-то воздействовал другой объект. Наиболее близким к этому в ООП является шаблон посетителя . Есть причина, что это не норма, хотя.
Нил
3
Это положит хороший поклон плохой идее .
Теластин
3
@ t3chb0t Вы можете найти интересное чтение в Королевстве Существительных .
Поль

Ответы:

51

Нет, item.AddTo(items)это не более естественно. Я думаю, что вы смешали это со следующим:

t3chb0t.Add(item).To(items)

Вы правы в том, что items.Add(item)не очень близки к естественному английскому языку. Но вы также не слышите item.AddTo(items)на естественном английском языке, не так ли? Обычно есть кто-то, кто должен добавить элемент в список. Будь то при работе в супермаркете или во время приготовления и добавления ингредиентов.

В случае с языками программирования мы сделали так, чтобы список выполнял одновременно: хранение своих элементов и ответственность за их добавление к себе.

Проблема с вашим подходом состоит в том, что элемент должен знать, что список существует. Но предмет может существовать, даже если списков не было вообще, верно? Это показывает, что он не должен знать о списках. Списки вообще не должны встречаться в его коде.

Однако списки не существуют без элементов (по крайней мере, они будут бесполезны). Поэтому хорошо, если они знают о своих предметах - по крайней мере, в общей форме.

valenterry
источник
4

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

Это сказал ...

1. Не Java

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

В Scala items.add (item) выглядит так:

  item :: items

Где :: это оператор, который добавляет элементы в списки. Это похоже на то, что вы хотите. Это на самом деле синтаксический сахар для

  items.::(item)

где :: - это метод списка Scala, который фактически эквивалентен списку Java list.add . Так что, хотя в первой версии это выглядит так, как будто элемент добавляет себя к элементам , в действительности элементы делают добавление. Обе версии действительны Scala

Этот трюк делается в два этапа:

  1. В Scala операторы - это на самом деле просто методы. Если вы импортируете списки Java в Scala, то items.add (item) также может быть записан 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. TL; Dr

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

itsbruce
источник
2

С вашим методом расширения вы все равно должны вызывать Add (), чтобы он не добавил ничего полезного в ваш код. Если ваш метод addto не выполнял какую-либо другую обработку, нет причин для его существования. А написание кода, не имеющего функционального назначения, плохо сказывается на удобстве чтения и обслуживания.

Если оставить его не общим, проблемы станут еще более очевидными. Теперь у вас есть предмет, который должен знать о различных видах коллекций, в которых он может находиться. Это нарушает принцип единой ответственности. Мало того, что предмет является держателем для его данных, он также манипулирует коллекциями. Вот почему мы оставляем ответственность за добавление элементов в коллекции до фрагмента кода, который потребляет их обоих.

контроль
источник
Если структура должна была распознавать различные виды ссылок на объекты (например, различать те, которые инкапсулируют владение, и те, которые этого не делают), может иметь смысл иметь средство, с помощью которого ссылка, которая инкапсулирует владение, может отказаться от владения коллекцией, которая была способный получить это. Если каждая вещь ограничена наличием одного владельца, передача права собственности через thing.giveTo(collection)[и требование collectionреализации интерфейса «acceptOwnership»] будет более целесообразной, чем collectionвключение метода, который захватит владение. Конечно ...
суперкат
... большинство вещей в Java не имеют концепции владения, и ссылка на объект может быть сохранена в коллекции без вовлечения идентифицированного объекта.
суперкат
1

Я думаю, что вы одержимы словом «добавить». Вместо этого попробуйте поискать альтернативное слово, такое как «собирать», которое даст вам более понятный для английского синтаксис без изменений в шаблоне:

items.collect(item);

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

Иногда простое переименование переменных, методов, классов и т. Д. Будет препятствовать повторной разработке схемы налогообложения.

Покойся с миром
источник
collectиногда используется в качестве имени для методов, которые превращают коллекцию элементов в какой-то особый результат (который также может быть коллекцией). Это соглашение было принято Java Steam API , что сделало использование имени в этом контексте чрезвычайно популярным. Я никогда не видел слово, использованное в контексте, который вы упоминаете в своем ответе. Я бы предпочел придерживаться addилиput
toniedzwiedz
@toniedzwiedz, так считай pushили enqueue. Дело не в конкретном названии примера, а в том, что другое имя может быть ближе к желанию ОП английской грамматики.
Брайан С
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-эквивалента для размещения куска бумаги с надписью «объект # 29521». Вместо этого кто-то копирует номер # 29521 на новый листок бумаги и показывает (а не дает) его чаше, которая сделает свою собственную копию, оставив оригинал без изменений.

Действительно, поскольку ссылки на объекты («листки бумаги») в Java можно пассивно наблюдать, ни ссылки, ни идентифицированные ими объекты не имеют никакого способа узнать, что с ними делается. Существуют некоторые виды коллекций, которые вместо того, чтобы просто хранить группу ссылок на объекты, которые могут ничего не знать о существовании коллекции, вместо этого устанавливают двунаправленную связь с объектами, инкапсулированными в ней. В некоторых таких коллекциях может иметь смысл попросить метод добавить себя в коллекцию (особенно если объект может находиться только в одной такой коллекции за раз). В целом, однако, большинство коллекций не соответствуют этому шаблону и могут принимать ссылки на объекты без какого-либо участия со стороны объектов, идентифицированных этими ссылками.

Supercat
источник
этот пост довольно трудно читать (стена текста). Не могли бы вы изменить его в лучшую форму?
комнат
@gnat: это лучше?
суперкат
1
@gnat: Извините - сбой сети привел к тому, что моя попытка опубликовать редактирование не удалась. Тебе сейчас нравится больше?
суперкат
-2

item.AddTo(items)не используется, потому что это пассивно. См. «Товар добавлен в список» и «Комната расписана декоратором».

Пассивные конструкции хороши, но, по крайней мере на английском языке, мы предпочитаем активные конструкции.

В ОО-программировании парадигма отдает приоритет актерам, а не тем, на кого действовали, как мы items.Add(item). Список - это то, что делает что-то. Это актер, так что это более естественный путь.

Мэтт Эллен
источник
Это зависит от того, где вы стоите ;-) - теория относительности - вы можете сказать то же самое о том, list.Add(item) что элемент добавляется в список, или список добавляет элемент, или о item.AddTo(list) том, что элемент добавляется в список, или я добавляю элемент к списку или, как вы сказали, элемент добавлен в список - все они верны. Итак, вопрос в том, кто актер и почему? Элемент также является актером, и он хочет присоединиться к группе (списку), а группа не хочет иметь этот элемент ;-) элемент действует активно, чтобы быть в группе.
t3chb0t
Актер - это то, что делает активную вещь. «Хотеть» - это пассивная вещь. В программировании это список, который изменяется при добавлении к нему элемента, элемент не должен меняться вообще.
Мэтт Эллен
... хотя это часто нравится, когда у него есть Parentсвойство. По общему признанию, английский не является родным языком, но, насколько я знаю, желание не является пассивным, если я не хочу или он не хочет, чтобы я что-то делал ; -]
t3chb0t
Какое действие вы совершаете, когда чего-то хотите? Это моя точка зрения. Это не обязательно грамматически пассивно, но семантически это так. Родительские атрибуты WRT, конечно, это исключение, но все эвристики имеют их.
Мэтт Эллен
Но List<T>(по крайней мере, найденный в System.Collections.Generic) не обновляет Parentсвойства вообще. Как это может быть, если он должен быть общим? Обычно контейнер несет ответственность за добавление его содержимого.
Артуро Торрес Санчес