As funções de primeira classe substituem o padrão de estratégia?

15

O padrão de design da estratégia é frequentemente considerado um substituto para funções de primeira classe em idiomas que não as possuem.

Por exemplo, digamos que você desejasse passar a funcionalidade para um objeto. Em Java, você teria que passar no objeto outro objeto que encapsula o comportamento desejado. Em um idioma como Ruby, você passaria a própria funcionalidade na forma de uma função anônima.

No entanto, eu estava pensando sobre isso e decidi que talvez a Strategy ofereça mais do que uma simples função anônima.

Isso ocorre porque um objeto pode conter um estado que existe independentemente do período em que o método é executado. No entanto, uma função anônima por si só pode conter apenas um estado que deixa de existir no momento em que a função termina a execução.

Em uma linguagem orientada a objetos que suporta funções de primeira classe, o padrão de estratégia tem alguma vantagem sobre o uso de funções?

Aviv Cohn
fonte
10
"No entanto, uma função anônima por si só só pode conter um estado que deixa de existir no momento em que a função termina a execução.": Isso não é verdade: um fechamento pode conter um estado que vive através de diferentes invocações.
Giorgio
"No entanto, uma função anônima por si só só pode conter um estado que deixa de existir no momento em que a função termina a execução." : não esquecendo variáveis ​​globais e variáveis ​​estáticas, no mínimo.
Gbjbaanb

Respostas:

13

Quando a linguagem suporta referências à função ( Java faz desde a versão 8 ), essas geralmente são uma boa alternativa para estratégias, porque geralmente expressam a mesma coisa com menos sintaxe. No entanto, existem alguns casos em que um objeto real pode ser útil.

Uma interface de estratégia pode ter vários métodos. Vamos usar uma interface RouteFindingStragegycomo exemplo, que engloba diferentes algoritmos de localização de rotas. Poderia declarar métodos como

  • Route findShortestRoute(Node start, Node destination)
  • boolean doesRouteExist(Node start, Node destination)
  • Route[] findAllPossibleRoutes(Node start, Node destination)
  • Route findShortestRouteToClosestDestination(Node start, Node[] destinations)
  • Route findTravelingSalesmanRoute(Node[] stations)

que então seriam todos implementados pela estratégia. Alguns algoritmos de localização de rota podem permitir otimizações internas para alguns desses casos de uso e outros não, portanto o implementador pode decidir como implementar cada um desses métodos.

Outro caso é quando a estratégia tem um estado interno. Certamente, em alguns idiomas, os fechamentos podem ter um estado interno, mas quando esse estado interno se torna muito complexo, muitas vezes fica mais elegante promover o fechamento para uma classe de pleno direito.

Philipp
fonte
2
"quando esse estado interno se torna muito complexo, muitas vezes se torna útil promover o fechamento de uma classe completa": Por quê? Se o estado interno ficar complexo, você também pode colocá-lo em um objeto / registro que é armazenado dentro do fechamento.
Giorgio
1
@ Giorgio Mas então você teria duas entidades de sintaxe para manter - o fechamento e a classe que gerencia seu estado interno. Portanto, o código pode ser simplificado movendo o fechamento para essa classe. Isso pode ser melhor ou não, o que depende do caso de uso exato, da linguagem de programação e das preferências pessoais.
Philipp
Parece-me uma visão razoável.
Giorgio
5

Não é verdade que uma função anônima possa manter apenas o estado que deixa de existir quando a função termina a execução.

Veja o seguinte exemplo no Common Lisp:

