被遗忘的设计模式——1.不变模式(Immutable)
本系列目录
代码示例下载
不变模式,这是一个非常基础的模式。
从对象的健壮性来思考:
这些对象共享相同对象的引用,为此,在对象构造好之后,不允许改变共享对象的内容。这就是不变模式,在程序中使用它的地方越合适,程序将越健壮,可维护性就越强。
从并发的角度来思考:
不变模式解决的是如何处理共享对象的问题。当多个对象使用或修改同一个类实例——也就是共享对象的时候,往往会有同步问题;而在多线程异步调用的情况下,则更难以控制这个共享对象的状态。
一种解决办法是,使用了Lock的机制来锁住对象,强制进行同步,但是会增加额外的开销。所以,能避免尽量避免使用Lock。故而,有另一种解决方案:即新建一个有着不同内容的对象,来替换原对象,其中,旧对象中未改变的值会复制到新对象中,而改变的值会使用新值——这种实现方式就称为不变模式。
Flyweight模式一般使用不变模式来实现享元,参见我的《我也设计模式》的Flyweight一章。
关键是这个基础类的设计,让我们考察一下复数类Plural的加法:
注意到,x和y字段都是私有且只读的,不对外暴露,它们相应的属性也是只读的:
我们只能在构造函数中初始化这两个字段,以后不能再进行修改——这也是“不变”一词的由来:
需要指出的是,对于我们这个复数类的加减乘除运算,一般使用运算符重载的技术,其实,也可以使用不变模式来实现,逻辑是:每个复数实例都是不可改变的,在加减乘除运算方法中,创建新的实例,并在其构造函数中设定新的参数,如下面的Add方法:
对于基础类Plural,确实没有暴露他自身的属性,但是,他的子类却有可能将其暴露出来,所以,一般要把这个基础类设为不可派生的:
不变模式在.NET中最广泛的使用就是String类的实现。我们知道,字符串是不可改变的。对于以下操作:
无论是ToUpper还是Substring等函数,都是新建了一个字符串,并重新将该字符串的引用添加到原来的变量s上。另外运算符重载+=,也是这么实现的。
几点注意:
1.构造函数是不需要同步控制的。对CLR认识深刻的人,会认为这是一句废话。毕竟,new还没做好呢,又怎么能Lock一个对象,hoho。
2.仔细观察上面的Add方法:
P.x为什么可以使用呢?x不是P对象的一个私有成员么?至少一开始我是这么认为的,但是编译期和runtime却运行良好。
OK,在Position类外部,确实是访问不了Position对象的x私有字段,为此需要公开其只读属性;但是,就在Position类内部,p作为Add方法的一个参数,却可以访问它自身的这个私有对象。重温private的定义:完全私有的,只有当前类中的成员能访问到。以上是我暂时能想到的一些思路,希望大家能给我更合理的解释。
下一篇:过滤器模式(Filter)
代码示例下载
不变模式,这是一个非常基础的模式。
从对象的健壮性来思考:
这些对象共享相同对象的引用,为此,在对象构造好之后,不允许改变共享对象的内容。这就是不变模式,在程序中使用它的地方越合适,程序将越健壮,可维护性就越强。
从并发的角度来思考:
不变模式解决的是如何处理共享对象的问题。当多个对象使用或修改同一个类实例——也就是共享对象的时候,往往会有同步问题;而在多线程异步调用的情况下,则更难以控制这个共享对象的状态。
一种解决办法是,使用了Lock的机制来锁住对象,强制进行同步,但是会增加额外的开销。所以,能避免尽量避免使用Lock。故而,有另一种解决方案:即新建一个有着不同内容的对象,来替换原对象,其中,旧对象中未改变的值会复制到新对象中,而改变的值会使用新值——这种实现方式就称为不变模式。
Flyweight模式一般使用不变模式来实现享元,参见我的《我也设计模式》的Flyweight一章。
关键是这个基础类的设计,让我们考察一下复数类Plural的加法:
注意到,x和y字段都是私有且只读的,不对外暴露,它们相应的属性也是只读的:
private readonly double x;
private readonly double y;
public double X
{
get { return x; }
}
public double Y
{
get { return y; }
}
private readonly double y;
public double X
{
get { return x; }
}
public double Y
{
get { return y; }
}
我们只能在构造函数中初始化这两个字段,以后不能再进行修改——这也是“不变”一词的由来:
public Plural(double x, double y)
{
this.x = x;
this.y = y;
}
{
this.x = x;
this.y = y;
}
需要指出的是,对于我们这个复数类的加减乘除运算,一般使用运算符重载的技术,其实,也可以使用不变模式来实现,逻辑是:每个复数实例都是不可改变的,在加减乘除运算方法中,创建新的实例,并在其构造函数中设定新的参数,如下面的Add方法:
public Plural Add(Plural P)
{
return new Plural(this.x + P.x, this.y + P.y);
}
{
return new Plural(this.x + P.x, this.y + P.y);
}
对于基础类Plural,确实没有暴露他自身的属性,但是,他的子类却有可能将其暴露出来,所以,一般要把这个基础类设为不可派生的:
public sealed class Plural
不变模式在.NET中最广泛的使用就是String类的实现。我们知道,字符串是不可改变的。对于以下操作:
String s = "Jax";
s += " Bao";
s = s.Substring(3);
s += " Bao";
s = s.Substring(3);
无论是ToUpper还是Substring等函数,都是新建了一个字符串,并重新将该字符串的引用添加到原来的变量s上。另外运算符重载+=,也是这么实现的。
几点注意:
1.构造函数是不需要同步控制的。对CLR认识深刻的人,会认为这是一句废话。毕竟,new还没做好呢,又怎么能Lock一个对象,hoho。
2.仔细观察上面的Add方法:
P.x为什么可以使用呢?x不是P对象的一个私有成员么?至少一开始我是这么认为的,但是编译期和runtime却运行良好。
OK,在Position类外部,确实是访问不了Position对象的x私有字段,为此需要公开其只读属性;但是,就在Position类内部,p作为Add方法的一个参数,却可以访问它自身的这个私有对象。重温private的定义:完全私有的,只有当前类中的成员能访问到。以上是我暂时能想到的一些思路,希望大家能给我更合理的解释。
下一篇:过滤器模式(Filter)