Patrones de diseño con Flutter: 5— Abstract Factory

Paolo Pinto
5 min readApr 12, 2024

--

Planteamos en el capitulo anterior tener una misma base de código pero con distintas interfaces(UI), pero no mencionamos si podiamos llevarlo a la realidad. El problema surge cuando tenemos 2 lenguajes de diseño en nuestra app(ios y android). Asi nos preguntamos ¿Podemos hacerlo con Flutter o debemos acudir al desarrollo nativo?

Ahora es cuando Imaginas! Tenemos una tienda de ropa para toda la familia(nosotros hacemos la ropa). Aperturas la tienda y te va de lo mejor, acabas todo el stock. De repente ves que la demanda es extraña, necesitas: Una variedad de modelos para cada tipo de ropa(tamaño, genero, color, material), y todo esto sin alterar lo que construiste.

familia en tienda de ropa

¿ Que se puede hacer con el patron Abstract Factory?

Nos permite producir familias de objetos relacionados sin especificar sus clases concretas. Se nos puede presentar que nos confundimos con el anterior(factory method), pero se complementan, method con abstract factory.

Se trata de crear una “fabrica” para cada categoria, en un ejemplo que me gusta, se da a conocer para una muebleria en este caso se tiene una fabrica para los objetos del tipo minimalsta y tradicional(victoriana).

abstracción de una factory

Nos sustetamos del hecho de que creamos nuestro “pequeños” factory de cada componente, en nuestro caso de ejemplo, serian camina, pantalon, zapatos. Luego abstraer una interface de un “factory” para crear cada catergoria. Como un factory de factories.

Diagrama de clases original

En este ejemplo tenemos tres abstract products, y en total seis concrete products, dos para cada interface(abstract products), esto debido a que tenemos dos concrete factories.

diagrama de clases ejemplo UI de IOS(Cuppertino) y Android(Material)

Como observamos tenemos como dos fabricas(factories) y debido a eso podemos implementar cuantos features deseemos sin alterar la estructura principal.

1. AbstractFactory: IWidgetsFactory

  • AbstractProductA: ISwitch
  • AbstractProductB: ISlider
  • AbstractProductC: IActivityIndicator
import 'package:flutter/material.dart';

// Interface for -> ConcreteProduct
abstract interface class ISwitch {
Widget render({required bool value, required ValueSetter<bool> onChanged});
}

// Interface for -> ConcreteProduct
abstract interface class ISlider {
Widget render(double value, ValueSetter<double> onChanged);
}


// Interface for -> ConcreteProduct
abstract interface class IActivityIndicator {
Widget render();
}


// Interface for -> ConcreteFactories
abstract interface class IWidgetsFactory {
String getTitle();
IActivityIndicator createActivityIndicator();
ISlider createSlider();
ISwitch createSwitch();
}

ConcreteProductA1: AndroidSwitch
ConcreteProductA2: IosSwitch

// Android
import 'package:flutter/material.dart';

import '../widgets_factory.dart';

class AndroidSwitch implements ISwitch {
const AndroidSwitch();

@override
Widget render({required bool value, required ValueSetter<bool> onChanged}) {
return Switch(
activeColor: Colors.black,
value: value,
onChanged: onChanged,
);
}
}

//IOS
import 'package:flutter/cupertino.dart';

import '../widgets_factory.dart';

class IosSwitch implements ISwitch {
const IosSwitch();

@override
Widget render({required bool value, required ValueSetter<bool> onChanged}) {
return CupertinoSwitch(
value: value,
onChanged: onChanged,
);
}
}

ConcreteProductB1: AndroidSlider
ConcreteProductB2: IosSlider

// Android
import 'package:flutter/material.dart';

import '../widgets_factory.dart';

class AndroidSlider implements ISlider {
const AndroidSlider();

@override
Widget render(double value, ValueSetter<double> onChanged) {
return Slider(
activeColor: Colors.black,
inactiveColor: Colors.grey,
max: 100.0,
value: value,
onChanged: onChanged,
);
}
}

// IOS

import 'package:flutter/cupertino.dart';

import '../widgets_factory.dart';

class IosSlider implements ISlider {
const IosSlider();

@override
Widget render(double value, ValueSetter<double> onChanged) {
return CupertinoSlider(
max: 100.0,
value: value,
onChanged: onChanged,
);
}
}

ConcreteProductB1: AndroidActivityIndicator
ConcreteProductB2: IosActivityIndicator

