Flutter开发实战:享元模式 (Flyweight Pattern)

190 阅读7分钟

享元模式 (Flyweight Pattern) 是一种结构型设计模式,其主要目的是尽量减少对象的数量,以减少内存占用和提高性能。这是通过共享尽可能多的相似对象来实现的,从而使得系统只需要维护一个相对较小的对象实例集合。

享元模式常常用于需要大量对象的系统,其中这些对象部分信息是相同的。这种模式的核心是分离出变化的和不变的部分。不变的部分被共享,变化的部分由外部传入。

享元模式的主要组成部分:

  1. Flyweight(享元接口): 定义一个接口,通过这个接口 flyweight 对象可以接收并作用于外部状态。
  2. ConcreteFlyweight(具体享元类): 实现 Flyweight 接口,并为内部状态(共享的状态)增加存储空间。该对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于该对象在系统中的应用。
  3. UnsharedConcreteFlyweight(不可共享的享元类): 并非所有的 Flyweight 子类都需要被共享。Flyweight 接口使得共享成为可能,但它并不强制共享。
  4. FlyweightFactory(享元工厂): 创建并管理 flyweight 对象。确保合理地共享 flyweight。当用户请求一个 flyweight 时,FlyweightFactory 提供一个已创建的实例或创建一个(如果不存在的话)。
  5. Client(客户端): 维持对 flyweight 的引用。计算或存储 flyweight 的外部状态。

xyms.png

如何工作:

  • 系统首先检查是否有之前已创建的对象。
  • 如果有,系统返回这个对象,否则它创建一个新的对象。
  • 享元模式通过共享已存在的对象来避免创建新对象,从而节省内存和计算资源。

应用场景:

  1. 当一个应用程序使用了大量的对象。
  2. 对象的大部分状态都可以外部化。
  3. 如果没有了共享的对象,由于大量的相似对象,存储成本会很高。
  4. 一旦外部状态被移除,许多组对象可以被替换为相对较少的共享对象。

在 Flutter 中,享元模式并不是经常直接使用,因为 Flutter 框架本身已经在底层进行了大量的优化。但为了理解和演示,我们可以通过下面两个在实际开发中常见的场景来看看怎样使用享元模式 (Flyweight Pattern)

场景一

有一个滚动列表,展示了数千个图标。但实际上,这些图标只有几种不同的类型,每种类型的图标在视觉上是相同的。为了优化性能和内存使用,不希望为每一个图标都创建一个新的对象实例,而是想重复使用相同的对象。

import 'package:flutter/material.dart';

// 1. Flyweight
abstract class IconFlyweight {
  Widget buildIcon();
}

// 2. ConcreteFlyweight
class ConcreteIconFlyweight implements IconFlyweight {
  final IconData iconData;

  ConcreteIconFlyweight(this.iconData);

  @override
  Widget buildIcon() {
    return Icon(iconData);
  }
}

// 3. FlyweightFactory
class IconFlyweightFactory {
  final Map<String, IconFlyweight> _icons = {};

  IconFlyweight getIcon(String iconName) {
    if (!_icons.containsKey(iconName)) {
      switch (iconName) {
        case 'heart':
          _icons[iconName] = ConcreteIconFlyweight(Icons.favorite);
          break;
        case 'star':
          _icons[iconName] = ConcreteIconFlyweight(Icons.star);
          break;
        default:
          throw Exception('Unknown icon name');
      }
    }
    return _icons[iconName]!;
  }
}

// 5. Client
class IconListPage extends StatelessWidget {
  final IconFlyweightFactory _factory = IconFlyweightFactory();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flyweight Pattern in Flutter')),
      body: ListView.builder(
        itemCount: 1000,
        itemBuilder: (context, index) {
          final iconName = index % 2 == 0 ? 'heart' : 'star';
          final icon = _factory.getIcon(iconName);
          return ListTile(title: icon.buildIcon());
        },
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flyweight Pattern in Flutter',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: IconListPage(),
    );
  }
}

void main() => runApp(MyApp());
  • 定义了一个 IconFlyweight 抽象类来表示可共享的图标。
  • ConcreteIconFlyweight 是具体的享元,它实现了 IconFlyweight 并存储了具体的 IconData
  • IconFlyweightFactory 是享元工厂,它负责创建和管理 IconFlyweight 实例。
  • IconListPage 它使用享元工厂来获取图标并显示在列表中。

由于只有两种图标,所以 IconFlyweightFactory 只创建两个 ConcreteIconFlyweight 实例,即使在滚动列表中有数千个图标。这样,避免了不必要的对象创建和内存浪费。

场景二:

