A classe RxJava Flowable pode legitimamente ter 460 métodos?

14

Estou apenas começando com o RxJava , a implementação do ReactiveX por Java (também conhecida como Rx e Reactive Extensions ). Algo que realmente me surpreendeu foi o enorme tamanho do RxJava Fluido classe : tem 460 métodos!

Para ser justo:

  • Existem muitos métodos sobrecarregados, que aumentam significativamente o número total de métodos.

  • Talvez essa classe deva ser dividida, mas meu conhecimento e entendimento do RxJava são muito limitados. As pessoas que criaram o RxJava são certamente muito inteligentes e, presumivelmente, podem oferecer argumentos válidos para optar por criar o Flowable com tantos métodos.

Por outro lado:

  • RxJava é a implementação em Java das Extensões Reativas da Microsoft , e que nem sequer tem uma classe Flowable , portanto, este não é o caso de portar cegamente uma classe existente e implementá-la em Java.

  • [ Atualização: o ponto anterior em itálico está factualmente incorreto: a classe Observable da Microsoft , que possui mais de 400 métodos, foi usada como base para a classe Observable do RxJava , e o Flowable é semelhante ao Observable, mas lida com a contrapressão para grandes volumes de dados. Portanto, a equipe do RxJava estava portando uma classe existente. Este post deveria ter sido desafiando o design original do Observable classe pela Microsoft ao invés de RxJava Fluido classe.]

  • O RxJava tem pouco mais de 3 anos, portanto, este não é um exemplo de código mal projetado devido à falta de conhecimento sobre os bons princípios de design de classe ( SOLID ) (como foi o caso das versões iniciais do Java).

Para uma classe tão grande quanto a Flowable, seu design parece inerentemente errado, mas talvez não; uma resposta a esta pergunta SE Qual é o limite para o número de métodos de uma classe? sugeriu que a resposta seja " Tenha quantos métodos você precisar ".

Claramente, existem algumas classes que legitimamente precisam de um número razoável de métodos para apoiá-las, independentemente da linguagem, porque elas não se decompõem prontamente em algo menor e possuem um número razoável de características e atributos. Por exemplo: seqüências de caracteres, cores, células da planilha, conjuntos de resultados do banco de dados e solicitações HTTP. Ter talvez algumas dezenas de métodos para as classes representarem essas coisas não parece irracional.

Mas o Flowable realmente precisa de 460 métodos, ou é tão grande que é necessariamente um exemplo de design de classe ruim?

[Para ser claro: esta pergunta refere-se especificamente a de RxJava Fluido classe em vez de objetos Deus em geral.]

skomisa
fonte
1
@gnat Bem, certamente relacionado, mas não é uma duplicata. Essa pergunta foi genérico, ea minha pergunta refere-se especificamente a de RxJava Fluido classe.
Skomisa
@skomisa Em seguida, corrija o título para atender à sua pergunta.
Euphoric
@Euphoric Point tomado.
Skomisa
1
Esta questão é muito interessante e fundamentada. No entanto, eu sugiro reformular-lo um pouco, a fim de adotar um estilo menos subjetivo (provavelmente devido ao choque inicial ;-))
Christophe

Respostas:

14

TL; DL

A falta de recursos de linguagem do Java em comparação com o C #, bem como considerações de descoberta nos fizeram colocar operadores de origem e intermediários em grandes classes.

Projeto

O Rx.NET original foi desenvolvido no C # 3.0, que possui dois recursos cruciais: métodos de extensão e classes parciais. O primeiro permite definir métodos de instância em outros tipos que parecem fazer parte desse tipo de destino, enquanto classes parciais permitem dividir grandes classes em vários arquivos.

Nenhum desses recursos estava ou está presente em Java; portanto, tivemos que encontrar uma maneira de ter o RxJava utilizável de maneira conveniente.

Existem dois tipos de operadores no RxJava: tipo fonte representado por métodos estáticos de fábrica e tipo intermediário representado por métodos de instância. O primeiro poderia viver em qualquer classe e, portanto, poderia ter sido distribuído por várias classes de utilidade. O último requer que uma instância seja trabalhada. Em conceito, tudo isso pode ser expresso por métodos estáticos, com o upstream como o primeiro parâmetro.

Na prática, no entanto, ter várias classes de entrada torna a descoberta de recursos por novos usuários inconveniente (lembre-se, o RxJava teve que trazer um novo conceito e paradigma de programação para Java), bem como o uso desses operadores intermediários foi um pesadelo. Portanto, a equipe original criou o chamado design de API fluente: uma classe que contém todos os métodos estáticos e de instância e representa um estágio de origem ou processamento por si só.

Devido à natureza de primeira classe dos erros, suporte à simultaneidade e natureza funcional, pode-se chegar a todos os tipos de fontes e transformações relacionadas ao fluxo reativo. À medida que a biblioteca (e o conceito) evoluíram desde os dias do Rx.NET, mais e mais operadores padrão foram adicionados, o que por natureza aumentou a contagem de métodos. Isso levou a duas reclamações comuns:

  • Por que existem tantos métodos?
  • Por que não existe um método X que resolva meu problema muito particular?

Escrever operadores reativos é uma tarefa difícil, que muitas pessoas não dominam ao longo dos anos; os usuários mais comuns da biblioteca não podem criar operadores sozinhos (e geralmente não é realmente necessário que eles tentem). Isso significa que, de tempos em tempos, adicionamos mais operadores ao conjunto padrão. Por outro lado, rejeitamos muito mais operadores por serem muito específicos ou simplesmente uma conveniência que não pode suportar seu próprio peso.

Eu diria que o design do RxJava cresceu organicamente e não de acordo com certos princípios de design, como o SOLID. É principalmente impulsionado pelo uso e sensação de sua API fluente.

