Usando impurezas em aplicações de vibração



Eu trabalho em uma empresa de desenvolvimento de jogos, mas como hobby em casa, recentemente me interessei em desenvolver aplicativos móveis. Portanto, quando um amigo me convidou para uma reunião dedicada ao desenvolvimento de aplicativos móveis usando a estrutura Flutter, concordei com prazer. Tendo tentado o Flutter em ação lá, decidi definitivamente estudar essa tecnologia. Como o Dart necessário para o desenvolvimento não me era familiar, o aprendizado de idiomas também foi incluído no programa obrigatório. Depois de analisar um pouco os exemplos de código, achei o Dart uma linguagem concisa e fácil de entender que realmente gostei. Uma das características de Dart que eu gostei são as impurezas.

O que são impurezas?


Para uma introdução inicial, darei um trecho da Wikipedia .
A mistura (mistura inglesa) é um elemento de uma linguagem de programação (geralmente uma classe ou módulo) que implementa algum comportamento claramente definido. Usado para esclarecer o comportamento de outras classes, não destinado a gerar objetos usados ​​independentemente.
No Dart, essas construções são definidas pela palavra mixin antes do nome.

A definição acima significa que obtemos a funcionalidade de comportamentos isolados logicamente que podem ser adicionados a outras classes.

Isso lembra a possibilidade de herança múltipla? Sim, mas me parece que a abordagem da impureza é melhor. E por que, vejamos um exemplo.

Suponha que tenhamos uma classe abstrata Animal.

abstract class Animal { void voice(); } 

E também as classes Gato e Cão que implementam a classe Animal.

 class Cat extends Animal { void voice() { print(“Meow”); } } class Dog extends Animal { void voice() { print(“Woof”); } } 

E então de repente precisávamos ...
gato

Sim, sim, eu pessoalmente não tenho essas coisas durante o desenvolvimento.

E no caso de herança múltipla, faríamos o mesmo.

 class CatDog extends Cat, Dog { } 

Mas assim que damos voz ao nosso animal de estimação, temos uma situação muito desagradável - não está claro o que exatamente ele deve responder, porque o método de voz é implementado nas duas classes. Essa situação é amplamente conhecida e é chamada de problema do diamante ou Diamante Mortal da Morte .

Diamante mortal da morte

No caso de vendas através de impurezas, não a encontraremos.

 lass Animal { void voice() { print(“Hakuna Matata!”); } } mixin Cat { void voice() { print(“Meow”); } } mixin Dog { void voice() { print(“Woof”); } } class CatDog extends Animal with Cat, Dog { } 

E o que ouviremos se agora dermos um comando de voz? Neste caso - Woof, e como você já entendeu, depende da ordem de adição de impurezas. Isso acontece porque sua adição não é paralela, mas sequencial.

Eu implementei a classe Animal especificamente para marcar um recurso introduzido no Dart 2.1 . Antes disso, as impurezas só podiam ser adicionadas às classes que herdam de Object . A partir da versão 2.1, a adição de qualquer classe aos herdeiros é implementada.

Esse mecanismo torna muito conveniente criar e usar as partes comuns do funcional, o que resolve o problema da duplicação de código. Vejamos um exemplo.

 abstract class Sportsman { void readySteadyGo(); } mixin SkiRunner { void run() { print(“Ski, ski, ski”); } } mixin RifleShooter { void shot() { print(“Pew, pew, pew”); } } class Shooter() extends Sportsman with RifleShooter { void readySteadyGo() { shot(); } } class Skier() extends Sportsman with SkiRunner { void readySteadyGo() { run(); } } class Biathlete() extends Sportsman with SkiRunner, RifleShooter { void readySteadyGo() { run(); shot(); } } 

Como você pode ver, distribuímos todo o código duplicado por impurezas e usamos apenas os necessários em cada uma das implementações.

Durante o desenvolvimento, pode surgir uma situação em que a funcionalidade de qualquer uma das impurezas não deve estar publicamente disponível para inclusão por todas as classes. E um mecanismo que nos permitirá impor essas restrições também está disponível. Esta é a palavra-chave on na declaração de mistura junto com o nome da classe. Portanto, limitaremos o uso de impurezas apenas às classes que implementam o especificado ou herdado dele.

Por exemplo:

 class A { } abstract class B { } mixin M1 on A { } mixin M2 on B { } 

Então podemos declarar classes semelhantes:

 class C extends A with M1 { } class D implements B with M2 { } 

Mas temos um erro ao tentar declarar algo assim:
 class E with M1, M2 { } 

Uso no aplicativo Flutter


Como mencionei acima, as impurezas permitem eliminar a duplicação de código e criar partes lógicas separadas que podem ser reutilizadas. Mas como isso é geralmente aplicável ao Flutter, onde tudo é atômico e, portanto, dividido em widgets responsáveis ​​por uma certa funcionalidade? Como exemplo, imaginei imediatamente uma situação em que o projeto usa muitos widgets, cuja exibição varia dependendo de um determinado estado interno. Vou considerar este exemplo na arquitetura BLoC e usar entidades da biblioteca rxDart.

Precisamos de uma interface para fechar o controlador de fluxo.

 /// Interface for disposable objects abstract class Disposable { void dispose(); } 

Uma mistura com a qual implementamos suporte estatal.

 /// Mixin for object which support state mixin StateProvider implements Disposable { static const NONE_STATE = "None"; final _stateController = BehaviorSubject<String>(seedValue: NONE_STATE); Observable<String> get stateOut => _stateController.stream; String get currentState => _stateController.value; void setState(String state) { _stateController.sink.add(state); } @override void dispose() { _stateController.close(); } } 

A parte lógica que controlará o estado do widget. Deixe-a definir um estado chamando o método e depois de 3 segundos altere-o para outro.

 /// Example BLoC class ExampleBloc implements Disposable with StateProvider { static const EXAMPLE_STATE_1 = "EX1"; static const EXAMPLE_STATE_2 = "EX2"; Timer _timer; void init() { setState(EXAMPLE_STATE_1); _timer = new Timer(const Duration(seconds: 3), () { _timer = null; setState(EXAMPLE_STATE_2); }); } @override void dispose() { if (_timer != null) { _timer.cancel(); _timer = null; } } } 

E o próprio widget que responderá a uma mudança de estado. Imagine obter o componente lógico desejado usando a injeção de dependência.

 class ExampleWidget extends StatelessWidget { final bloc = di<HomePageBloc>(); @override Widget build(BuildContext context) { return StreamBuilder( stream: bloc.stateOut, builder: (BuildContext context, AsyncSnapshot<String> snapshot) { // build widget by state }, ); } } 

Se desejar, podemos até adicionar à mistura o que está escrito e exigir apenas a implementação do método construtor usando a interface, mas parece-me que isso já é desnecessário, porque é improvável que existam muitos widgets simples no projeto, porque era apenas um exemplo. No entanto, a parte lógica do suporte do estado funcional será facilmente adicionada a qualquer um dos BLoCs usando essa mistura.

Conclusão


O mecanismo do uso de impurezas me pareceu uma ferramenta de desenvolvimento bastante interessante e flexível, que possibilita a construção de uma arquitetura simples, compreensível e conveniente. Por mim, decidi que essa ferramenta no meu kit obviamente não será supérflua, espero que seja útil para você também.

Recursos:
Um tour pela língua dos dardos
Wikipedia

Source: https://habr.com/ru/post/pt467143/


All Articles