Uso de impurezas en aplicaciones Flutter



Trabajo en una empresa de desarrollo de juegos, pero como hobby hogareño, recientemente me interesé en desarrollar aplicaciones móviles. Por lo tanto, cuando un amigo me invitó a una reunión dedicada al desarrollo de aplicaciones móviles utilizando el marco Flutter, estuve de acuerdo. Después de haber probado Flutter en acción allí, decidí definitivamente estudiar esta tecnología. Como el Dart necesario para el desarrollo no me era familiar, el aprendizaje de idiomas también se incluyó en el programa obligatorio. Después de sentarme un poco sobre los ejemplos de código, encontré a Dart un lenguaje conciso y fácil de entender que realmente me gustó. Una de las características de Dart que me ha gustado es las impurezas.

¿Qué son las impurezas?


Para un conocimiento inicial, daré un extracto de Wikipedia .
Admixture (English mix in) es un elemento de un lenguaje de programación (generalmente una clase o módulo) que implementa un comportamiento claramente definido. Se utiliza para aclarar el comportamiento de otras clases, no destinado a generar objetos utilizados de forma independiente.
En Dart, tales construcciones están definidas por la palabra mixin antes del nombre.

La definición anterior significa que obtenemos la funcionalidad de comportamientos lógicamente aislados que se pueden agregar a otras clases.

¿Te recuerda la posibilidad de herencia múltiple? Sí, pero me parece que el enfoque de impureza es mejor. Y por qué, veamos un ejemplo.

Supongamos que tenemos una clase abstracta Animal.

abstract class Animal { void voice(); } 

Y también las clases de perros y gatos que implementan la clase Animal.

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

Y de repente necesitábamos ...
gato

Sí, sí, personalmente no tengo tales cosas durante el desarrollo.

Y en el caso de herencia múltiple, haríamos lo mismo.

 class CatDog extends Cat, Dog { } 

Pero tan pronto como expresamos nuestro comando de voz a nuestra mascota, tenemos una situación muy desagradable: no está claro qué debe responder exactamente, porque el método de voz se implementa en ambas clases. Esta situación es ampliamente conocida y se llama el problema del diamante o Diamante mortal de la muerte .

Diamante mortal de la muerte

En el caso de ventas a través de impurezas, no lo 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 { } 

¿Y qué escucharemos si ahora damos un comando de voz? En este caso, Woof, y como ya entendió, depende del orden de adición de impurezas. Esto sucede porque su adición no es paralela, sino secuencial.

Implementé la clase Animal específicamente para marcar una característica introducida en Dart 2.1 . Antes, las impurezas solo podían agregarse a las clases que heredan de Object . A partir de la versión 2.1, se implementa la adición de cualquier clase a los herederos.

Este mecanismo hace que sea muy conveniente hacer y usar las partes comunes de lo funcional, lo que resuelve el problema de la duplicación de código. Veamos un ejemplo.

 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 puede ver, distribuimos todo el código duplicado por impurezas y solo utilizamos los necesarios en cada una de las implementaciones.

Durante el desarrollo, puede surgir una situación en la que la funcionalidad de cualquiera de las impurezas no esté disponible públicamente para su inclusión por todas las clases. Y también está disponible un mecanismo que nos permitirá imponer estas restricciones. Esta es la palabra clave on en la declaración de mezcla junto con el nombre de la clase. Por lo tanto, limitaremos el uso de impurezas solo a las clases que implementan lo especificado o heredado de él.

Por ejemplo:

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

Entonces podemos declarar clases similares:

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

Pero recibimos un error al intentar declarar algo como esto:
 class E with M1, M2 { } 

Uso en la aplicación Flutter


Como mencioné anteriormente, las impurezas le permiten deshacerse de la duplicación de código y crear partes lógicas separadas que pueden reutilizarse. Pero, ¿cómo es esto generalmente aplicable a Flutter, donde todo es atómico y está dividido en widgets que son responsables de cierta funcionalidad? Como ejemplo, inmediatamente imaginé una situación en la que el proyecto usa muchos widgets, cuya visualización varía según un determinado estado interno. Consideraré este ejemplo en la arquitectura BLoC y utilizaré entidades de la biblioteca rxDart.

Necesitamos una interfaz para cerrar el controlador de flujo.

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

Una mezcla con la que implementamos el apoyo 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(); } } 

La parte lógica que controlará el estado del widget. Deje que establezca un estado llamando al método y después de 3 segundos cámbielo a otro.

 /// 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; } } } 

Y el widget en sí que responderá a un cambio de estado. Imagine obtener el componente lógico deseado usando la inyección de dependencia.

 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 }, ); } } 

Si lo desea, incluso podemos agregar a la mezcla lo que está escrito y solo requiere la implementación del método de construcción utilizando la interfaz, pero me parece que esto ya no es necesario, porque es poco probable que haya muchos widgets tan simples en el proyecto, porque era solo un ejemplo. Sin embargo, la parte lógica del soporte de estado funcional se agregará fácilmente a cualquiera de los BLoC que utilicen este aditivo.

Conclusión


El mecanismo de usar impurezas me pareció una herramienta de desarrollo bastante interesante y flexible, que hace posible construir una arquitectura simple, comprensible y conveniente. Por mi parte, decidí que esta herramienta en mi kit obviamente no será superflua, espero que también te sea útil.

Recursos:
Un recorrido por el lenguaje dardo
Wikipedia

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


All Articles