Outras relações com Rx.NET

Entrei para o desenvolvimento do RxJava no final de 2013. Até onde eu sei, as versões iniciais iniciais do 0.x eram em grande parte uma reimplementação em caixa-preta, na qual os nomes e assinaturas dos Observableoperadores do Rx.NET , além de algumas decisões arquiteturais, foram reutilizados. Isso envolveu cerca de 20% dos operadores do Rx.NET. A principal dificuldade na época era a resolução de diferenças de linguagem e plataforma entre C # e Java. Com muito esforço, conseguimos implementar muitos operadores sem olhar o código-fonte do Rx.NET e portamos os mais complicados.

Nesse sentido, até o RxJava 0.19, o nosso Observableera equivalente ao Rx.NET IObservablee seus Observablemétodos de extensão complementares . No entanto, o chamado problema de contrapressão apareceu e o RxJava 0.20 começou a divergir do Rx.NET em nível de protocolo e arquitetura. Os operadores disponíveis foram ampliados, muitos tornaram-se sensíveis à contrapressão e introduzimos novos tipos: Singlee Completablena era 1.x, que não tem contrapartidas no Rx.NET até o momento.

A percepção da contrapressão complica consideravelmente as coisas e o 1.x o Observablerecebe como uma reflexão tardia. Juramos lealdade à compatibilidade binária, portanto, a alteração do protocolo e da API geralmente não era possível.

Havia outro problema na arquitetura do Rx.NET: o cancelamento síncrono não é possível porque, para isso, é necessário Disposableretornar antes que o operador comece a executar. No entanto, fontes como Rangeestavam ansiosas e não retornam até que terminem. Esse problema pode ser resolvido injetando um Disposableno em Observervez de retornar um subscribe().

O RxJava 2.x foi redesenhado e reimplementado do zero ao longo dessas linhas. Temos um tipo separado, sensível à pressão, Flowableque oferece o mesmo conjunto de operadores que Observable. Observablenão suporta contrapressão e é um pouco equivalente ao Rx.NET Observable. Internamente, todos os tipos reativos injetam seu identificador de cancelamento em seus consumidores, permitindo que o cancelamento síncrono funcione com eficiência.

akarnokd
fonte
10

Embora eu admita que não estou familiarizado com a biblioteca, dei uma olhada na classe Flowable em questão e parece que ela está agindo como uma espécie de hub. Em outras palavras, é uma classe destinada a validar entradas e distribuir chamadas de acordo no projeto.

Portanto, essa classe não seria realmente considerada um objeto de Deus, pois um objeto de Deus é aquele que tenta fazer tudo. Isso faz muito pouco em termos de lógica. Em termos de responsabilidade única, pode-se dizer que o único trabalho da turma é delegar trabalho em toda a biblioteca.

Então, naturalmente, essa classe exigiria um método para todas as tarefas possíveis que você exigiria de uma Flowableclasse nesse contexto. Você vê o mesmo tipo de padrão com a biblioteca jQuery em javascript, em que a variável $tem todas as funções e variáveis ​​necessárias para executar chamadas na biblioteca, embora no caso do jQuery, o código não seja simplesmente delegado, mas também com uma boa lógica é executado dentro.

Eu acho que você deve tomar cuidado para criar uma classe como essa, mas ela tem seu lugar, desde que o desenvolvedor se lembre de que é apenas um hub e, portanto, não se converte lentamente em um objeto divino.

Neil
fonte
2
Desculpas por remover a aceitação de sua resposta, o que foi útil e esclarecedor, mas a resposta subsequente de akarnokd foi de alguém da equipe do RxJava!
Skomisa
@skomisa Eu não poderia esperar mais nada se fosse minha própria pergunta! Nenhuma ofensa tomada! :)
Neil
7

O equivalente do RX do .NET Flowableé Observable . Ele também possui todos esses métodos, mas eles são estáticos e utilizáveis ​​como métodos de extensão . O ponto principal do RX é que a composição é escrita usando uma interface fluente .

Mas, para Java ter uma interface fluente, ele precisa que esses métodos sejam métodos de instância, pois os métodos estáticos não seriam bem compostos e não possui métodos de extensão para tornar os métodos estáticos compostos. Portanto, na prática, todos esses métodos poderiam ser transformados em métodos estáticos, desde que você não estivesse usando a sintaxe da interface fluente.

Eufórico
fonte
3
O conceito de interface fluente é, IMHO, uma solução alternativa para limitações de idioma. Efetivamente, você está construindo uma linguagem usando uma classe sobre Java. Se você pensar em quantos recursos, mesmo em uma linguagem de programação simples, possui e não conta todas as variantes sobrecarregadas, fica bastante fácil ver como você acaba com tantos métodos. Os recursos funcionais do Java 8 podem abordar muitos dos problemas que levam a esse tipo de design e linguagens contemporâneas, como o Kotlin, estão se movendo para poder obter o mesmo tipo de benefícios sem a necessidade de encadeamento de métodos.
precisa saber é o seguinte
Graças ao seu post, eu analisei mais profundamente e parece que o Observable do RX do .NET é ≈ para o Observable do RxJava, então uma premissa da minha pergunta estava incorreta. Atualizei o OP de acordo. Além disso, o Flowable R do RxJava (observável + alguns métodos extras para paralelizar e lidar com a contrapressão).
Skomisa
@skomisa O Java's Observable também possui centenas de métodos. Então você pode comparar os dois. Mas a principal diferença é que o Observable do .NET é estático e todos os métodos são estáticos. Enquanto o Java não é. E essa é uma enorme diferença. Mas, na prática, eles se comportam da mesma maneira.
Euphoric