Vejo os benefícios de tornar objetos no meu programa imutáveis. Quando estou realmente pensando profundamente em um bom design para o meu aplicativo, muitas vezes chego naturalmente a muitos dos meus objetos sendo imutáveis. Muitas vezes chega ao ponto em que eu gostaria de ter todos os meus objetos imutáveis.
Esta pergunta lida com a mesma idéia, mas nenhuma resposta sugere qual é uma boa abordagem para a imutabilidade e quando realmente usá-la. Existem bons padrões de design imutáveis? A idéia geral parece ser "tornar objetos imutáveis, a menos que você precise absolutamente mudar", o que é inútil na prática.
Minha experiência é que a imutabilidade direciona meu código cada vez mais ao paradigma funcional e essa progressão sempre acontece:
- Começo a precisar de estruturas de dados persistentes (no sentido funcional), como listas, mapas etc.
- É extremamente inconveniente trabalhar com referências cruzadas (por exemplo, nó de árvore que faz referência a seus filhos enquanto filhos referenciam seus pais), o que me faz não usar referências cruzadas, o que novamente torna minhas estruturas de dados e código mais funcionais.
- A herança para para fazer algum sentido e eu começo a usar a composição.
- Todas as idéias básicas de OOP, como encapsulamento, começam a desmoronar e meus objetos começam a parecer funções.
Neste ponto, praticamente não estou mais usando nada do paradigma OOP e posso apenas mudar para uma linguagem puramente funcional. Assim, minha pergunta: existe uma abordagem consistente para o bom design imutável de OOP ou sempre é que, quando você leva a idéia imutável ao máximo de seu potencial, você sempre acaba programando em uma linguagem funcional que não precisa mais de nada do mundo da OOP? Existem boas diretrizes para decidir quais classes devem ser imutáveis e quais devem permanecer mutáveis para garantir que a POO não desmorone?
Apenas por conveniência, darei um exemplo. Vamos ter uma ChessBoard
coleção imutável de peças de xadrez imutáveis (estendendo a classe abstrataPiece
) Do ponto de vista do POO, uma peça é responsável por gerar movimentos válidos a partir de sua posição no tabuleiro. Mas, para gerar os movimentos, a peça precisa de uma referência ao seu tabuleiro, enquanto o tabuleiro precisa ter referência às suas peças. Bem, existem alguns truques para criar essas referências cruzadas imutáveis, dependendo da sua linguagem OOP, mas eles são difíceis de gerenciar, é melhor não ter uma peça para referenciar seu quadro. Mas então a peça não pode gerar seus movimentos, pois não sabe o estado do tabuleiro. Em seguida, a peça se torna apenas uma estrutura de dados contendo o tipo e a posição da peça. Você pode usar uma função polimórfica para gerar movimentos para todos os tipos de peças. Isso é perfeitamente possível na programação funcional, mas quase impossível no OOP sem verificações de tipo de tempo de execução e outras práticas ruins de OOP ...
Respostas:
Não vejo porque não. Faz isso há anos que o Java 8 fica funcional, de qualquer maneira. Já ouviu falar de Strings? Agradável e imutável desde o início.
Também precisei deles o tempo todo. A invalidação de meus iteradores porque você modificou a coleção enquanto eu estava lendo é apenas rude.
Referências circulares são um tipo especial de inferno. A imutabilidade não salvará você disso.
Bem, aqui estou com você, mas não vejo o que isso tem a ver com imutabilidade. A razão de eu gostar de composição não é porque eu amo o padrão de estratégia dinâmica, é porque me permite alterar meu nível de abstração.
Estremeço ao pensar qual é a sua idéia de "OOP como encapsulamento". Se envolver getters e setters, basta parar de chamar esse encapsulamento, porque não é. Isso nunca foi. É manual Programação Orientada a Aspectos. A chance de validar e um local para colocar um ponto de interrupção é boa, mas não é um encapsulamento. O encapsulamento está preservando meu direito de não saber ou me importar com o que está acontecendo lá dentro.
Seus objetos devem parecer funções. Eles são sacos de funções. São sacos de funções que se movem juntas e se redefinem juntas.
A programação funcional está em voga no momento e as pessoas estão lançando alguns conceitos errados sobre OOP. Não deixe que isso o confunda, acreditando que este é o fim do POO. Funcional e POO podem conviver muito bem.
A programação funcional está sendo formal sobre atribuições.
OOP está sendo formal sobre ponteiros de função.
Realmente isso. Dykstra nos disse que
goto
era prejudicial, por isso fomos formais e criamos programação estruturada. Assim, esses dois paradigmas são sobre como encontrar maneiras de fazer as coisas, evitando as armadilhas que surgem ao se fazer essas coisas problemáticas casualmente.Deixe-me mostrar-lhe algo:
f n (x)
Essa é uma função. Na verdade, é um continuum de funções:
f 1 (x)
f 2 (x),
...
f n (x)
Adivinha como expressamos isso nas línguas OOP?
n.f(x)
Esse pouco
n
escolhe qual implementaçãof
é usada E decide quais são algumas das constantes usadas nessa função (que significa francamente a mesma coisa). Por exemplo:f 1 (x) = x + 1
f 2 (x) = x + 2
É a mesma coisa que os fechamentos fornecem. Onde os fechamentos se referem ao seu escopo, os métodos de objeto se referem ao estado da instância. Objetos podem fazer fechamentos um melhor. Um fechamento é uma única função retornada de outra função. Um construtor retorna uma referência a um pacote inteiro de funções:
g 1 (x) = x 2 + 1
g 2 (x) = x 2 + 2
Sim, você adivinhou:
n.g(x)
feg são funções que mudam juntas e se movem juntas. Então, colocamos na mesma bolsa. Isto é o que realmente é um objeto. Manter
n
constante (imutável) significa apenas que é mais fácil prever o que eles farão quando você os chamar.Agora isso é apenas a estrutura. A maneira como penso sobre OOP é um monte de coisinhas que falam com outras coisinhas. Esperemos que um pequeno grupo seleto de pequenas coisas. Quando codifico, imagino-me como o objeto. Eu olho as coisas do ponto de vista do objeto. E tento ser preguiçoso para não trabalhar demais no objeto. Recebo mensagens simples, trabalho nelas um pouco e envio mensagens simples apenas para meus melhores amigos. Quando termino com esse objeto, pulo para outro e vejo as coisas da perspectiva dele.
Os cartões de responsabilidade de classe foram os primeiros a me ensinar a pensar dessa maneira. Cara, eu estava confuso sobre eles naquela época, mas caramba, se eles ainda não são relevantes hoje.
Arg! Novamente com as desnecessárias referências circulares.
Que tal: A
ChessBoardDataStructure
transforma cabos xy em referências de peças. Essas peças têm um método que pega x, ye um particularChessBoardDataStructure
e o transforma em uma coleção de novos tipos deChessBoardDataStructure
s. Em seguida, enfia isso em algo que pode escolher a melhor jogada. AgoraChessBoardDataStructure
pode ser imutável e as peças também. Desta forma, você só tem um peão branco na memória. Existem apenas várias referências a ele nos locais xy certos. Orientado a objetos, funcional e imutável.Espere, já não conversamos sobre xadrez?
fonte
Os conceitos mais úteis introduzidos no mainstream pelo OOP, na minha opinião, são:
Todos esses benefícios também podem ser obtidos sem os detalhes tradicionais da implementação, como herança ou mesmo classes. A idéia original de Alan Kay de um "sistema orientado a objetos" usava "mensagens" em vez de "métodos" e estava mais próxima de Erlang do que, por exemplo, em C ++. Veja o Go, que elimina muitos detalhes tradicionais da implementação de OOP, mas ainda parece razoavelmente orientado a objetos.
Se você usar objetos imutáveis, ainda poderá usar a maioria dos presentes tradicionais de OOP: interfaces, envio dinâmico, encapsulamento. Você não precisa de setters, e muitas vezes nem precisa de getters para objetos mais simples. Você também pode colher os benefícios da imutabilidade: sempre tem certeza de que um objeto não mudou nesse meio tempo, nenhuma cópia defensiva, nenhuma corrida de dados e é fácil tornar os métodos puros.
Veja como Scala tenta combinar imutabilidade e abordagens de FP com OOP. É certo que não é a linguagem mais simples e elegante. É razoavelmente praticamente bem-sucedido, no entanto. Além disso, dê uma olhada no Kotlin, que fornece muitas ferramentas e abordagens para um mix semelhante.
O problema usual de tentar uma abordagem diferente da que os criadores de idiomas tinham em mente N anos atrás é a "incompatibilidade de impedâncias" da biblioteca padrão. Os ecossistemas Java e .NET da OTOH atualmente têm um suporte razoável da biblioteca padrão para estruturas de dados imutáveis; é claro que também existem bibliotecas de terceiros.
fonte