《Effective Ruby:改善Ruby程序的48条建议》一第15条:优先使用实例变量而非类变量

第15条:优先使用实例变量而非类变量

Ruby语言存在两种用@标识的变量:实例变量和类变量。每个运行中的Ruby程序都有自己的私有实例变量集,如你所知,它们的名字都以“@”符号开头。修改对象的实例变量不会影响另一个对象的同名实例变量。(但是改变变量就是另外一回事儿了;如果两个对象共享第三个对象的引用,那么修改自然会影响到另一个了。详见第16条)。
类变量(那些以“@@”开头的变量)则被区别对待。不同于绑定在单个上对象,类变量被绑定到一个类型之上,并且它们对于那个类的所有的实例都是可见的。设置一个对象的类变量会立即反映到这个类的其他对象上。当然,这也没什么值得惊奇的,大多数面向对象编程语言都具备这种类范围内共享变量的能力。不过类变量和继承体系的交互方式可能是会让你感到惊讶。
类变量通常的用途是(当然不是唯一的用途)实现单例模式(不要和Ruby单例类相混淆)。某些时候在大型的应用中你只希望某个类存在一个实例,并让所有的代码去访问这个唯一的实例。比如配置类。你可能希望一次性解析配置文件,然后在整个应用中共享信息,而不是通过变量在每个需要它的方法中传来传去。基本上,单例模式就是用来解决这种神圣的全局变量问题的。
暂且抛开并发性,让我们编写一个实现了单例模式的简单的类,并通过继承该类实现其他的类:

通过单例类型的定义可以知道,它们不能存在一个以上的实例,所以典型的做法是使new方法私有化。这就阻止了任何想从外部创建新对象的可能。我们还要对dup和clone方法做同样的处理以保证不能对唯一的实例进行克隆。访问这个正式对象的唯一方法是通过那个名为instance的类方法。它的职责是确保new方法只被调用一次,这是通过将其存在名为@@single的类变量中来实现的。如果变量@@single的值为nil,那么instance方法就会调用new方法创建一个新的实例并将其存在@@single类变量中。否则,它只是返回类变量@@single当前的值。让我们创建两个继承自类Singleton的类,从而使它们可以使用相同的逻辑:

目前看来还不错。一个使用了这些类的应用可以在其代码的任何地方访问唯一的配置文件对象和唯一的数据库连接对象。但是如果这样就万事大吉,那我也不会额外地写这么多代码了,让我们看看真正使用这些类的时候会发生什么吧:

糟糕!发生什么了?显然,两个类共享了同样的类变量。当Conf?iguration::instance被调用的时候@@single变量的值还是nil,因此调用了new方法并将结果对象进行了缓存。而当Database::instance被调用的时候类变量@@single因为已经有值(配置类的对象)了就没有再调用new方法创建新对象而是直接将其返回。解决这个问题的方法虽然很简单,但还是需要解释一下。
超类中的类变量会被所有子类共享。有点混乱的是,这些类的任何实例变量都可以访问并修改这些共享类变量。实例方法和类方法共享同样的类变量,使它们变得像全局变量那样不可取。所以实例应该坚持使用实例变量。当然这里其实是有解决方案的。类也是对象,所以它们也有实例变量。每个类都是一个唯一的对象并持有自身的私有实例变量集合。将变量@@single从一个类变量改为一个类的实例变量就可以解决这个
问题:

如果你之前没有见过类方法中的实例变量,这看上去有些奇怪。同时也很容易把一个对象的实例变量与之相混淆。但是它们确实属于其自身的类。如果你记得类方法其实是一个假象的话就容易理解了。因为类也是对象,我们所称的“类方法”实际上就是类对象的实例方法。类方法仅仅是从名字上来说的。
除了打破了类和子类的共享关系以外,类实例变量同时也提供了更多的封装特性。尽管类变量(@@)可以直接在实例或类方法中被访问或修改,类实例变量(“@”)却只能在类定义级别或者类方法中被访问。所以,类的实例或外部的代码只有在你为它们特别定义了类方法(比如说Singleton::instance)的情况下才能访问。你应该像保护其他任何实例变量一般保护它们的访问权限,只在必要的时候提供访问权限。
顺便说一句,之前我说先把并发相关的问题放一放,那是因为类变量以及类的实例变量和全局变量一样有着相同的问题。如果你的程序存在多线程控制,那么在不使用互斥锁的情况下改变任何变量都是不安全的。还好,Singleton模块作为Ruby标准库的一部分,正确地以线程安全的方式实现了单例模式。使用它们很简单:

要点回顾
优先使用实例变量而非类变量。
类也是对象,所以它们拥有自己的私有实例变量集合。

时间: 2024-10-26 05:44:14

《Effective Ruby:改善Ruby程序的48条建议》一第15条:优先使用实例变量而非类变量的相关文章

编写高质量代码改善C#程序的157个建议[IEnumerable<T>和IQueryable<T>、LINQ避免迭代、LINQ替代迭代]

