目录
一.专栏简介
本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。
本章将开始**蝇量模式(Flyweight Pattern)**的学习。
二.蝇量模式概念
当某个类的一个实例可以用于提供许多虚拟实例时,使用蝇量模式。
下面我们举一个反例,然后用蝇量模式改进。
三.不使用蝇量模式的反例
假设我们要在一个田园游戏中放置非常多的树,那么我们容易想到直接实例化一堆树,然后根据树的位置和年龄(推断大小)绘制在界面上。这里我们就不实际绘制了,用标准输出代替。代码如下:
Flyweight.h:
cpp
#pragma once
#include <iostream>
using namespace std;
class Tree
{
public:
Tree(int x, int y, size_t age):
_x(x), _y(y), _age(age)
{}
// 强制访问每个 Tree 对象的成员,让操作系统把虚拟内存映射到物理内存,从而产生实际的内存开销,便于观察
void display()
{
cout << "树显示在(" << _x << "," << _y << ")处,年龄为:" << _age << "岁!" << endl;
}
private:
int _x;
int _y;
size_t _age;
};
main.cpp:
cpp
#include "Flyweight.h"
#include <vector>
#include <windows.h>
const int TREECOUNT = 100000000;
int main()
{
vector<Tree*> trees;
for (int i = 0;i < TREECOUNT;++i)
{
Tree* tree = new Tree(i, i + 1, i + 2);
trees.push_back(tree);
}
for (const auto& e : trees)
e->display();
Sleep(60000);
return 0;
}
代码很简单,就是一个Tree类,我们new很多很多个Tree,然后调用它们的display()函数进行打印输出。这里调用display()函数的操作非常关键,因为访问成员变量会造成缺页中断,操作系统将虚拟内存映射到物理内存,从而造成实际的内存开销,便于我们观察。
运行之前我电脑的内存占用:

仅百分之48。
运行之后:

内存占用高达百分之95,内存差点爆掉。
四.蝇量模式优化
直接上代码,代码如下:
FlyWeight.h:
cpp
#pragma once
#include <iostream>
#include <vector>
using namespace std;
class TreeManager
{
public:
void addTree(int x, int y, size_t age)
{
treePositions.push_back(make_pair(x, y));
ages.push_back(age);
}
void displayTrees()
{
for (int i = 0;i < treePositions.size(); ++i)
{
Tree::display(treePositions[i].first, treePositions[i].second, ages[i]);
}
}
private:
vector<pair<int, int>> treePositions; // 所有树的位置
vector<size_t> ages; // 所有树的年龄
};
main.cpp:
cpp
#include "Flyweight.h"
#include <windows.h>
const int TREECOUNT = 100000000;
int main()
{
TreeManager treeManager;
for (int i = 0;i < TREECOUNT;++i)
{
treeManager.addTree(i, i + 1, i + 2);
}
treeManager.displayTrees();
Sleep(60000);
return 0;
}
这里我用一个TreeManager存储了所有树的状态,然后Tree类里的display()函数直接变成静态的,最终在main()函数里一个Tree对象都没有实例化,只是使用treeManager这一个对象,这个对象在displayTrees()中读取所有树的属性,调用静态的Tree::display()函数进行显示(实际上是打印)。
运行结果:

同样是显示一亿棵树,这里我的内存占用骤降至百分之58,对比之前的百分之95真是一个巨大的优化!
五.蝇量模式优点
- 减少运行时对象实例的数目,节省内存。
- 把许多"虚拟"对象的状态集中放进一个地方。
六.蝇量的用途和优点
- 用途:当一个类有许多实例,而这些实例能够用一致的方法控制时,用蝇量。
- 缺点:一旦你实现了蝇量模式,那么类的单个逻辑实例,将无法拥有和其他实例不同的独立行为。也就是说牺牲了 "实例行为的独立性"。