(defun number-strings (ss)
  (let ((counter 0))
    (mapcar #'(lambda (s) (format nil "~a: ~a" (incf counter) s)) ss)))

Esta função pega uma lista de strings e precede um contador para cada elemento da lista. Então, por exemplo, invocando

(number-strings '("a" "b" "c"))

("1: a" "2: b" "3: c")

A função number-stringsusa internamente uma função anônima com uma variável counterque mantém o estado (o valor atual do contador) que é reutilizado toda vez que a função é chamada.

Em geral, você pode pensar em um fechamento como um objeto com apenas um método. Como alternativa, um objeto é uma coleção de fechamentos que compartilham as mesmas variáveis ​​de fechamento. Portanto, não tenho certeza se há casos em que você precisa usar um objeto em vez de um fechamento: eu argumentaria que ambas são maneiras de olhar para o mesmo padrão de perspectivas diferentes.

Em particular, o padrão de estratégia requer um objeto com apenas um método, portanto, um fechamento deve fazer o trabalho. Mas, como Philipp observou em sua resposta, dependendo das circunstâncias (estado complexo) e das linguagens de programação, você pode obter uma solução mais elegante usando objetos.

Giorgio
fonte
Então, em uma linguagem que suporta funções de primeira classe como fechamento, você ainda usaria a estratégia 'clássica'?
Aviv Cohn
1
Costumo concordar com Philipp: depende do idioma e das preferências pessoais. Eu sempre escolheria a abordagem que torna a notação o mais simples possível. Por exemplo, no Lisp, eu poderia definir meu estado como uma lista de variáveis ​​através de a lete depois definir meu fechamento dentro dele. Basicamente, eu teria definido um objeto com um método em tempo real. Em outra linguagem (por exemplo, Java), pode ser mais conveniente (sintaticamente mais simples) definir um objeto apropriado para conter o estado. Então, eu decidiria caso a caso.
Giorgio
1

Só porque dois projetos podem resolver o mesmo problema não significa que eles sejam substituições diretas um do outro.

Se você precisar rastrear o estado em um programa funcional, não modifique uma variável fechada, mesmo que o idioma permita. Você planeja chamar uma função que aceita um estado como argumento e retorna o novo estado como seu valor de retorno.

Sua arquitetura terá uma aparência muito diferente, mas você alcançará o mesmo objetivo. Não tente forçar os padrões de um paradigma diretamente sobre o outro.

Karl Bielefeldt
fonte
"Se você precisa rastrear o estado em um programa funcional, não modifica uma variável fechada, mesmo que a linguagem permita.": Eu sou fã do estilo puramente funcional e concordo com o seu conselho. Por outro lado, os fechamentos não são apenas uma construção funcional, muito menos puramente funcional. A idéia de fechar as variáveis ​​do contexto lexical é ortogonal à transparência / imutabilidade referencial.
Giorgio
Desculpe, mas não posso seguir sua argumentação. O que o tratamento de estado na programação puramente funcional tem a ver com a questão em questão?
Philipp
1
O ponto é que se você usa uma parte de um paradigma funcional, como funções de primeira classe, não se surpreenda se precisar extrair outras partes do paradigma para fazê-lo funcionar sem problemas.
Karl Bielefeldt
1

Estratégia é um conceito , uma receita útil para resolver um problema recorrente recorrente. Não é uma construção de linguagem, nem é sobre qualquer forma de implementação . Um fechamento pode ser usado para implementar a estratégia em um dia e o Observer no dia seguinte.

O termo Estratégia é muito útil em conversas com outros programadores para expressar de forma concisa sua intenção. Não há nada de mágico nisso.

idobia
fonte
2
A pergunta menciona especificamente o padrão de design da estratégia que possui uma estrutura de classe específica. O outro significado de "estratégia" como plano de ação destinado a atingir um objetivo específico é impreciso neste contexto.
Estou inclinado a concordar com @Snowman. Tem certeza de que está falando sobre o padrão de estratégia ?
Rowan Freeman
1
@ Snowman, mesmo a página à qual você vinculou não indica exatamente como esse padrão deve ser implementado, eles fornecem exemplos em linguagens específicas, mas não há nada no diagrama UML que diga que eu preciso usar herança C ++, interfaces Java ou blocos Ruby . Por isso, discordo gentilmente da sua análise.
Idoby