原文:编写高质量代码改善C#程序的157个建议[IEnumerable<T>和IQueryable<T>.LINQ避免迭代.LINQ替代迭代] 前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议29.区别LINQ查询中的IEnumerable<T>和IQueryable<T> 建议30.使用LINQ取代集合中的比较器和迭代器 建议31.在LINQ查询中避免不必要的迭代

编写高质量代码改善C#程序的157个建议[为泛型指定初始值、使用委托声明、使用Lambda替代方法和匿名方法]

原文:编写高质量代码改善C#程序的157个建议[为泛型指定初始值.使用委托声明.使用Lambda替代方法和匿名方法] 前言 泛型并不是C#语言一开始就带有的特性,而是在FCL2.0之后实现的新功能.基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用.同时,它减少了泛型类及泛型方法中的转型,确保了类型安全.委托本身是一种引用类型,它保存的也是托管堆中对象的引用,只不过这个引用比较特殊,它是对方法的引用.事件本身也是委托,它是委托组,C#中提供了关键字event来对事件进行特别区分.一旦我们

编写高质量代码改善C#程序的157个建议[用抛异常替代返回错误、不要在不恰当的场合下引发异常、重新引发异常时使用inner Exception]

原文:编写高质量代码改善C#程序的157个建议[用抛异常替代返回错误.不要在不恰当的场合下引发异常.重新引发异常时使用inner Exception] 前言 自从.NET出现后,关于CLR异常机制的讨论就几乎从未停止过.迄今为止,CLR异常机制让人关注最多的一点就是"效率"问题.其实,这里存在认识上的误区,因为正常控制流程下的代码运行并不会出现问题,只有引发异常时才会带来效率问题.基于这一点,很多开发者已经达成共识:不应将异常机制用于正常控制流中.达成的另一个共识是:CLR异常机制带来

编写高质量代码改善C#程序的157个建议[4-9]

原文:编写高质量代码改善C#程序的157个建议[4-9] 前言 本文首先亦同步到http://www.cnblogs.com/aehyok/p/3624579.html.本文主要来学习记录一下内容: 建议4.TryParse比Parse好 建议5.使用int?来确保值类型也可以为null 建议6.区别readonly和const的使用方法 建议7.将0值设为枚举的默认值 建议8.避免给枚举类型的元素提供显式的值 建议9.习惯重载运算符 建议4.TryParse比Parse好 如果注意观察,除st

编写高质量代码改善C#程序的157个建议[C#闭包的陷阱、委托、事件、事件模型]

原文:编写高质量代码改善C#程序的157个建议[C#闭包的陷阱.委托.事件.事件模型] 前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议38.小心闭包中的陷阱 建议39.了解委托的实质 建议40.使用event关键字对委托施加保护 建议41.实现标准的事件模型 建议38.小心闭包中的陷阱 首先我们先来看一段代码: class Program { static void Main(string[] arg

编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

原文:编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串.实现浅拷贝和深拷贝.用dynamic来优化反射] 前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议13.为类型输出格式化字符串 建议14.正确实现浅拷贝和深拷贝 建议15.使用dynamic来简化反射实现 建议13.为类型输出格式化字符串   有两种方法可以为类型提供格式化的字符串输出. 一种是意识到类型会产生格式化字符串输出,于是

编写高质量代码改善C#程序的157个建议[匿名类型、Lambda、延迟求值和主动求值]

原文:编写高质量代码改善C#程序的157个建议[匿名类型.Lambda.延迟求值和主动求值] 前言 从.NET3.0开始,C#开始一直支持一个新特性:匿名类型.匿名类型由var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性: 1.既支持简单类型也支持复杂类型.简单类型必须是一个非空初始值,复杂类型则是一个以new开头的初始化项. 2.匿名类型的属性是只读的,没有属性设置器,它一旦倍初始化就不可更改. 3.如果两个匿名类型的属性值相同,那么就任务这两个匿名类型

编写高质量代码改善C#程序的157个建议[动态数组、循环遍历、对象集合初始化]

原文:编写高质量代码改善C#程序的157个建议[动态数组.循环遍历.对象集合初始化] 前言   软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类.不管是数组还是集合类,它们都有各自的优缺点.如何使用好集合是我们在开发过程中必须掌握的技巧.不要小看这些技巧,一旦在开发中使用了错误的集合或针对集合的方法,应用程序将会背离你的预想而运行. 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议16.

编写高质量代码改善C#程序的157个建议[10-12]

原文:编写高质量代码改善C#程序的157个建议[10-12] 前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议10.创建对象时需要考虑是否实现比较器 建议11.区别对待==和Equals 建议12.重写Equals时也要重写GetHashCode 建议10.创建对象时需要考虑是否实现比较器 有对象的地方就会存在比较,就像小时候每次拿着考卷回家,妈妈都会问你隔壁的那谁谁谁考了多少分呀.下面我们也来举个简单