Fiquei me perguntando o que torna o Iterator especial quando comparado a outras construções semelhantes, e isso fez o Gang of Four listá-lo como um padrão de design.
O Iterador é baseado no polimorfismo (uma hierarquia de coleções com uma interface comum) e na separação de preocupações (a iteração sobre as coleções deve ser independente da forma como os dados são estruturados).
Mas e se substituirmos a hierarquia de coleções por, por exemplo, uma hierarquia de objetos matemáticos (número inteiro, float, complexo, matriz etc.) e o iterador por uma classe que representa algumas operações relacionadas a esses objetos, por exemplo, funções de poder. O diagrama da classe seria o mesmo.
Provavelmente, poderíamos encontrar muitos exemplos semelhantes, como Writer, Painter, Encoder e provavelmente melhores, que funcionam da mesma maneira. No entanto, eu nunca ouvi nenhum deles ser chamado de Padrão de Design.
Então, o que torna o Iterator especial?
É o fato de ser mais complicado porque requer um estado mutável para armazenar a posição atual na coleção? Mas então, o estado mutável geralmente não está sendo considerado desejável.
Para esclarecer meu argumento, deixe-me dar um exemplo mais detalhado.
Aqui está o nosso problema de design:
Digamos que temos uma hierarquia de classes e uma operação definida nos objetos dessas classes. A interface desta operação é a mesma para cada classe, mas as implementações podem ser completamente diferentes. Supõe-se também que faz sentido aplicar a operação várias vezes no mesmo objeto, digamos com parâmetros diferentes.
Aqui está uma solução sensata para o nosso problema de design (praticamente uma generalização do padrão do iterador):
Para separação de preocupações, as implementações da operação não devem ser adicionadas como funções à hierarquia de classes original (objetos de operando). Como queremos aplicar a operação várias vezes no mesmo operando, ela deve ser representada por um objeto que mantém uma referência ao operando, não apenas por uma função. Portanto, o objeto operando deve fornecer uma função que retorna o objeto que representa a operação. Este objeto fornece uma função que executa a operação real.
Um exemplo:
Existe uma classe base ou interface MathObject
(nome estúpido, eu sei, talvez alguém tenha uma idéia melhor.) Com classes derivadas MyInteger
e MyMatrix
. Para cada MathObject
uma operação Power
deve ser definida que permita o cálculo de quadrado, cubo e assim por diante. Para podermos escrever (em Java):
MathObject i = new MyInteger(5);
Power powerOfFive = i.getPower();
MyInteger square = powerOfFive.calculate(2); // should return 25
MyInteger cube = powerOfFive.calculate(3); // should return 125
fonte
Respostas:
A maioria dos padrões do livro do GoF tem o seguinte em comum:
Os problemas resolvidos por esses padrões são tão básicos que muitos desenvolvedores os entendem principalmente como soluções alternativas para os recursos ausentes da linguagem de programação , o que é um ponto de vista válido para o IMHO (observe que o livro do GoF é de 1995, onde Java e C ++ não ofereciam tantos recursos como hoje).
O padrão do iterador se encaixa bem nessa descrição: ele resolve um problema básico que ocorre com muita frequência, independentemente de qualquer domínio específico, e, como você escreveu por si mesmo, é um bom exemplo de "separação de preocupações". Como você certamente sabe, o suporte direto ao iterador é algo que você encontra em muitas linguagens de programação desprezíveis atualmente.
Agora compare isso com os problemas que você escolheu:
Além disso, não vejo nada no seu exemplo de função de poder que não possa ser interpretado como uma aplicação do padrão de estratégia ou de comando, o que significa que essas partes básicas já estão no livro do GoF. Uma solução melhor pode conter métodos de sobrecarga ou extensão do operador, mas essas são coisas sujeitas a recursos de idioma, e é exatamente isso que o "OO significa" usado pelo "Gang" não poderia fornecer.
fonte
The problems solved by these patterns are so basic that many developers think their main purpose is to be workarounds for missing programming language features
- A ironia é que os desenvolvedores de software usam rotineiramente padrões de design de software com 20 anos de idade, enquanto ainda acreditam que estão escrevendo códigos de última geração.IEnumerable
eyield
). Mas para outros padrões do GoF, o que você escreveu provavelmente pode ser verdade.workarounds for missing programming language features:
blog.plover.com/prog/johnson.htmlThe Gang of Four cita a definição de padrão de Christopher Alexander:
Qual é o problema resolvido pelos iteradores?
Portanto, alguém poderia argumentar que o padrão do iterador é, por definição, específico do domínio para coleções. E isso está perfeitamente bem. Outros padrões, como o padrão do intérprete, são específicos do domínio para idiomas específicos do domínio, os padrões de fábrica são específicos do domínio para a criação de objetos,…. É claro que esse é um entendimento bastante tolo de "específico de domínio". Desde que seja um par recorrente entre problema e solução, podemos chamá-lo de padrão.
E é bom que o padrão Iterator exista. Coisas ruins acontecem se você não usá-lo. Meu anti-exemplo favorito é o Perl. Aqui, cada coleção (matriz ou hash) inclui o estado do iterador como parte da coleção. Por que isso é ruim? Podemos iterar facilmente um hash com um tempo - cada loop:
Mas e se chamarmos uma função no corpo do loop?
Esta função agora pode fazer praticamente o que quiser, exceto:
Se a função chamada deve usar o iterador, o comportamento do nosso loop se torna indefinido. Isso é um problema. E o padrão do iterador tem uma solução: coloque todo o estado da iteração em um objeto separado, criado por iteração.
Sim, é claro que o padrão do iterador está relacionado a outros padrões. Por exemplo, como o iterador é instanciado? Em Java, temos um genérico
Iterable<T>
eIterator<T>
interface. Um iterável concretoArrayList<T>
cria um tipo específico de iterador, enquanto umHashSet<T>
pode fornecer um tipo de iterador completamente diferente. Isso me lembra muito o padrão abstrato de fábrica, onde aIterable<T>
é a fábrica abstrata e oIterator
é o produto.Um iterador polimórfico também pode ser interpretado como um exemplo do padrão de estratégia. Por exemplo, uma árvore pode oferecer diferentes tipos de iteradores (pré-encomenda, encomenda, pós-venda,…). Externamente, todos compartilhariam uma interface do iterador e produziriam elementos em alguma sequência. O código do cliente precisa depender apenas da interface do iterador, não de um algoritmo de passagem em árvore específico.
Os padrões não existem isolados, independentes um do outro. Alguns padrões são soluções diferentes para o mesmo problema, e alguns padrões descrevem a mesma solução em contextos diferentes. Alguns padrões implicam outro. Além disso, o espaço do padrão não é fechado quando você vira a última página do livro Design Patterns (consulte também sua pergunta anterior O Gang of Four explorou completamente o “Espaço do Padrão”? ). Os padrões descritos no livro Design Patterns são muito flexíveis e amplos, abertos a variações infinitas e definitivamente não são os únicos padrões existentes.
Os conceitos que você lista (escrever, pintar, codificar) não são padrões, porque não descrevem uma combinação problema-solução. Uma tarefa como "preciso gravar dados" ou "preciso codificar dados" não é realmente um problema de design e não inclui uma solução; uma "solução" que consiste apenas em "Eu sei, vou criar uma classe Writer" não tem sentido. Mas se tivermos um problema como “Não quero que gráficos meio renderizados sejam pintados na tela”, pode existir um padrão: “Eu sei, usarei gráficos com buffer duplo!”
fonte