Flutter: Componentização com Abstract Factory Pattern
Carlos Costa • 07/11/2024 Abstract Factory é um padrão de criação que fornece uma interface para criar conjuntos de objetos relacionados ou dependentes sem especificar suas classes concretas. Nesse tutorial, vamos entender como applicar esse pattern ao Flutter.Introdução
Abstract Factory é um padrão de criação que fornece uma interface para criar conjuntos de objetos relacionados ou dependentes sem especificar suas classes concretas.
Alguns benefícios do Abstract Factory são:
-
Encapsulamento: A lógica da criação encapsulada na classe Factory, os clients não precisam se preocupar com a implementação detalhada.
-
Baixo acoplamento: o código do client é desacoplado das classes concretas. O client só precisa saber sobre a interface abstrata.
-
Responsabilidade única: A lógica da criação é concentraada em apenas um lugar, permitindo que o código seja mais fácil de manter.
Vejamos um exemplo:
// Abstract Shape class
abstract class Shape {
void draw();
factory Shape.circle() {
return Circle();
}
factory Shape.square() {
return Square();
}
}
// Concrete Circle class
class Circle implements Shape {
@override
void draw() {
print('Drawing a Circle');
}
}
// Concrete Square class
class Square implements Shape {
@override
void draw() {
print('Drawing a Square');
}
}
void main(List<String> args) {
var circle = Shape.circle();
circle.draw(); // Output: Drawing a Circle
var square = Shape.square();
square.draw(); // Output: Drawing a Square
}
Podemos explicar o exemplo anterior da seguinte forma: A factory Shape
é responsável por definir os contratos e fornecer todos objetos do tipo Shape
. O cliente main
não precisa saber sobre a implementação concreta de cada objeto Shape
, ele apenas precisa saber sobre o objeto Shape
e como desenhar.
Componentização aplicado ao Flutter com Abstract Factory
O nosso próximo exemplo será baseado no seguinte problema:
Precisamos criar um factory que vai fornecer Widgets para vários tipos de pagamento: pix, boleto e cartão de crédito. Cada Widget que vai entrar na lista deve seguir o contrato definido pela factory obedecendo todos os requisitos necessários.
Primeiro vamos definir o nosso contrato com a seguinte classe abstrata:
abstract class PaymentOption extends StatelessWidget {
final String title;
final String subtitle;
final String? description;
final Function(Order order) paymentAction;
final ImageProvider icon;
const PaymentOption({
super.key,
required this.title,
required this.subtitle,
required this.description,
required this.paymentAction,
required this.icon,
});
@override
Widget build(BuildContext context);
}
Agora vamos criar as classes concretas que implementam o contrato:
//pix
class _PixPayment extends PaymentOption {
const _PixPayment({
required super.title,
required super.subtitle,
required super.description,
required super.paymentAction,
required super.icon,
});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
//credit card
class _CreditCardPayment extends PaymentOption {
const _CreditCardPayment({
required super.title,
required super.subtitle,
required super.description,
required super.paymentAction,
required super.icon,
});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
//bill
class _BillPayment extends PaymentOption {
const _BillPayment({
required super.title,
required super.subtitle,
required super.description,
required super.paymentAction,
required super.icon,
});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
Ok, nossas opções de pagamento foram criadas, agora podemos usar a nossa factory para fornecer cada tipo de pagamento.
abstract class PaymentOption extends StatelessWidget {
final String title;
final String subtitle;
final String? description;
final Function(Order order) paymentAction;
final ImageProvider icon;
const PaymentOption({
super.key,
required this.title,
required this.subtitle,
required this.description,
required this.paymentAction,
required this.icon,
});
@override
Widget build(BuildContext context);
factory PaymentOption.pix({
required String title,
required String subtitle,
required String? description,
required Function(Order order) paymentAction,
required ImageProvider icon,
}) {
return _PixPayment(
title: title,
subtitle: subtitle,
description: description,
paymentAction: paymentAction,
icon: icon,
);
}
factory PaymentOption.creditCard({
required String title,
required String subtitle,
required String? description,
required Function(Order order) paymentAction,
required ImageProvider icon,
}) {
return _CreditCardPayment(
title: title,
subtitle: subtitle,
description: description,
paymentAction: paymentAction,
icon: icon,
);
}
factory PaymentOption.bill({
required String title,
required String subtitle,
required String? description,
required Function(Order order) paymentAction,
required ImageProvider icon,
}) {
return _BillPayment(
title: title,
subtitle: subtitle,
description: description,
paymentAction: paymentAction,
icon: icon,
);
}
}
Apesar da classe PaymentOption
ser abstrata, ela pode fornecer objetos através de construtores factory
.
Factory Contructors são um tipo especial de construtor no Dart que não cria diretamente uma instância da classe, em vez disso, eles delegam a criação do objeto a outro construtor ou método, isso permite estratégias de criação de objetos mais flexíveis
Tudo pronto, nossa factory está completa, agora podemos criar nossa lista de widgets de pagamentos.
List<PaymentOption> paymentOptions = [
PaymentOption.pix(
title: 'Pagamento via pix',
subtitle: '10% de desconto',
paymentAction: (order) {},
icon: Image.asset('assets/pix.png'),
),
PaymentOption.creditCard(
title: 'Cartão de crédito',
subtitle: 'Até 8 vezes sem juros',
description: 'A confirmação de pagamento é feita pelo cartão de crédito e pode levar até 8 dias para ser processada.',
paymentAction: (order) {},
icon: Image.asset('assets/credit_card.png'),
),
PaymentOption.bill(
title: 'Pagamento por boleto',
subtitle: '5% de desconto',
paymentAction: (order) {},
icon: Image.asset('assets/bill.png'),
),
];
Conclusão
Criar nossos components com o auxílio do Abstract Factory Pattern pode ser uma técnica útil para projetos que exigem uma estrutura modular e reutilizável, podemos ter como vantagens o baixo acoplamento, flexibilidade na criação de objetos e a segurança de seguir um contrato para a criação de futuros componentes que compartilham o mesmo padrão.