Flutter开发实战:访问者模式(Visitor Patt
访问者模式(Visitor Pattern)
是一种行为设计模式,允许您向对象结构中的元素添加进一步的操作,而无需修改这些元素的类。这是通过在访问者类中将操作与元素类分离来实现的。
模式的结构
访问者模式主要包含以下几种角色:
- Element(元素) :定义一个接受访问者的操作。
- ConcreteElement(具体元素) :实现Element接口中定义的接受操作。该操作的功能是允许访问者访问该元素类。
- Visitor(访问者) :定义一个代表访问者的接口。为每个ConcreteElement类声明一个访问操作。
- ConcreteVisitor(具体访问者) :为Visitor接口实现每一个操作。
- ObjectStructure(对象结构) :表示可以被访问的元素的集合。它提供了一个高级的接口,可以允许访问者访问它的元素。
优点:
- 可以在不修改已存在的对象结构的情况下,增加新的操作。
- 访问者模式集中了相关的行为,而不是分散在多个对象类中。
- 访问者可以累计状态,当它遍历对象结构时。
缺点:
- 如果经常需要添加或更改ConcreteElement类,访问者模式可能会变得困难。
- 使用访问者模式可能会破坏封装性。因为访问者需要访问元素的内部属性和方法。
下面让我们一起通过两个在实际开发中用到的场景来看一下利用访问者模式(Visitor Pattern)
如何使我们的开发更便捷。
场景一:多语言支持
需要支持多种语言,可能会有一系列的部件,每个部件都包含一些文本内容。为了支持多语言,需要为这些部件提供不同语言的翻译。
使用访问者模式,可以创建一个访问者,该访问者将为每种语言提供翻译,并应用于部件中的文本内容。这样,你就不需要修改每个部件的实现,而只需提供相应语言的翻译。
// ----------------------------------
// Element
// ----------------------------------
abstract class WidgetElement {
void accept(TranslationVisitor visitor);
}
// ----------------------------------
// ConcreteElement
// ----------------------------------
class TextElement implements WidgetElement {
String content;
TextElement(this.content);
@override
void accept(TranslationVisitor visitor) {
content = visitor.translateText(content);
}
}
// ----------------------------------
// Visitor
// ----------------------------------
abstract class TranslationVisitor {
String translateText(String text);
}
// ----------------------------------
// ConcreteVisitor
// ----------------------------------
class EnglishTranslationVisitor implements TranslationVisitor {
@override
String translateText(String text) {
if (text == "欢迎") {
return "Welcome";
}
return text;
}
}
class ChineseTranslationVisitor implements TranslationVisitor {
@override
String translateText(String text) {
if (text == "Welcome") {
return "欢迎";
}
return text;
}
}
// ----------------------------------
// Client
// ----------------------------------
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _languageCode = 'en';
final TextElement _textElement = TextElement('Welcome');
TranslationVisitor get _translator {
switch (_languageCode) {
case 'zh':
return ChineseTranslationVisitor();
default:
return EnglishTranslationVisitor();
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Visitor Pattern in Flutter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton<String>(
value: _languageCode,
items: const [
DropdownMenuItem(
child: Text('English'),
value: 'en',
),
DropdownMenuItem(
child: Text('中文'),
value: 'zh',
),
],
onChanged: (value) {
setState(() {
_languageCode = value!;
_textElement.accept(_translator);
});
},
),
const SizedBox(height: 20),
Text(
_textElement.content,
style: const TextStyle(fontSize: 24),
),
],
),
),
),
);
}
}
void main() => runApp(MyApp());
- Element(元素) :WidgetElement 定义了一个接受访问者的操作。
- ConcreteElement(具体元素) :TextElement实现了Element接口中定义的接受操作。
- Visitor(访问者) :TranslationVisitor定义了一个代表访问者的接口。
- ConcreteVisitor(具体访问者) :EnglishTranslationVisitor为Visitor接口实现每一个操作。两个具体的访问者:
EnglishTranslationVisitor
和ChineseTranslationVisitor
。它们都实现了TranslationVisitor
接口,并为TextElement
提供翻译。 - ObjectStructure(对象结构) :
_MyAppState
可以视为对象结构,因为它包含了可以被访问的元素(_textElement
)它还提供了一个高级的接口(DropdownButton
),允许用户选择访问者并应用它来翻译文本元素。
场景二:税务计算
在电子商务应用中,我们可能会有各种不同类型的商品,例如书籍、电子产品和食品。每种商品的税率可能会有所不同,取决于所在的国家或地区。
使用访问者模式,可以轻松地为各种商品计算税款,而无需更改商品的实现。当税率或税务规则发生变化时,只需添加或修改访问者即可。
abstract class Product {
double price;
Product(this.price);
double accept(TaxVisitor visitor);
}
class Book extends Product {
Book(double price) : super(price);
@override
double accept(TaxVisitor visitor) {
return visitor.calculateTaxForBook(this);
}
}
class Electronics extends Product {
Electronics(double price) : super(price);
@override
double accept(TaxVisitor visitor) {
return visitor.calculateTaxForElectronics(this);
}
}
class Food extends Product {
Food(double price) : super(price);
@override
double accept(TaxVisitor visitor) {
return visitor.calculateTaxForFood(this);
}
}
abstract class TaxVisitor {
double calculateTaxForBook(Book book);
double calculateTaxForElectronics(Electronics electronics);
double calculateTaxForFood(Food food);
}
class USTaxVisitor implements TaxVisitor {
static final USTaxVisitor _instance = USTaxVisitor._privateConstructor();
factory USTaxVisitor() {
return _instance;
}
USTaxVisitor._privateConstructor();
@override
double calculateTaxForBook(Book book) {
return book.price * 0.08; // 8% tax for books in US
}
@override
double calculateTaxForElectronics(Electronics electronics) {
return electronics.price * 0.15; // 15% tax for electronics in US
}
@override
double calculateTaxForFood(Food food) {
return 0; // No tax for food in US
}
}
class UKTaxVisitor implements TaxVisitor {
static final UKTaxVisitor _instance = UKTaxVisitor._privateConstructor();
factory UKTaxVisitor() {
return _instance;
}
UKTaxVisitor._privateConstructor();
@override
double calculateTaxForBook(Book book) {
return book.price * 0.05; // 5% tax for books in UK
}
@override
double calculateTaxForElectronics(Electronics electronics) {
return electronics.price * 0.20; // 20% tax for electronics in UK
}
@override
double calculateTaxForFood(Food food) {
return food.price * 0.05; // 5% tax for food in UK
}
}
class TaxApp extends StatefulWidget {
@override
_TaxAppState createState() => _TaxAppState();
}
class _TaxAppState extends State<TaxApp> {
TaxVisitor _taxVisitor = USTaxVisitor();
final products = [
Book(20),
Electronics(100),
Food(10),
];
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Visitor Pattern for Tax Calculation'),
),
body: ListView(
children: [
DropdownButton<TaxVisitor>(
value: _taxVisitor,
items: [
DropdownMenuItem(
child: const Text('US Tax'), value: USTaxVisitor()), // 使用单例
DropdownMenuItem(
child: const Text('UK Tax'), value: UKTaxVisitor()), // 使用单例
],
onChanged: (value) {
setState(() {
_taxVisitor = value!;
});
},
),
...products.map((product) {
return ListTile(
title: Text(product.runtimeType.toString()),
subtitle: Text(
'Tax: \$${product.accept(_taxVisitor).toStringAsFixed(2)}'),
);
}).toList(),
],
),
),
);
}
}
void main() => runApp(TaxApp());
1.Element(元素) :Product
是一个定义了接受访问者操作的抽象类。
2.ConcreteElement(具体元素) : 这些是实现了Product
(Element)的具体类,每个类都有一个accept
方法,允许访问者访问它。
3.Visitor(访问者) : TaxVisitor
是一个定义了对每种具体元素访问操作的接口。
4.ConcreteVisitor(具体访问者) : 这些类为TaxVisitor
接口实现了每个操作,分别代表不同的税务规则。
5.ObjectStructure(对象结构) : 是一个包含了可以被访问的元素集合的类。_TaxAppState
类实际上充当了这个角色,因为它持有了一个Product
(即Element
)的列表,并为这些产品应用了访问者来计算税款。
结论
设计模式在软件工程中被广泛采用,因为它们提供了经过验证的解决方案来解决常见的软件设计问题。其中,访问者模式是一种特别有用的设计模式,它允许我们为对象结构中的元素添加新的操作,而无需修改这些元素的类。这种模式在多语言支持和税务计算等领域特别有用。
多语言支持
软件需要支持多种语言。如何在不改变应用的主体结构的情况下,轻松地添加或切换语言?
如何利用访问者模式:
- Element:所有需要翻译的UI元素。
- ConcreteElement:特定的UI元素,例如文本控件或按钮。
- Visitor:定义了翻译的接口。
- ConcreteVisitor:为每种语言实现翻译,例如英语、中文等。
这种方法的优势在于,当需要添加新的语言时,只需增加一个新的访问者,而无需改变元素的结构。
税务计算
电商平台经常需要根据商品种类和买家所在地区来计算税费。如何在不改变商品类的前提下,灵活地为不同的税收政策计算税费?
如何利用访问者模式:
- Element:表示所有商品。
- ConcreteElement:具体的商品种类,如书籍、电子产品等。
- Visitor:定义了税收计算的接口。
- ConcreteVisitor:为不同的地区或税收策略实现税务计算,如美国税务或英国税务。
这种结构使得添加新的税务政策或更改现有政策变得非常简单。
结论
访问者模式提供了一种强大且灵活的方式,使得我们可以扩展应用的功能而无需修改现有的对象结构。无论是为应用添加多语言支持,还是为各种商品和地区计算税费,访问者模式都证明了其在实际开发中的价值。
希望对您有所帮助谢谢!!!