Uso de mixins no Dart

Várias vezes uma pergunta foi feita aos colegas de que não está claro por que os mixins (impurezas) na língua Dart são necessários. Decidi ver o que há na Internet sobre esse assunto. Para grande desgosto dos artigos encontrados, eles falam principalmente sobre como usar impurezas, mas não explicam por que são necessários; nesses casos, seu uso é mais preferível do que a herança comum ou a implementação de interfaces. Este artigo é uma tentativa de preencher essa lacuna.


Apesar de haver artigos suficientes na Internet sobre impurezas em Dart e Flutter, eles não trazem clareza em minha opinião, porque os exemplos apresentados mostram a mecânica pura de construir classes com impurezas, o que está longe de ser razoável e, portanto, não demonstra o escopo real de sua aplicação. . Em particular, conheci esse exemplo . Nós temos:


class Animal {} class Dog {} class Cat {} 

E, por alguma razão, queríamos obter um animal que tivesse as propriedades de cães e gatos ao mesmo tempo. Nesse caso, podemos fazer o seguinte:


 class CatDog extends Animal with Cat, Dog {} 

Há pelo menos duas perguntas neste exemplo:


  • por que precisamos de um cruzamento entre um gato e um cachorro?
  • Por que um gato e um cachorro não herdam do Animal ? Eles não são animais?

Ao mesmo tempo, por que as impurezas são necessárias? Continua sendo um mistério.


Na minha humilde opinião, para entender o significado das impurezas, é preciso começar a considerar o problema com a relação de herança. O principal ponto de herança na OOP é que uma entidade é uma variação de outra entidade. Por exemplo, o é uma variação da ou o é uma variação do . E é exatamente isso que deve ser o fator determinante na construção de uma hierarquia de classes.


Se observarmos a herança de um ponto de vista diferente, veremos que o herda as propriedades da e o herda as propriedades do . Se você não prestar atenção à lógica, puramente tecnicamente, poderá querer herdar as propriedades de várias entidades diferentes. Para fazer isso, algumas linguagens de programação suportam herança múltipla .


A herança múltipla é criticada por várias deficiências (consulte a Wikipedia ); muitas linguagens de programação não usam herança múltipla, mas usam um mecanismo para implementar interfaces e / ou impurezas. E, do ponto de vista da lógica, as construções resultantes de herança múltipla não são fáceis de compreender.


Para entender o material a seguir, é necessário recuperar alguns conceitos da lógica elementar. Em particular, os conceitos de propriedades essenciais e não essenciais . As propriedades essenciais dos objetos são aquelas cuja presença se refere a uma classe específica de objetos. Propriedades não essenciais de um objeto são aquelas cuja presença, ausência ou valores específicos não afetam o objeto pertencente a uma determinada classe de objetos. Por exemplo, a forma de um retângulo é uma propriedade essencial desta figura, porque se alterarmos essa forma (remover ou adicionar um lado ou alterar os ângulos), o retângulo deixará de ser um retângulo. Mas se você redimensionar o retângulo, ele continuará sendo um retângulo. Portanto, as dimensões são uma propriedade insignificante.


A construção de uma hierarquia de classes geralmente se baseia na adição de propriedades essenciais à classe pai. Por exemplo


 abstract class Shape { void draw(); } class Rectangle extends Shape { @override void draw() { print('Draw rectangle'); } } class Circle extends Shape { @override void draw() { print('Draw circle'); } } 

A base dessa hierarquia é a propriedade essencial da forma da figura.


Outro exemplo:


 abstract class Widget { void render(); } class Container extends Widget { @override void render() { print('Renders container'); } } class Text extends Widget { @override void render('Render text'); } 

Uma propriedade essencial aqui é o objetivo do widget.


Agora, suponha que precisássemos adicionar propriedades não essenciais às nossas entidades. Essa propriedade, por exemplo, é cor. Vamos agora querer colorir algumas formas e widgets.


Para fazer isso, é claro, você pode usar a herança e primeiro implementar as PaintableWidget e PaintableWidget . Mas isso não é conveniente, porque, em primeiro lugar, teremos que duplicar a implementação da funcionalidade de coloração nas duas hierarquias e, em segundo lugar, para cada figura e widget que queremos colorir, teremos que implementar novas classes, por exemplo, PaintableRect e PaintableContainer .


Você pode usar o mecanismo para implementar interfaces. Então temos algo parecido com isto:


 enum Color {red, yellow, green} abstract class Paintable { void paint(Color color); Color get color; } class PaintableRect extends Rectangle implements Paintable { Color _color; @override void paint(Color color) {_color = color;} @override Color get color => _color; } class PaintableContainer extends Container implements Paintable { Color _color; @override void paint(Color color) {_color = color;} @override Color get color => _color; } 

Como você pode ver, essa também não é a melhor solução, pois precisamos duplicar o mesmo código para cada entidade resolvível.


Mas todos esses problemas podem ser resolvidos se a funcionalidade associada a uma propriedade insignificante for removida como uma mistura separada (mixin):


 enum Color {red, yellow, green} mixin PaintableMixin { Color _color; void paint(Color color) {_color = color;} Color get color => _color; } class PaintableRect extends Rectangle with PaintableMixin { @override void draw() { print('Draw rectangle with color $color'); } } class PaintableContainer extends Container with PaintableMixin { @override void render() { print('Render container with color $color'); } } 

Agora você pode usá-lo:


 main() { PaintableRect() ..paint(Color.red) ..draw(); PaintableContainer() ..paint(Color.yellow) ..render(); } 

Para resumir o exposto, pode-se determinar da seguinte maneira quando é conveniente usar impurezas: se existem várias hierarquias diferentes que precisam adicionar a mesma funcionalidade que define alguma propriedade não essencial para as entidades dessas hierarquias. Ou pode ser uma hierarquia, mas estamos lidando com seus diferentes ramos. Como exemplo, considere os widgets da estrutura Flutter.


Suponha que precisássemos adicionar funcionalidades relacionadas à mesma propriedade em alguns widgets. Os widgets no Flutter são construídos da seguinte maneira:


 class MyStatelessWidget extends StatelessWidget {} 

ou


 class MyStatefulWidget extends StatefulWidget {} 

Para adicionar uma propriedade por herança, você precisará implementar pelo menos duas classes:


 class StatelessWidgetWithProperty extends StatelessWidget {} class StatefulWidgetWithPropery extends StatefulWidget {} 

ao mesmo tempo, como você pode ver novamente, é necessário duplicar a funcionalidade associada à propriedade adicionada.


Ao usar impurezas, o problema é resolvido:


 mixin Property {} class MyStatelessWidget extends StatelessWidget with Propery {} class MyStatefulWidget extends StatefulWidget with Property {} 

Para aqueles que estão familiarizados com os padrões de design, o uso de impurezas em alguns casos pode substituir o uso do padrão Bridge .


Em conclusão, deve-se notar que, dessa maneira, pode-se misturar a funcionalidade de várias propriedades diferentes ao mesmo tempo em combinações arbitrárias.


Este artigo não pretende definir exaustivamente o uso de impurezas. Provavelmente, a mente indagadora do desenvolvedor poderá encontrar muitos usos mais bonitos para eles. Eu ficaria feliz se essas opções para o uso de impurezas aparecerem nos comentários deste artigo.

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


All Articles