Notese que con cupertino es potencialmente más sencillo que con material en el ActivityIndicator.

// Android
import 'package:flutter/material.dart';

import '../widgets_factory.dart';

class AndroidActivityIndicator implements IActivityIndicator {
const AndroidActivityIndicator();

@override
Widget render() {
return CircularProgressIndicator(
backgroundColor: const Color(0xFFECECEC),
valueColor: AlwaysStoppedAnimation<Color>(
Colors.black.withOpacity(0.65),
),
);
}
}
// IOS
import 'package:flutter/cupertino.dart';

import '../widgets_factory.dart';

class IosActivityIndicator implements IActivityIndicator {
const IosActivityIndicator();

@override
Widget render() {
return const CupertinoActivityIndicator();
}
}

ConcreteFactory1: MaterialWidgetsFactory

class MaterialWidgetsFactory implements IWidgetsFactory {
const MaterialWidgetsFactory();

@override
String getTitle() => 'Android widgets';

@override
IActivityIndicator createActivityIndicator() =>
const AndroidActivityIndicator();

@override
ISlider createSlider() => const AndroidSlider();

@override
ISwitch createSwitch() => const AndroidSwitch();
}

ConcreteFactory2: CupertinoWidgetsFactory

class CupertinoWidgetsFactory implements IWidgetsFactory {
const CupertinoWidgetsFactory();

@override
String getTitle() => 'iOS widgets';

@override
IActivityIndicator createActivityIndicator() => const IosActivityIndicator();

@override
ISlider createSlider() => const IosSlider();

@override
ISwitch createSwitch() => const IosSwitch();
}

Codigo Cliente:

import 'package:flutter/material.dart';


class AbstractFactoryExample extends StatefulWidget {
const AbstractFactoryExample();

@override
_AbstractFactoryExampleState createState() => _AbstractFactoryExampleState();
}

class _AbstractFactoryExampleState extends State<AbstractFactoryExample> {
// Fabrica
final widgetFactory = const MaterialWidgetsFactory();
// con IOS:
// final widgetFactory = const CupertinoWidgetsFactory();

// widgets activity indicator y slider
late IActivityIndicator _activityIndicator;
late ISlider _slider;
var _sliderValue = 50.0;
String get _sliderValueString => _sliderValue.toStringAsFixed(0);

// switch + valores necesarios para switch
late ISwitch _switch;
var _switchValue = false;
String get _switchValueString => _switchValue ? 'ON' : 'OFF';

@override
void initState() {
super.initState();
// crear widgets desde nuestra factory
_createWidgets();
}


// crear widgets desde nuestra fabrica
void _createWidgets() {
_activityIndicator = widgetFactory.createActivityIndicator();
_slider = widgetFactory.createSlider();
_switch = widgetFactory.createSwitch();
}

void _setSliderValue(double value) => setState(() => _sliderValue = value);

void _setSwitchValue(bool value) => setState(() => _switchValue = value);

@override
Widget build(BuildContext context) {
return ScrollConfiguration(
behavior: const ScrollBehavior(),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Text(widgetFactory.getTitle()),
const SizedBox(height: 30),
Text(
'Widgets showcase',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 50),

const Text('Activity Indicator',),
_activityIndicator.render(),

const SizedBox(height: 50),
Text('Slider ($_sliderValueString%)'),
_slider.render(_sliderValue, _setSliderValue),

const SizedBox(height: 50),
Text('Switch ($_switchValueString)'),
_switch.render(
value: _switchValue,
onChanged: _setSwitchValue,
),
],
),
),
);
}
}

main.dart

import 'package:design_patterns/abstrac_factory_example.dart';
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
theme: ThemeData(
useMaterial3: true
),
home: Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: AbstractFactoryExample()
),
);
}
}

Listo! Ya implementaste Abstract Factory

Otros articulos para esta serie

Creacionales:

Estructurales:

  • Pronto…

De Comportamiento:

  • Pronto…..

Tu contribución

👏 ¡Presiona el botón de aplaudir a continuación para mostrar tu apoyo y motivarme a escribir mejor!
💬 Deje una respuesta a este artículo brindando sus ideas, comentarios o deseos para la serie.
📢 Comparte este artículo con tus amigos y colegas en las redes sociales.
➕ Sígueme en Medium.
⭐ Ve los ejemplos prácticos desde mi canal en Youtube: Pacha Code

--

--