Existe um padrão para uma maneira mais "natural" de adicionar itens às coleções? [fechadas]

13

Eu acho que a maneira mais comum de adicionar algo a uma coleção é usar algum tipo de Addmétodo que uma coleção fornece:

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

e na verdade não há nada incomum nisso.

Eu me pergunto, no entanto, por que não fazemos dessa maneira:

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

parece ser de alguma forma mais natural que o primeiro método. Isso teria a vantagem de que quando a Itemclasse tem uma propriedade como Parent:

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

você pode tornar o setter privado. Nesse caso, é claro que você não pode usar um método de extensão.

Mas talvez eu esteja errado e nunca vi esse padrão antes porque é tão incomum? Você sabe se existe esse padrão?

Em C#um método de extensão, seria útil para isso

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

E outras línguas? Eu acho que na maioria deles a Itemclasse tinha que fornecer uma ICollectionIteminterface , vamos chamá-lo .


Atualização-1

Estive pensando um pouco mais e esse padrão seria realmente útil, por exemplo, se você não quiser que um item seja adicionado a várias coleções.

ICollectableinterface de teste :

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

e uma implementação de exemplo:

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
}

uso:

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
fonte
13
item.AddTo(items)suponha que você tenha um idioma sem métodos de extensão: natural ou não, para oferecer suporte a addTo, todo tipo precisaria desse método e o forneceria para todo tipo de coleção que suporta anexos. Esse é o melhor exemplo de introdução de dependências entre tudo o que eu já ouvi: P - Acho que a falsa premissa aqui é tentar modelar alguma abstração de programação para a vida 'real'. Isso costuma dar errado.
stijn
1
@ t3chb0t Hehe, bom ponto. Fiquei imaginando se sua primeira língua falada influencia qual construção parece mais natural. Para mim, o único que parece natural seria add(item, collection), mas isso não é bom estilo OO.
precisa saber é o seguinte
3
@ t3chb0t Na linguagem falada, você pode ler as coisas normalmente ou de forma reflexiva. "John, por favor, adicione esta maçã ao que você está carregando." vs "A maçã deve ser adicionada ao que você está carregando, John." No mundo da OOP, reflexivo não faz muito sentido. Você não pede que algo seja afetado por outro objeto em geral. O mais próximo disso no OOP é o padrão de visitantes . Há uma razão para que essa não seja a norma.
Neil
3
Isso está colocando um arco legal em torno de uma má ideia .
Telastyn 15/01
3
@ t3chb0t Você pode achar o Reino dos Substantivos uma leitura interessante.
paul

Respostas:

51

Não, item.AddTo(items)não é mais natural. Eu acho que você mistura isso com o seguinte:

t3chb0t.Add(item).To(items)

Você está certo, pois items.Add(item)não está muito próximo do idioma natural inglês. Mas você também não ouve item.AddTo(items)no inglês natural, não é? Normalmente, há alguém que deveria adicionar o item à lista. Seja quando estiver trabalhando em um supermercado ou enquanto estiver cozinhando e adicionando ingredientes.

No caso das linguagens de programação, fizemos isso para que uma lista faça as duas coisas: armazenando seus itens e sendo responsável por adicioná-los a si mesmo.

O problema com sua abordagem é que um item precisa estar ciente de que existe uma lista. Mas um item poderia existir mesmo que não houvesse listas, certo? Isso mostra que ele não deve estar ciente das listas. As listas não devem ocorrer no seu código de forma alguma.

No entanto, as listas não existem sem itens (pelo menos seriam inúteis). Portanto, tudo bem se eles souberem de seus itens - pelo menos de uma forma genérica.

valenterry
fonte
4

A @valenterry está certa sobre o problema da separação de preocupações. As classes não devem saber nada sobre as várias coleções que podem contê-las. Você não gostaria de alterar a classe do item sempre que criar um novo tipo de coleção.

Dito isto...

1. Não é Java

O Java não ajuda em nada aqui, mas algumas linguagens têm uma sintaxe mais flexível e extensível.

No Scala, items.add (item) fica assim:

  item :: items

Onde :: é um operador que adiciona itens às listas. Parece muito o que você quer. Na verdade, é açúcar sintático para

  items.::(item)

onde :: é um método de lista Scala que é efetivamente equivalente ao list.add do Java . Portanto, embora pareça na primeira versão como se o item estivesse se adicionando aos itens , na verdade os itens estão fazendo a adição. Ambas as versões são Scala válidas

Esse truque é realizado em duas etapas:

  1. No Scala, os operadores são realmente apenas métodos. Se você importar listas de Java para Scala, em seguida, Items.Add (item) podem também ser escritos itens adicionar o item . Em Scala, 1 + 2 é na verdade 1. + (2) . Na versão com espaços, em vez de pontos e colchetes, Scala vê o primeiro objeto, procura um método correspondente à próxima palavra e (se o método usa parâmetros) passa a próxima coisa (ou coisas) para esse método.
  2. Em Scala, os operadores que terminam em dois pontos são associativos à direita, e não à esquerda. Portanto, quando Scala vê o método, ele procura o proprietário do método à direita e alimenta o valor à esquerda.

