类是Ruby作为面向对象语言的特征之一,模块又是Ruby的特色花样之一,就来聊聊这两方面内容吧
一 类 (class)
类是面向对象中,定义对象的数据与行为的结构
类就像是对象的雏形或设计图,决定了对象的行为
<1> 常见类方法
下面介绍几个常用的方法:
-
类方法: new方法
既然对象是类的实例,那么,类便提供了,创建对象的方法,即为new方法
同时,数值类,字符串类,数组可以使用字面量来进行初始化
a = Array.new b = [1,2,"asa"] c = 123 d = hash.new p d #=> {} p a #=> []
-
实例方法: class方法
既然每个对象都是类的实例,那么如何判断改对象的类,使用class方法,可以判断出对象的类
array = [ ] hash = { } p hash.class #=> "Hash" p array.class #=> "Array"
-
实例方法: instance_of?
用于判断某个对象是否属于某方法,和以前的约定一样返回值为真假的以”?”结尾
array = [] p array.instance_of?(Array) #=> true p array.instance_of?(String) #=> false
<2> 继承
继承是类中很重要一个概念
继承 : 通过扩展已定义的类来创建新的类
那么,继承有什么作用呢?
-
再不影响原有类功能的前提下,追加新的功能
-
可以重定义父类中的功能,使名称相同的方法产生不同的结果
-
在已有的功能上,追加新的处理,扩展已有功能
新创建的类叫子类(subclass),被继承的类叫父类(superclass)
注意: Ruby不支持父类的多重继承,即一个类只能继承一次,只能有一个父类,Ruby保证了单一继承关系
那么,有时候就是需要多个类的功能该怎么办,后面自有办法,使用模块(module)
Basicobject类shiRuby中最最基础的类,仅仅包含了作为一个类最低限度的方法
所以,Ruby中一般默认的继承类是Object类,我们可以看看这两个类中方法的比较
p BasicObject.instance_methods
#=> [:!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]
p Object.instance_methods
#=> [:remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :public_send, :instance_variable_defined?, :singleton_method, :instance_variable_set, :method, :public_method, :extend, :define_singleton_method, :to_enum, :enum_for, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :display, :to_s, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variable_get, :instance_variables, :!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]
而我们一般创建类时,默认的继承类是Object类,所以,这个类就不能再继承其他类了,
违背单一继承的规则,会抛出异常
下面是一张Ruby中常见类的继承关系图
现在,再介绍一种方法,用来辨别继承关系
4.is_a?方法
is_a? 方法用来辨别接收者是否是参数的子类
p Array.is_a?(Object) #=> true
p Array.is_a?(String) #=> false
p Array.is_a?(Array) #=> false
a = []
p a.is_a?(Array) #=> true
<3> 类的创建
上面说了那么多,类的创建是怎么做的呢?
类定义的语法
class class_name
class_definition
end
其中需要介绍的一个是,initialize方法
类创建一个对象的new方法,就是以initialize方法进行定义的
虽然调用的是new方法,但是再类的定义中就是用的initialize方法
class HelloWorld
def initialize(name = "Ruby")
@name = name
end
def hello
puts "Hello I'm #{@name}"
end
end
m = HelloWorld.new("SinCrow")
n = HelloWorld.new
m.hello #=> Hello I'm SinCrow
n.hello #=> Hello I'm Ruby
上面的例子中,new方法便是由initialize方法定义的,传参也是同一道理
3.1 实例变量与实例方法
之前总是说实例变量,实例方法.到底是怎么使用的呢?
1.实例变量,就是在类的定义中,@var形式的变量,可以在类的实例方法中通用
算是类中实例方法间的全局变量,只要在同一个实例中,就可以超越方法的局限,任意引用,修改实例变量
2.实例方法,顾名思义,实例才能调用的方法,即对象的方法
备注: 不同的实例的实例变量值是可以不同的,而且这些实例变量都是存在的,只要实例存在,就不会消失
反观,局部变量的作用域,生命周期都是在方法中,只能在方法内部使用,方法调用结束,局部变量释放
3.2 存取器
那么,现在就有一个问题,关于在实例方法中的实例变量,既然是在类中定义的,随着实例存在
如果,需要使用实例变量怎么办呢?
很容易就可以构建下面的两个实例方法
class HelloWorld
def initialize(myname = "Ruby")
@name = myname
end
def name
@name
end
def name=(modify_name)
@name = modify_name
end
end
其中需要注意的是,name=方法,一定是紧紧相连的,不然就不能定义为方法名
但是,很没意思啊,整天都要些存取器的内容,每个实例变量都要写,所以Ruby提供了定义方法
存取器 | 作用 |
---|---|
attr_reader | 读方法(name方法) |
attr_writer | 写方法(name=方法) |
attr_accessor | 皆有 |
attr_reader :a
attr_writer :b
attr_accessor :c, :d
这样定义就可以了,用法相当于直接调用name方法与name=方法
3.3 self变量
介绍一个十分特殊的变量,self变量
以前就提到过,Ruby是完全的面向对象,
实际上,在顶部,是main对象,平时调用的函数式方法也是有接收者的
只不过是不在意接收者的信息,实际上函数式方法的接收者,就是self
我不清楚Java的this指针与self有何区别
但是,Ruby是解释性语言,所以,他的self严格的与上下文息息相关
self变量,其实就是指当前的层次
来看看,下面的例子
p self #=> main
class SelfTest
p self #=> SelfTest
def method_test
p self #=> #<SelfTest:0x0000000001c48500>
end
end
m = SelfTest.new
m.method_test
对于最外层顶部来讲,就是main,
对于类来讲,就是类名
对于实例方法来讲,就是本实例,而本实例是存在于内存上的,所以就是地址了
那么,self变量有什么用呢?
可以进行变量自身的调用,而且,可以隐式调用,Ruby缺省条件下,接收者就是self
看下面这个例子:
class Hello
attr_accessor :name
def initialize(myname)
@name = myname
end
def hello
puts "Hello ,My name is #{name}"
end
end
实例方法中的hello,获取到,name变量的值,但是,name并不是传进来的参数
而且,这个方法是没有Bug的!
其实这个name是 #{self.name},换成{@name}也是可以的
那么,平时调用函数式方法,是不是也可以加上self呢?
答案是,不行!,函数式方法,多为private(私有)方法,必须是隐式调用
注意: 调用写方法的时候,不能隐式调用,必须显式调用,不然会有很诡异的事情发生呦,来看下面的例子
name = "hello" #=> name视为变量
self.name = "hello" #=> name= 视为写方法
这样懂了吧? (滑稽)
3.4 类方法
我们在这里就不多纠缠了,类方法就是给类对象使用的方法
对,实际上我们区分方法按照接收者去区分
但是,实际上,Ruby所有的方法都应该叫做实例方法,每一个对象都有类
类方法调用者(接收者)是类,各个类,都是Class类的实例对象
下面介绍4种Ruby类方法的定义形式
-
单例方法形式
在class HelloWorld已存在条件下 class << Hello def hello puts "I'm Class method" end end
-
class上下文中,引用self形式
class Hello class << self def hello puts "Class method" end end end
-
显式标明类名,进行实例方法形式的定义
class Hello def Hello.name(myname) puts "my name is #{myname}" end end
-
等效于3,类名可以用self变量取代,但是,self切记不能省略,不然会视为实例方法
class Hello def self.name puts "My name is Ruby" end end
虽然提到了四种定义的形式,准确的来讲应该是三种,其中第四种用的最多,单例方法的形式,重复量太大
而将,类方法以实例方法的形式定义,很方便
后面还会有通过模块的方法进行类方法定义,也是很方便的方法
备注: class « 类名 ~ end 的写法为单例类定义,其中的方法是单例方法,后面会详解
3.5 常量
类中肯定也是可以定义常量的,一般常量为大写,通过”::”进行访问,类似于类方法的访问
class Hello
VERSION = "v1.0"
end
p Hello::VERSION #=> "v1.0"
3.6 类变量
类变量,类似于实例变量,是在整个类的实例方法间都可以任意引用,修改的变量
但是,区别于实例变量,类变量是子类继承后仍然能够存在的变量,存储期更长
实例变量,是随着实例对象存在而存在.那么,类变量就是随着类对象存在而存在
@@class_var是存在于类中的,所以几个实例对象,是共享这个类变量的
,但是,类变量定义存取器,不能使用attr系列方法,attr系列只能给实例变量使用
可以看看下面的例子:
class Hello
@@hello_count = 0
attr_accessor :name
def initialize(myname = "Ruby")
@name = myname
end
def hello
puts "my name is #{name}"
@@hello_count += 1
end
def self.count
@@hello_count
end
end
a = Hello.new
b = Hello.new("NIOH")
c = Hello.new("Uncharted")
p Hello::count #=> 0
a.hello
p Hello::count #=> 1
b.hello
p Hello::count #=> 2
c.hello
p Hello::count #=> 3
3.7 修饰方法的关键字
曾经看过C,C种存在着,static关键字,可以用来修饰函数,使函数只能在本文件中调用,常说的:
具有内部链接的静态类型函数,链接,存储期,作用域是C中进行控制的几个关键点
随着代码组织的不同,相应的关键字发生变化(hepangda说的,不是我说的)
在,Ruby中有这样三个修饰方法的关键字:public, private, protected
之前被国内的概念误导了很长时间,
他们死要纠缠访问与调用这两个概念, , ,
其实,这三个关键字分得很清楚,可以用下面几句话概括:
1. public以外的两类方法,都不能被显式的实例对象调用
2. public方法为默认属性,可以任意访问调用,无论是类还是实例对象,或是同父类的实例对象
3. protected方法相当于类之内的public方法,可以被随意调用,即所谓的被同父类的实例对象访问
接上一条,也就是指可以在其他的方法中,调用protected方法,其他方法的实例对象是self
4. private方法就更好说了,只能进行隐式的调用,即自身调用(self)
即private是这个类进行私有实现所使用的,只能隐式的被self调用,不能显示地指定接收者
要写,只能 private_method_test,不能obj.private_method_test
有一句话,挺恰当的,private私有方法是内部实现所使用的,对其他的类(非同父类),实例都是不可见的
在Ruby社区提问,得到了这样比较好的答案
访问和调用扯半天,真的是拖裤子放P
<4> 扩展类
4.1 给原有类添加方法
很简单,就是定义和类名相同的类,如果是已存在的类,则会追加方法
class String
def count_word
array = split(/\s+/) #=> array = self.spilt(/\s+/)
return array.size
end
end
str = "hello world"
p str.count_word #=> 2
4.2 继承
前面说了很多次继承了,下面是继承的语法
class SubClass < SuperClass
class_definition
end
还是要提醒一点,Ruby缺省继承为Object类
4.3 alias与undef
这两个关键字在其他语言中可能也用过
alias就是Linux中命令别名的命令,这里同义
alias new_name old_name
alias :new_symbol :old_symbol
undef在C中应该也是使用过的,用来取消命令别名
undef 方法名
undef :方法名
用来删除方法定义的,例如:我们可以在子类中删除不需要的父类方法定义,并且重新定义
聊一聊”单例类”
之前,进行类方法的定义时说过,使用单例类的形式进行类方法的定义
什么叫单例类?
单例类,指的就是,并非对类定义进行修改,只是针对这一个实例进行方法等的修改,只对这一个实例有效
所以叫单例类,单例类中的方法叫单例方法
str1 = "hello"
str2 = "world"
class << str1
def hello
puts "I'm hello"
end
end
str1.hello #=> "I'm hello"
str2.hello #=> undefined method `hello' for "world":String (NoMethodError)
上面的例子即可说明hello方法对于同类实例str1与str2而言,只对str1有效,所以叫做单例方法
那么,我们定义类方法,与单例(实例)又有什么关系呢?
这里就可以清晰地说明: Ruby中一切皆对象,类都是Class类的实例,所以使用单例方法,就是添加类方法
前面说过,Ruby部支持父类多重继承(没有Java diao,对吧?(滑稽))
但是,Ruby却很好的处理了一定情况下的需求,靠得就是模块(module)
下面,我们来仔细看看模块(module)
二 模块 (module)
<1> 模块的作用与意义
模块为什么存在,从下面两点来说明:
1.1 提供命名空间
之前其实一直没有注意过,但是,一旦工程量比较大的时候,命名空间就十分重要了.
系统的讲,命名空间就是指对方法,常量,类的名称进行区分和管理的单位
使用模块(module),天生进行命名空间的区分,互相不污染
举个简单的例子:
很火爆的网络Web服务器Nginx
extern ngx_module_t ngx_kqueue_module;
extern ngx_module_t ngx_eventport_module;
extern ngx_module_t ngx_devpoll_module;
extern ngx_module_t ngx_epoll_module;
extern ngx_module_t ngx_select_module;
static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf);
static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle);
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle);
static char *ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static void *ngx_event_core_create_conf(ngx_cycle_t *cycle);
static char *ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf);
使用ngx作为统一的命名空间,当然这里只是举例子,实际上Ruby中使用模块可以避免命名空间冲突的问题
A模块中的foo方法,与B模块中的foo方法完全是两个方法
我们使用类似于调用类中的方法的放法来调用模块方法,模块名.方法名,也叫模块函数
FileTest.exist?("/usr?bin/ruby")
FileTest.size("/usr?bin/ruby")
如果,在当前命名空间中不与模块函数冲突,那么,可以使用include来省略模块名
include Math
sin(30)
sqrt(100)
注意,一定是不发生命名空间冲突的时候,可以使用include进行命名空间的合并,反之,抛出异常
def sin
5
end
include Math
p sin(5)
#> in `sin': wrong number of arguments (given 1, expected 0) (ArgumentError)from test.rb:6:in `<main>'
这个错误,会在继承机制中进行解答,见下
1.2 利用Mix-in进行扩展
前面提到过,Ruby只支持单一父类继承,那么问题来了,如果碰到需要继承多个类的功能时怎么办?
答案是,使用模块来进行类功能的扩展
可以将模块作为通用功能的实现,来完成多个功能类似的类的扩展
Mix-in指的就是,将通用功能设计在模块中实现,之后include,则module的变量,方法都可以在类中使用
那么,套用前人总结的经验,一般Mix-in解决这两种问题:
1. 虽然两个类拥有相似的功能,但是不惜望将他们作为相同的类来考虑时
2. Ruby不支持父类的多重继承,因此无法对已继承的类添加共通的功能的时候
Mix-in Example
module MyModule
module_difinition
end
class MyClass1
include MyModule
end
class MyClass2
include MyModule
end
<2> 创建模块
上面说了这么多,下面说说模块的用法吧
首先,使用module关键字
至于语法,与class是一致的
module module_name
module_difination
end
下面是一个模块的使用示范
module Hello
Version = "v1.0"
def hello
puts "Hello"
end
module_function :hello
end
p Hello::Version #=> "v1.0
Hello.hello #=> "Hello"
include Hello
p Version #=> "v1.0"
hello #=> "Hello"
现在来说说上面中的一些之前没有见到过的东西
2.1 常量
常量的用法同类中的常量(并非类变量)
模块中,不能出现类变量,实例变量(如有不懂,看前面)
2.2 方法
模块中的方法,定义是同类方法的
但是,需要注意的是:
如果要想使模块函数对外可见,使其可以使用”模块名.方法名”的形式调用
这里指的是,使用模块进行模块函数的调用,任何包含此模块的上下文都可以直接调用模块函数
那么,一定要使用module_function方法,进行模块函数的声明,使其对外可见,参数是模块函数的符号名
另外,
模块方法中的self,是当前模块对象
module HelloName
p self #=> "HelloName"
def hello
p self
end
module_function :hello
end
HelloName.hello #=> "HelloName"
区别于类的不同之处:类中的实例方法,self只当前的对象(实例,有实体)
而模块函数中的self指模块对象,因为模块不具有实例
但是,模块中的self一旦Mix-in之后,便表示类对象,所以,Mix-in一般不建议模块使用self
<3> Mix-in
聊聊Mix-in及其相关
首当其冲的是,判断一个类是否Mix-in了某模块,使用include? 方法
class ClassName
end
ClassName.include?(kernel) #=> "true"
现在,就来说说一个很关键的,类的继承机制
在类中include的模块会做为虚拟的父类进行方法的继承,以及方法的查找
来看一个例子:
module TestModule
end
class TestClass
include TestModule
end
p TestClass.ancestors #=> "[TestClass, TestModule, Object, Kernel, BasicObject]"
p TestClass.superclass #=> "Object"
如上所示,TestModule模块作为虚拟的”父类”进入了TestClass的继承顺序列表中
但是,TestClass的直接父类还是Object类
就像这样,我们可以使用模块功能来实现构建多个功能类似的扩展类的需求
<4> 来聊聊模块方法查找规则
-
原类中定义了同名方法时,优先使用类中的方法
module M def hello puts "M#hello" end end class C include M def hello puts "C#hello" end end C.new.hello #=> "C#合理咯"
原因就是: p C.ancestors #=> [C, M, Object, Kernel, BasicObject]
类的继承顺序上,C类在M模块之前
-
在同一个类中,优先使用最后一个包含的模块
module M1 def hello puts "M1#hello" end end module M2 def hello puts "M2#hello" end end class C include M1 include M2 end C.new.hello #=> "M2#hello"
同理,看继承顺序: [C, M2, M1, Object, Kernel, BasicObject]
-
嵌套模块和类时,继承也是线性的
module M1 end module M2 end module M3 include M2 end class C include M1 include M3 end p C.ancestors #=> "[C, M3, M2, M1, Object, Kernel, BasicObject]"
-
多次使用include嵌套同一模块,忽略已经包含的模块
module M1 end module M2 end class C include M1 include M2 include M1 end p C.ancestors #=> "[C, M2, M1, Object, Kernel, BasicObject]"
之前提到单例方法时,都是使用 “ class « 实例 ~ end “ 的形式定义单例方法
对于类(Class类的实例)来讲,就是进行类方法的定义
出了使用这种形式以外,我们还可以灵活使用模块,来实现单例方法
同之前的例子,看一下这个例子
module Hello
def hello
puts "hello"
end
#module_function :hello
end
str1 = "hello"
str2 = "world"
str1.extend(Hello)
p str1.hello #=> "hello"
p str2.hello #=> "nil"in `<main>': undefined method `hello' for "world":String (NoMethodError)
将单例方法,定义成为模块函数,之后对单例使用Object#extend方法,进行单例方法的扩展即可
注意: 其中的module_funcation是不能有的,他会将模块函数定义为某唉进行调用的外部函数
如果将类作为实例,那自然就是类方法了,
现在,我们可以系统的来说:
使用模块,一方面可以利用Mix-in特性来,扩展类.另一方面,可以使用Object#extend方法来扩展单例
即,使用include可以帮助我们突破继承的限制.extend可以跨过类,使用模块来扩展对象
<5> 类与Mix-in
之前说过很多次类方法
我们可以这样理解类方法:
1. Class类的实例方法 (任何类都是Class类的实例)
2. 类对象的单例方法(只对此类适用,对其他类不适用,这些类都作为对象来考虑)
所以,在扩展类的方面,我们这样综合类与模块
module Hello
def hello
puts "hello,method"
end
end
module Bye
def bye
puts "Bye,method"
end
end
class Test
include Hello #=> include将Hello模块扩展为类的实例方法
extend Bye #=> extend将Bye扩展为类的类方法
end
m = Test.new
p m.hello
p Test.bye
又扯到之前提到过的: 其实Ruby中一切皆对象,所谓的类方法其实也是实例方法
面向对象程序设计
这部分,我也不时啃的十分清楚,说说下面几个问题:
-
什么是对象
-
封装与多态
-
鸭子类型
来说点其他的吧,寒假学习,确实容易放松,这篇博客拖了很长时间
一方面是理解起面向对象,走了一点弯路,另一方面还是太皮了
不过现在搞得差不多了,Ruby整个语法框架差不多了
后面也就是运算符,异常处理,块的内容,理解消化挺快的
接下来,就是一系列Ruby默认类的内容了,很方便,有各种黑魔法(可能就拖起来看了,唉~)
之前逛得太多了,下来改回归Kernel的任务了
驱动,CS:APP还是要好好对待,估计到开学勉强差不多
对了,还有算法题,那就好好写写BFS与DFS吧,写的深入一点
February 15, 2018 3:41 AM