简易引用计数器

思考并回答以下问题:

  • 为什么管理类都使用单例模式?

引用计数使用场景

有一间黑色的屋子,里边有一盏灯。当第一个人进屋的时候灯会打开,之后的人进来则不用再次打开了,因为已经开过了。当屋子里的所有人离开的时候,灯则会关闭。

我们先定义灯的对象模型:

1
2
3
4
5
6
7
8
9
10
11
12
class Light
{
public void Open()
{
Log.I("灯打开了");
}

public void Close()
{
Log.I("灯关闭了");
}
}

很简单就是两个方法而已。

再定义屋子的类,屋子应该持有一个Light的对象,并且要记录人们的进出。当有人进入,进入后当前房间只有一个人的时候,要把灯打开。当最后一个人离开的时候灯要关闭。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Room
{
private Light mLight = new Light();

private int mPeopleCount = 0;

public void EnterPeople()
{
if (mPeopleCount == 0)
{
mLight.Open();
}

++mPeopleCount;

Log.I("一个人走进房间,房间里当前有{0}个人",mPeopleCount);
}

public void LeavePeople()
{
--mPeopleCount;

if (mPeopleCount == 0)
{
mLight.Close();
}

Log.I("一个人走出房间,房间里当前有{0}个人", mPeopleCount);
}
}

很简单,我们来看下测试代码。

1
2
3
4
5
6
7
8
9
10
var room = new Room();
room.EnterPeople();
room.EnterPeople();
room.EnterPeople();

room.LeavePeople();
room.LeavePeople();
room.LeavePeople();

room.EnterPeople();

看下输出的结果:

1
2
3
4
5
6
7
8
9
10
灯打开了
一个人走进房间,房间里当前有1个人
一个人走进房间,房间里当前有2个人
一个人走进房间,房间里当前有3个人
一个人走出房间,房间里当前有2个人
一个人走出房间,房间里当前有1个人
一个人走出房间,房间里当前有0个人
灯关闭了
灯打开了
一个人走进房间,房间里当前有1个人

OK。以上就是引用计数这项计数的使用场景的所有代码。测试的代码比较整齐,很容易算出来当前有多少个人在屋子里,所以看不出来引用计数的作用。但是在日常开发中,我们可能会把EnterPeople和LeavePeople散落在工程的各个位置。这样就很难统计,这时候引用计数的作用就很明显了,它可以自动帮助你判断什么时候进行开灯关灯操作,而你不用写开关灯的一行代码。

这个例子比较接近生活,假如笔者再换个例子,我们把Light对象换成资源对象,其开灯对应加载资源,关灯对应卸载资源。而屋子则是对应资源管理器,EnterPeople对应申请资源对象,LeavePeople对应归还资源对象。这样你只管在各个界面中申请各个资源,只要在界面关闭的时候归还各个资源对象就可以不用关心资源的加载和卸载了,可以减轻大脑的负荷。

简易计数器实现

计数器的接口很简单,代码如下:

1
2
3
4
5
6
7
public interface IRefCounter
{
int RefCount { get; }

void Retain(object refOwner = null);
void Release(object refOwner = null);
}

Retain是增加引用计数(RefCount+1),Release是减去一个引用计数(RefCount-1)。在接下来的具体实现中,当RefCount降为0时我们需要捕获一个事件,这个事件叫OnZeroRef。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SimpleRC : IRefCounter
{
public SimpleRC()
{
RefCount = 0;
}

public int RefCount { get; private set; }

public void Retain(object refOwner = null)
{
++RefCount;
}

public void Release(object refOwner = null)
{
--RefCount;
if (RefCount == 0)
{
OnZeroRef();
}
}

protected virtual void OnZeroRef()
{
}
}

以上就是简易引用计数器的所有实现了。

接下来我们用这个引用计数器,重构灯的使用场景的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Light
{
public void Open()
{
Log.I("灯打开了");
}

public void Close()
{
Log.I("灯关闭了");
}
}

class Room : SimpleRC
{
private Light mLight = new Light();

public void EnterPeople()
{
if (RefCount == 0)
{
mLight.Open();
}

Retain();

Log.I("一个人走进房间,房间里当前有{0}个人",RefCount);
}

public void LeavePeople()
{
// 当前还没走出,所以输出的时候先减1
Log.I("一个人走出房间,房间里当前有{0}个人", RefCount - 1);

// 这里才真正的走出了
Release();
}

protected override void OnZeroRef()
{
mLight.Close();
}
}

测试代码和之前的一样,我们看下测试结果:

1
2
3
4
5
6
7
8
9
10
灯打开了
一个人走进房间,房间里当前有1个人
一个人走进房间,房间里当前有2个人
一个人走进房间,房间里当前有3个人
一个人走出房间,房间里当前有2个人
一个人走出房间,房间里当前有1个人
一个人走出房间,房间里当前有0个人
灯关闭了
灯打开了
一个人走进房间,房间里当前有1个人

0%