一个社交媒体应用的用户界面,其中有很多用户的头像。假设有一些用户没有上传自己的头像,所以系统为他们提供了默认的头像。因为默认的头像是相同的,可以使用享元模式来优化它。

需求

  • 在社交媒体应用中,每个用户都可以有一个头像。
  • 用户可以选择上传自己的头像,或者使用系统提供的默认头像。
  • 应用需要在多个地方显示这些头像(例如,消息列表、用户列表、评论等)。
  • 为了减少内存使用,我们希望重复使用默认头像的实例。

// 1. Flyweight
abstract class UserAvatarFlyweight {
  Widget buildAvatar();
}

// 2. ConcreteFlyweight
class DefaultAvatarFlyweight implements UserAvatarFlyweight {
  @override
  Widget buildAvatar() {
    return const CircleAvatar(
      child: Icon(Icons.person),
    );
  }
}

class CustomAvatarFlyweight implements UserAvatarFlyweight {
  final ImageProvider imageProvider;

  CustomAvatarFlyweight(this.imageProvider);

  @override
  Widget buildAvatar() {
    return CircleAvatar(backgroundImage: imageProvider);
  }
}

// 3. FlyweightFactory
class AvatarFlyweightFactory {
  final UserAvatarFlyweight _defaultAvatar = DefaultAvatarFlyweight();
  final Map<String, UserAvatarFlyweight> _customAvatars = {};

  UserAvatarFlyweight getAvatar(String? imageUrl) {
    if (imageUrl == null || imageUrl.isEmpty) {
      return _defaultAvatar;
    }

    if (!_customAvatars.containsKey(imageUrl)) {
      _customAvatars[imageUrl] = CustomAvatarFlyweight(NetworkImage(imageUrl));
    }

    return _customAvatars[imageUrl]!;
  }
}

// 4. Client
class UserListPage extends StatelessWidget {
  final AvatarFlyweightFactory _factory = AvatarFlyweightFactory();
  final List<String?> userImageUrls = [
    'https://xxxx.com/user1.jpg',
    null, // This user will have the default avatar
    'https://xxxx.com/user3.jpg',
    null, // Another user with the default avatar
    // ... add more users
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flyweight Pattern in Flutter'),
      ),
      body: ListView.builder(
        itemCount: userImageUrls.length,
        itemBuilder: (context, index) {
          final avatar = _factory.getAvatar(userImageUrls[index]);
          return ListTile(leading: avatar.buildAvatar());
        },
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flyweight Pattern in Flutter',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: UserListPage(),
    );
  }
}

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

  • 为用户头像定义了一个 UserAvatarFlyweight 抽象类。
  • 有两个具体的享元:DefaultAvatarFlyweight 用于默认头像,而 CustomAvatarFlyweight 用于用户上传的头像。
  • AvatarFlyweightFactory 是享元工厂,它管理头像的实例并提供了一个方法来获取头像。
  • UserListPage 展示了一个用户列表。对于没有上传头像的用户,它使用默认头像;对于上传了头像的用户,它使用用户的头像。

这种方法确保了默认头像只有一个实例,即使在用户列表中有很多没有上传头像的用户。此外,对于每个特定的上传头像,也只创建一个实例,这有助于减少内存使用并优化性能。

总结

在应用开发中,特别是在资源有限的移动设备上,优化内存和性能是至关重要的。享元模式作为一种结构型设计模式,旨在通过共享尽可能多的相似对象来实现这一目标。

通过两个具体的 Flutter 示例,探讨了如何应用此模式。

图标列表:

首先,我们考虑了一个包含大量图标的滚动列表。尽管列表中有数千个图标,但实际上只有几种不同的图标类型。通过享元模式,避免了为每个图标创建一个新的对象实例,从而大大减少了内存的使用。

社交媒体头像:

在一个社交媒体应用场景中,用户可以上传自己的头像或使用默认头像。为了优化性能,我们使用享元模式确保只为默认头像创建一个对象实例,并为每一个独特的用户头像创建一个对象实例。

核心组件:

在两个示例中,都遵循了享元模式的核心组件:

  • Flyweight:一个接口,定义了如何接收和处理外部状态。
  • ConcreteFlyweight:具体的享元实现,存储了内部状态。
  • FlyweightFactory:负责创建和管理享元对象,确保对象合理地共享。
  • Client:使用享元对象并存储其外部状态。

结论

享元模式在 Flutter 中提供了一个优雅的方法来优化对象的使用,特别是在有大量重复对象的场景中。通过共享对象而不是频繁地创建新对象,可以节省宝贵的内存资源并提高应用性能。当面临资源约束和性能挑战时,考虑使用享元模式可能是一个明智的选择。

希望对您有所帮助谢谢!!!