本文主要是介绍灵动的适配器模式 | Flutter 设计模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
设计模式系列的前两篇,分别向大家介绍了一种 创建性型模式 (单例模式) 和一种 行为型设计模式 (观察者模式),今天我们再来介绍一种结构型设计模式 —— 适配器模式。
适配器模式 (Adapter Design Pattern),顾名思义,这个模式就是用来做适配的,像一个「粘合剂」一样。
适配器模式可以将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类黏合在一起,最终使他们可以一起工作。
和 观察者模式 中的观察者与被观察者类似,适配器模式中担任主要角色是 适配器 (Adapter) 和 被适配者 (Adaptee)。一个比较典型的例子是,插座转接头可以被认为是一种适配器,可以把本身不兼容的接口,通过转接变得可以一起工作。
在代码世界中,也有很多接口不适配的场景,如我们引入了一个第三方库后,发现它其中的类实现与我们现有代码并不兼容,需要一个 Adapter 类做一层转换才行。另外,相较于直接接触原始的代码实现,这种模式下,客户端仅仅依赖适配器类,对于代码复用和维护性也多了一层保障。
类适配器与对象适配器
适配器模式有两种实现方式:类适配器 和 对象适配器。其中,类适配器使用继承关系来实现,而对象适配器使用组合关系来实现。具体的代码实现如下所示。
/// 被适配者
class Adaptee {String concreteOperator() {return 'Adaptee';}
}abstract class ITarget {String operator();
}/// 对象适配器
class ObjectAdapter implements ITarget {var adaptee = Adaptee();String operator() {return adaptee.concreteOperator();}
}/// 类适配器
class ClassAdapter extends Adaptee {String operator() {return super.concreteOperator();}
}
ITarget
表示要转化成的接口,是一个规范化的接口定义。Dart 本身不支持关键词 interface
,因此我们可以创建一个没有默认实现的抽象类代替。
需要被适配的 Adaptee
表示一组不兼容 ITarget
接口定义的类或接口,ObjectAdapter
和 ClassAdapter
两种适配器分别用不同的方式将 Adaptee
转化成了符合 ITarget
接口定义的接口。而在客户端使用时只需要依赖 ITarget
即可完成对 Adaptee
的适配。
class Client {Client(this.adapter);final ITarget adapter;operator() {var result = adapter.operator();assert(result == 'Adaptee');}
}
关于类适配器与对象适配器:
如果希望你一个适配器可以同时适配多个不同的类,则单继承机制的 Dart 语言无法使用 类适配器 实现这种一对多的适配器。
如果
Adaptee
接口很多,而且Adaptee
和ITarget
接口定义大部分都相同,那我们推荐使用类适配器,因为 可以充分将继承的代码复用作用利用起来。大部分场景下,我们推荐使用 对象适配器 的方式实现适配器模式,因为 继承 在很多情况下容易被 滥用 并造成 层级过多 的现象,而 组合 更加灵活。
实现
在代码应用中,适配器模式典型的例子是 Android 中的 ListView,在 Android 中,ListView 作为一个展示列表的 UI 组件,它的主要作用是将用户交给它的 Item View 以列表形式展示出来,然而描述 View 的形式却多种多样,可以是 Android 中 XML 布局,也可以是以 Java 代码中自定义 View 的形式提供,甚至可以是自定义的一套规则,实现自己的 UI 描述语言。
本身,XML 或者其他描述语言对于 ListView 是不可知的,所以,在 ListView 和它们之前介入一个适配器就可以有效的解决这个问题,适配器的作用就是将这些形式转换成 Item View 以适应 Listview。
在 Flutter 中,这种形式的模式很容易实现,例如,我们想自定义一个可以展示蔬果列表的组件 VeggieList
:
class VeggieList extends StatefulWidget {@override_VeggieListState createState() => _VeggieListState();
}class _VeggieListState extends State<VeggieList> {final List<Veggie> veggies = [];@overrideWidget build(BuildContext context) {return veggies.isEmpty? Text('无水果'): Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[for (var veggie in veggies)ListTile(title: Text('${veggie.name}')))],);}
}
这个组件主要关心的是 veggies
,一个提供一组 Veggie
对象的数组。
veggies
数据源并不统一,可能来自多个不同的接口,可能在云端,也可能是本地的假数据,并且不同的接口提供的数据格式也可能不相同,可能是 xml 或者是 json。
/// 返回 json 数据格式的接口
class JsonVeggiesApi {final String _veggiesJson = '''{"veggies": [{"name": "apple (JSON)",},{"name": "banana (JSON)",},]}''';String getVeggiesJson() {return _veggiesJson;}
}/// 返回 xml 数据格式的接口
class XmlVeggiesApi {final String _contactsXml = '''<?xml version="1.0"?><veggies><veggie><name>apple (XML)</name></veggie><veggie><name>banana (XML)</name></veggie></veggies>''';String getVeggiesXml() {return _contactsXml;}
}
这些接口显然不能直接应用在 VeggieList
中展示,因此,需要做一些适配工作,适配的目的就是将这些数据转换成 Veggie
对象的数组,因此我们可以定义如下这个接口:
abstract class IVeggiesAdapter {List<Veggie> getVeggies();
}
其中的 getVeggies
方法返回的就是 VeggieList
组件需要的 Veggie
对象数组。
创建适配器时,只需要实现这个接口,然后组合目标需要被适配的类做接口转换即可,例如下面的 JsonnVeggiesAdapter
,专门负责将 JsonVeggiesApi
转换为兼容 VeggieList
的适配器:
class JsonnVeggiesAdapter implements IVeggiesAdapter {final JsonVeggiesApi _api = JsonVeggiesApi();@overrideList<Veggie> getVeggies() {final veggiesJson = _api.getVeggiesJson();final veggiesList = _parseContactsJson(veggiesJson);return veggiesList;}List<Veggie> _parseContactsJson(String contactsJson) {final contactsMap = json.decode(contactsJson) as Map<String, dynamic>;final contactsJsonList = contactsMap['contacts'] as List;final contactsList = contactsJsonList.map((json) {final contactJson = json as Map<String, dynamic>;return Veggie(name: contactJson['name'] as String,);}).toList();return contactsList;}
}
最终,在使用到 VeggieList
时,注入 JsonnVeggiesAdapter
这个适配器就可以将原本不兼容的 JsonVeggiesApi
中的数据展示出来了:
class AdapterExample extends StatelessWidget {const AdapterExample();@overrideWidget build(BuildContext context) {return ScrollConfiguration(behavior: const ScrollBehavior(),child: SingleChildScrollView(child: VeggieList(adapter: JsonnVeggiesAdapter(),),),);}
}/// 最终的 VeggieList
class VeggieList extends StatefulWidget {final IVeggiesAdapter adapter;const VeggieList({@required this.adapter,});@override_VeggieListState createState() => _VeggieListState();
}class _VeggieListState extends State<VeggieList> {final List<Veggie> veggies = [];void _getVeggies() {setState(() {veggies.addAll(widget.adapter.getVeggies());});}@overrideWidget build(BuildContext context) {return veggies.isEmpty? Text('无水果',): Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[for (var veggie in veggies)ListTile(title: Text('${veggie.name}')))],);}
}
同理,不同接口来源的数据都可以通过适配器实现 IVeggiesAdapter
接口与 VeggieList
做兼容。
Flutter
在应用中,我们经常会使用到 CustomScrollView
创建拥有自定义滚动效果的组件,而 CustomScrollView
只允许包含 sliver
系列组件 (SliverAppBar
、SliverList
、SliverPersistentHeader
等) ,如果想包含普通的组件,必然需要使用 SliverToBoxAdapter
:
return MaterialApp(home: CustomScrollView(controller: scrollController,slivers: <Widget>[SliverAppBar(),SliverToBoxAdapter(child: Container(height: 100.0,),),],),
);
这里,将 Container
放入 SliverToBoxAdapter
中便可以在 CustomScrollView
展示出来了。
我们认为普通的 widget 是不兼容 CustomScrollView
的,SliverToBoxAdapter
在其中就扮演了适配器的角色。它使用 类适配器 的方式,将 SingleChildRenderObjectWidget
中 createRenderObject
接口重写转换成可以包含 RenderBox
(对应一般 widget 的 RenderObject
) 的 RenderSliver
(对应 sliver 系列 widget 的 RenderObject
),即这里的 RenderSliverToBoxAdapter
:
class SliverToBoxAdapter extends SingleChildRenderObjectWidget {/// Creates a sliver that contains a single box widget.const SliverToBoxAdapter({Key? key,Widget? child,}) : super(key: key, child: child);@overrideRenderSliverToBoxAdapter createRenderObject(BuildContext context) => RenderSliverToBoxAdapter();
}
拓展阅读
适配器模式
https://refactoringguru.cn/design-patterns/adapter组合优于继承
https://time.geekbang.org/column/article/169593Flutter Sliver
https://juejin.cn/post/6844903901720739848
关于本系列文章
Flutter / Dart 设计模式从南到北 (简称 Flutter 设计模式) 系列内容由 CFUG 社区成员、《Flutter 开发之旅从南到北》作者、小米工程师杨加康撰写并发布在 Flutter 社区公众号和 flutter.cn 网站的社区教程栏目。
本系列内容旨在推进 Flutter / Dart 语言特性的普及,帮助开发者更高效地开发出高质量、可维护的 Flutter 应用。如果你对本文还有任何疑问或者文章的建议,欢迎向中文社区官方 GitHub 仓库 (cfug/flutter.cn) 提交 Issue 或者直接与我联系 (yangjiakay@gmail.com)。
这篇关于灵动的适配器模式 | Flutter 设计模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!