Se você não se sente confortável com muitos operadores e deseja o seu addTo , o Scala oferece outra opção: conversões implícitas. Isso requer duas etapas

  1. Crie uma classe de wrapper chamada, digamos, ListItem , que pegue um item em seu construtor e tenha um método addTo que pode adicionar esse item a uma determinada lista.
  2. Crie uma função que use um item como parâmetro e retorne um ListItem contendo esse item . Marque-o como implícito .

Se as conversões implícitas estiverem ativadas e o Scala visualizar

  item addTo items

ou

  item.addTo(items)

e o item não possui addTo , ele pesquisará no escopo atual as funções implícitas de conversão. Se qualquer uma das funções de conversão retorna um tipo que faz ter um addTo método, isso acontece:

  ListItem.(item).addTo(items)

Agora, você pode achar que as conversões implícitas são mais direcionadas ao seu gosto, mas isso aumenta o perigo, porque o código pode fazer coisas inesperadas se você não estiver claramente ciente de todas as conversões implícitas em um determinado contexto. É por isso que esse recurso não é mais ativado por padrão no Scala. (No entanto, enquanto você pode optar por habilitá-lo ou não em seu próprio código, não pode fazer nada sobre seu status nas bibliotecas de outras pessoas. Aqui estão dragões).

Os operadores com terminação de dois pontos são mais feios, mas não representam uma ameaça.

Ambas as abordagens preservam o princípio da separação de preocupações. Você pode fazer parecer que o item conhece a coleção, mas o trabalho é realmente feito em outro lugar. Qualquer outro idioma que deseje fornecer esse recurso deve ser pelo menos tão cuidadoso.

2. Java

Em Java, onde a sintaxe não é extensível por você, o melhor que você pode fazer é criar algum tipo de classe wrapper / decorator que conheça as listas. No domínio em que seus itens são colocados em listas, você pode agrupá-los. Quando eles saírem desse domínio, remova-os. Talvez o padrão de visitantes seja o mais próximo.

3. tl; dr

Scala, como alguns outros idiomas, pode ajudá-lo com uma sintaxe mais natural. Java só pode oferecer padrões feios e barrocos.

itsbruce
fonte
2

Com o seu método de extensão, você ainda precisa chamar Add () para não adicionar nada útil ao seu código. A menos que seu método addto tenha feito algum outro processamento, não há razão para ele existir. E escrever código que não tem um propósito funcional prejudica a legibilidade e a manutenção.

Se você deixar não genérico, os problemas se tornarão ainda mais aparentes. Agora você tem um item que precisa saber sobre os diferentes tipos de coleções em que pode estar. Isso viola o princípio de responsabilidade única. Além de um item possuir seus dados, ele também manipula coleções. É por isso que deixamos a responsabilidade de adicionar itens às coleções até o trecho de código que está consumindo os dois.

ao controle
fonte
Se uma estrutura reconhecer diferentes tipos de referências a objetos (por exemplo, distinguir entre aquelas que encapsulam a propriedade e as que não o fazem), pode fazer sentido ter um meio pelo qual uma referência que encapsula a propriedade possa renunciar à propriedade de uma coleção que foi capaz de recebê-lo. Se cada coisa estiver limitada a ter um proprietário, transferir a propriedade via thing.giveTo(collection)[e exigir collectiona implementação de uma interface "acceptOwnership"] faria mais sentido do que collectionincluir um método que assumisse a propriedade. Claro ...
supercat 15/01
... a maioria das coisas em Java não tem um conceito de propriedade, e uma referência a um objeto pode ser armazenada em uma coleção sem envolver o objeto identificado.
Supercat
1

Eu acho que você está obcecado com a palavra "adicionar". Tente procurar uma palavra alternativa, como "collect", que daria uma sintaxe mais amigável ao inglês, sem alterações no padrão:

items.collect(item);

O método acima é exatamente o mesmo que items.add(item);, com a única diferença sendo a escolha de palavras diferentes e flui muito bem e "naturalmente" em inglês.

Às vezes, apenas a renomeação de variáveis, métodos, classes etc. impedirá a redefinição de impostos do padrão.

Descanse em paz
fonte
collectàs vezes é usado como o nome de métodos que reduzem uma coleção de itens em algum tipo de resultado singular (que também pode ser uma coleção). Essa convenção foi adotada pela API Java Steam , que tornou imensamente popular o uso do nome nesse contexto. Eu nunca vi a palavra usada no contexto que você mencionou na sua resposta. Prefiro ficar com addouput
toniedzwiedz 15/01
@toniedzwiedz, Então considere pushou enqueue. O ponto não é o nome do exemplo específico, mas o fato de um nome diferente estar mais próximo do desejo do OP de gramática em estilo inglês.
Brian S
1

Embora não exista esse padrão para o caso geral (como explicado por outros), o contexto em que um padrão semelhante tem seus méritos é uma associação bidirecional um para muitos no ORM.

Nesse contexto, você teria duas entidades, digamos Parente Child. Um pai pode ter muitos filhos, mas cada filho pertence a um pai. Se o aplicativo exigir que a associação seja navegável pelas duas extremidades (bidirecional), você teria um List<Child> childrenna classe pai e um Parent parentna classe filho.

A associação deve sempre ser recíproca, é claro. As soluções ORM normalmente impõem isso nas entidades que eles carregam. Mas quando o aplicativo está manipulando uma entidade (persistente ou nova), ele deve aplicar as mesmas regras. Na minha experiência, você costumava encontrar um Parent.addChild(child)método que chama Child.setParent(parent), que não é para uso público. No último, você normalmente encontra as mesmas verificações implementadas no seu exemplo. A outra rota também pode ser implementada: Child.addTo(parent)quais chamadas Parent.addChild(child). Qual rota é melhor difere de situação para situação.

Maarten Winkels
fonte
1

Em Java, uma coleção típica não contém coisas - contém referências a coisas, ou cópias mais precisas de referências a coisas. A sintaxe de membro de ponto, por outro lado, é geralmente usada para agir sobre a coisa identificada por uma referência, e não sobre a própria referência.

Se colocar uma maçã em uma tigela alteraria alguns aspectos da maçã (por exemplo, sua localização), colocar um pedaço de papel que diz "objeto # 29521" em uma tigela não mudaria a maçã de nenhuma maneira, mesmo que isso aconteça. objeto # 29521. Além disso, como as coleções contêm cópias de referências, não há Java equivalente a colocar um pedaço de papel dizendo "objeto # 29521" em uma tigela. Em vez disso, copia-se o número # 29521 para um novo pedaço de papel e mostra-o (não doa) para a tigela, que fará sua própria cópia, deixando o original inalterado.

De fato, como as referências a objetos (os "pedaços de papel") em Java podem ser observadas passivamente, nem as referências nem os objetos identificados por meio deles têm como saber o que está sendo feito com eles. Existem alguns tipos de coleções que, em vez de apenas conter um monte de referências a objetos que nada sabem da existência da coleção, estabelecem uma relação bidirecional com os objetos nela encapsulados. Com algumas dessas coleções, pode fazer sentido solicitar que um método se adicione a uma coleção (especialmente se um objeto for capaz de estar em uma dessas coleções por vez). No entanto, em geral, a maioria das coleções não se encaixa nesse padrão e pode aceitar referências a objetos sem qualquer envolvimento por parte dos objetos identificados por essas referências.

supercat
fonte
é difícil ler este post (parede de texto). Você se importaria de editá -lo em uma forma melhor?
Gnat
@gnat: Isso é melhor?
Supercat
1
@gnat: Desculpe - um problema de rede causou uma falha na minha tentativa de publicar a edição. Você gosta mais agora?
Supercat
-2

item.AddTo(items)não é usado porque é passivo. Cf "O item é adicionado à lista" e "a sala é pintada pelo decorador".

Construções passivas são boas, mas, pelo menos em inglês, tendemos a preferir construções ativas.

Na programação OO, o pardigma dá destaque aos atores, e não àqueles com quem atuamos, assim temos items.Add(item). A lista é a coisa que está fazendo alguma coisa. É o ator, então esse é o caminho mais natural.

Matt Ellen
fonte
Depende de onde você está ;-) - a teoria da relatividade - você pode dizer o mesmo sobre list.Add(item) um item que está sendo adicionado à lista ou uma lista que adiciona um item ou sobre item.AddTo(list) o item que se adiciona à lista ou eu adiciono o item à lista ou como você disse que o item foi adicionado à lista - todos eles estão corretos. Então a pergunta é quem é o ator e por quê? Um item também é um ator e deseja ingressar em um grupo (lista), e não o grupo que deseja ter esse item ;-) o item atua ativamente para pertencer a um grupo.
precisa saber é
O ator é quem faz a coisa ativa. "Desejar" é uma coisa passiva. Na programação, é a lista que muda quando um item é adicionado a ele, o item não deve mudar.
Matt Ellen
... embora muitas vezes goste quando tem uma Parentpropriedade. É certo que o inglês não é o idioma nativo de maio, mas até onde eu sei querer não é passivo, a menos que eu seja querido ou ele queira que eu faça alguma coisa ; -]
t3chb0t
Que ação você executa quando quer alguma coisa? Esse é o meu ponto. Não é necessariamente gramaticalmente passivo, mas semanticamente é. Atributos pai do WRT, com certeza é uma exceção, mas todas as heurísticas os possuem.
Matt Ellen
Mas List<T>(pelo menos o encontrado em System.Collections.Generic) não atualiza nenhuma Parentpropriedade. Como poderia, se deveria ser genérico? Geralmente, é responsabilidade do contêiner adicionar seu conteúdo.
Arturo Torres Sánchez