类(Class)是面向对象中一个重要的术语。
类表示对象的种类,Ruby中的对象一定都属于某一个类。如“字符串”对象实际上是"String"类的对象(实例)
生成新的对象时,一般会用到各个类的new
方法
当查询对象属于哪个类的时候,我们可以使用class
方法
当判断对象是否属于某个类时,我们可以用instance_of?
方法
ary = Array.new
p ary
ary1 = []
str = "Hello World!"
p ary1.class
p ary.instance_of?(Array)
p str.class
通过扩展已定义的类来创建新类称为继承。
继承后创建的新类称为子类(subclass),被继承的类称为父类(superclass)。
通过继承可以实现以下操作
- 在不影响原有功能的前提下追加新功能
- 重定义原有功能,是名称相同的方法产生不同的效果
- 在已有功能的基础上追加处理,扩展已有功能
- 可以创建多个具有相似功能的类
BasicObject类是最最基础的类,它定义了作为作为Ruby对象的最基本功能。一般对象所需要的类一般都被定义为Object类。
子类和父类是一种“is-a”关系,根据类的关系反向追查对象是否属于某个类时,则可以用is_a
?方法
str = "This is a String"
p str.is_a?(String) =>true
p str.is_a?(Object) =>true
class Helloworld # 创建class语句
def initialize(myname = "Ruby") # initialize方法
@name = myname # 初始化实例变量
end
def hello # 实例方法
puts "hello #{@name}!"
end
end
harry = Helloworld.new("Harry") # 新建实例对象
ruby = Helloworld.new("ruby")
ruby1 = Helloworld.new
harry.hello # 调用方法
ruby.hello
ruby1.hello
class 类名
类的定义
end
# 类名的首字母必须大写
使用new方法生成新的对象时,Initialize方法会被调用,同时new方法的参数也会原封不动的传给Initialize方法,因此初始化对象时需要的处理一般都写在这个方法中。
harry = Helloworld.new("Harry")
# 该行代码是新建一个Helloworld类的对象,并将“Harry”参数传至myname
ruby1 = Helloworld.new
# 如果未指定参数,会使用默认参数
通过@name = myname这行程序,作为参数传切进来的对象会被赋值给@name,我们把以@开头的变量称为实例变量。引用为初始化的实例变量时返回值为nil。
def initialize(myname = "Ruby") # initialize方法
@name = myname # 初始化实例变量
end
Ruby中,从对象外部不能直接访问实例变量或对实例变量赋值,需要通过方法
class Helloworld
def initialize(myname = "Ruby")
@name = myname
end
def hello
puts "hello #{@name}!"
end
def name # 获取name
@name
end
def name=(value) #修改name
@name = value
end
end
harry = Helloworld.new("Harry")
p harry.name # 可以像访问属性一样使用方法
harry.name = "harry" # 看似是给对象的属性赋值,实际是调用name=(value)方法。
p harry.name
当对象有多个实例变量时,如果逐个定义存取器,就会使程序变得难懂也容易写错。为此,Ruby提供了更简便的定义方法attr_reader、attr_writer、attr_accseeor。只要指定实例变量名的符号,Ruby就会自动帮我们定义相应的存取器
定义 | 意义 |
---|---|
attr_reader :name | 只读(定义name方法) |
attr_writer :name | 只写(定义name=方法) |
attr_accessor :name | 读写(定义以上两个方法) |
class Helloworld
attr_accessor :name
end
在实例方法中,可以用self这个特殊的变量来引用方法的接受者。
class Helloworld
attr_accessor :name
def initialize(myname = "Ruby")
@name = myname
end
def hello
puts "hello #{@name}!"
end
def greet
puts "Hi I am #{self.name}!"
end
end
方法的接受者就是类本身(类对象)的方法称为类方法。
1.在class << 类名 ~ end
这个特殊定义中,定义实例方法的形式来定义类方法
此种方法定义的方法称为单例方法
class Helloworld
attr_accessor :name
def initialize(myname = "Ruby")
@name = myname
end
def hello
puts "hello #{@name}!"
end
end
class << Helloworld
def hello(name)
puts "#{name} said HelloWorld!"
end
end
Helloworld.hello("Harry")
2..在class <<self ~end
这个特殊定义中,定义实例方法的形式来定义类方法
class Helloworld
class << self
def hello(name)
puts "#{name} said Helloworld!"
end
end
end
Helloworld.hello("Harry")
3.在def 类名.方法名 ~ end
这个特殊定义中,定义实例方法的形式来定义类方法
class Helloworld
attr_accessor :name
def initialize(myname = "Ruby")
@name = myname
end
end
def Helloworld.hello(name)
puts "#{name} said Helloworld!"
end
Helloworld.hello("Harry")
4.在def 类名.方法名 ~ end
这个特殊定义中,也可以在类定义使用self定义实例方法的形式来定义类方法
class Helloworld
attr_accessor :name
def initialize(myname = "Ruby")
@name = myname
end
def self.hello(name)
puts "#{name} said hello"
end
end
Helloworld.hello("Harry")
在class语句中可以定义常量
class Helloworld
Version = "1.0"
end
p Helloworld::Version
以@@开头的变量称为,类变量。类变量是该类所有实例的共享变量,这一点与常量相似,不同的是类变量的值可以多次修改。但是类变量不能使用存取器,所以需要直接定义。
class HelloCount
@@count = 0
def HelloCount.count
@@count
end
def initialize(myname="Ruby")
@name = myname
end
def hello
@@count+=1
puts " Hello I am #{@name}!"
end
end
Harry = HelloCount.new("Harry")
ruby = HelloCount.new("ruby")
ruby1 = HelloCount.new
p HelloCount.count
Harry.hello
ruby.hello
ruby1.hello
p HelloCount.count
Ruby提供了3种方法的访问级别:
- public : 以实例方法的形式向外部公开该方法(在没有指定访问级别时默认为public,但initialize通常会被定义为private)
- private :在指定接收者的情况下,不能调用该方法(只能使用缺省接收者的方法调用该方法,因此无法从实例的外部访问)
- protected :在同一个类中时可将该方法作为实例方法调用
class AccTest
def pub
puts "pub is a public method."
end
public :pub #把pub方法指定为public(可省略)
def priv
puts "pub is a private method."
end
private :priv #把priv方法指定为private
end
acc = AccTest.new
acc.pub # 可正常调用
acc.priv # 会报错
希望统一定义多个方法的访问级别时,可以使用下面的语法
class AccTest
def pub # 默认为Public
puts "pub is a public method."
end
private # 以下的方法都被定义为private
def priv
puts "pub is a private method."
end
end
acc = AccTest.new
acc.pub # 可正常调用
acc.priv # 会报错
定义为protected的方法,在同一个类(及其子类)中可作为实例方法调用,而在除此以外的地方则无法使用。
class Point
attr_accessor :x,:y
protected :x=,:y=
def initialize(x=0.0,y=0.0)
@x,@y = x,y
end
def swap(other)
tmp_x,tmp_y = @x,@y
@x,@y = other.x,other.y
other.x,other.y = tmp_x,tmp_y
return self
end
end
p0 = Point.new
p1 = Point.new(1.0,2.0)
p [p0.x, p0.y]
p [p1.x, p1.y]
p0.swap(p1)
p [p0.x, p0.y]
p [p1.x, p1.y]
# p0.x = 10.0 #会报错,x=为protected
Ruby允许我们在已经定义好的类中添加方法。
class String
def count_word
ary = self.split(/\s+/) # 用空格分隔self
# 分解成数组
return ary.size # 返回分隔后的数组的元素总数
end
end
str = "Let us Just Be Friend"
p str.count_word
# 继承语法:
class 类名<父类名
类定义
end
利用继承,我们可以把共同的功能定义在父类,把各自独有的功能定义在子类。
定义类时,如果没有指定父类,Ruby会默认该类为Object类的子类
class RingArray < Array # 指定父类
def [] (i) # 重定义运算符[]
idx = i%size # 计算新索引值
super(idx) # 调用父类中同名的方法
end
end
weekday = RingArray["一","二","三","四","五","六","日"]
p weekday[6]
p weekday[11]
p weekday[15]
p weekday[-1]
有时我们希望给已存在的方法设置别名,这时候就需要用到alias方法。
alias 别名 原名 # 直接使用方法名
alias :别名 :原名 # 使用符号名
# 定义了类C1及其子类C2,在类C2中,
# 对hello方法设置别名old_hello后,重定义了hello方法
class C1
def hello
"Hello"
end
end
class C2<C1
alias old_hello hello
def hello
"#{old_hello},again"
end
end
obj = C2.new
p obj.old_hello
p obj.hello
undef用于删除已定义的方法。与alias一样,参数可以指定方法名或者符号名
undef 方法名 # 直接使用方法名
undef :方法名 # 使用符号名
在子类中希望删除父类定义的方法时可以使用undef
# hello只是str1的方法
str1 = "Ruby"
str2 = "ruby"
class << str1
def hello
"Hello #{self}"
end
end
p str1.hello
# p str2.hello # 会报错(NoMethodError)
Ruby中所有的类都是Class类的对象,因此Class类的实例方法以及类对象所添加的单例方法都是类方法。
模块是Ruby的特色功能之一。如果说类表现的是食物的实体(数据)及其行为(处理),那么模块表现的就只是事物的行为部分。模块鱼类有以下两点不同:
- 模块不能拥有实例
- 模块不能被继承
Mix-in就是将模块混合到类中。在定义类时使用Include,模块中的方法、常量就能被类使用。
Mix-in可以灵活地解决下面的问题
- 虽然两个类拥有相似的功能,但是不希望它们作为相同的类来考虑
- Ruby不支持父类的多重继承,因此无法对已经继承的类添加共通的功能
module MyMoudle
# 共同的方法等
end
class Myclass1
include MyMoudle
# Myclass1中独有的方法
end
class Myclass2
include MyMoudle
# Myclass2中独有的方法
end
**命名空间(namespace)**就是对方法、常量、类等名称进行区分及管理的单位。模块提供各自独立的命名空间,因此不同模块的的同名方法会被程序认为是两个不同的方法,同名常量也会被认为不同常量,因此通过在模块中定义名称,可以解决命名冲突的问题。
我们使用“模块名.方法名”的形式来调用在模块中定义的方法,这样的方法称为模块函数。
# 常量
p Math::PI
p Math.sqrt(2)
puts "include:"
include Math
p PI
p sqrt(2)
module 模块名
模块定义
end
# 模块名首字母需要大写
module HelloModule # module语句
Version = "1.0" # 定义常量
def hello(name) # 定义方法
puts "Hello,#{name}!"
end
module_function :hello # 指定hello 为模块函数
def foo
p self
end
module_function :foo
end
p HelloModule::Version
HelloModule.hello("Harry")
HelloModule.foo
puts "include:"
include HelloModule # 包含模块
p Version
hello("Harry")
和类一样,在模块中定义的常量可以通过模块名来访问。
和类一样可以直接在Module语句中定义方法,但是如果只定义了方法,虽然在模块内部与包含此模块的语句中可以直接调用,但是却不能以“模块名.方法名”的形式直接调用。如果希望把方法作为模块函数公开给外部使用,就需要用到module_function方法。module_function的参数是表示方法名的符号
def hello(name) # 定义方法
puts "Hello,#{name}!"
end
module_function :hello # 指定hello 为模块函数
以“模块名.方法名”的形式调用时,如果在方法中调用self(接收者),就会获得该模块的对象
def foo
p self
end
module_function :foo
被包含的模块的作用,类似于虚拟的父类。
虽然Ruby采用的是不允许具有多个父类的单一继承模型,但是通过利用Mix-in,就既可以保持单一继承的关系,又可以同时让多个类共享功能。
module M
def meth
"meth"
end
end
class C
include M
end
c = C.new
p c.meth
p C.include?(M) # 判断类是否包含模块
p C.ancestors # 调查类的继承关系,返回列表
p C.superclass # 返回类的父类
1.同继承关系一样,原类中已经定义了同名的方法时,优先使用该方法
module M
def meth
"M#meth"
end
end
class C
include M
def meth
"C#meth"
end
end
c = C.new
p c.meth # =>"C#meth"
2.在同一个类中包含多个模块式,优先使用最后一个包含的模块
module M1
def meth
"M1#meth"
end
end
module M2
def meth
"M2#meth"
end
end
class C
include M1
include M2
end
c = C.new
p c.meth # => "M2#meth"
p C.ancestors # => [C, M2, M1, Object, Kernel, BasicObject]
3.嵌套inclued时,查找顺序也是线性的
module M1
def meth
"M1#meth"
end
end
module M2
def meth
"M2#meth"
end
end
module M3
include M2
end
class C
include M1
include M3
end
c = C.new
p c.meth # => "M2#meth"
p C.ancestors # =>[C, M3, M2, M1, Object, Kernel, BasicObject]
4.相同的模块被包含两次以上时,第二次以后的会被省略
module M1
def meth
"M1#meth"
end
end
module M2
def meth
"M2#meth"
end
end
class C
include M1
include M2
include M1
end
p C.ancestors # => [C, M2, M1, Object, Kernel, BasicObject]
利用Object#extend方法,可以实现批量定义单例方法。extends方法可以使单例类包含模块,并把模块的功能扩展到对象中
module Number
def number(n)
"第#{n}个#{self}"
end
end
str = "Girlfriend"
str.extend(Number)
p str.number(99)
include 可以帮助我们突破继承的限制,通过模块扩展类的功能。
extend可以帮助我们跨过类,直接通过模块扩展对象的功能。
Ruby中,所有类本身都是Class类的对象。类方法就是类对象的实例方法:
- Class类的实例方法
- 类对象的单例方法
继承类后,这些方法就会作为类方法被子类继承,对于类定义单例方法,实际上也就是定义了新的类方法。
使用extend方法也同样能为类对象追加类方法,include是追加实例方法
module ClassMethods # 定义类方法的模块
def cmethod
"Class Method"
end
end
module InstanceMethods # 定义实例方法的模块
def imethod
"Instance Methods"
end
end
class MyClass
# 使用extend方法定义类方法
extend ClassMethods
# 使用include方法定义实例方法
include InstanceMethods
end
p MyClass.cmethod
p MyClass.new.imethod
面向对象语言中的“对象”就是指数据(或者说数据的集合)及操作该数据的方法的组合
- 封装(encapsulation)
- 对象管理的数据不能直接从外部进行操作,数据的更新、查询等操作都必须通过调用对象的方法来完成。通过封装,可以防止因把非法数据设置给对象而使程序产生异常的情况发生。Ruby对象在默认情况下是强制被封装的,虽然有attr_accessor这样简单定义访问级别的方法,但也不能过度使用
- 多态(polymorphism)
- 各个对象都有自己独有的消息解释权。同名方法属于多个对象(不同对象的处理结果也不一样)
能像鸭子一样走路,能像鸭子一样叫的,那一定就是鸭子
对象的特征并不是由其种类(类及其继承关系)决定的,而是由对象本身具有什么样的行为(拥有什么方法)决定的。
def fetch_and_downcase(ary,index)
if str = ary[index]
return str.downcase
end
end
ary = ["Harry","Eva","Hopkins"]
p fetch_and_downcase(ary,1)
hash = {0 => "Harry",1 => "Eva",3 =>"Hopkins"}
p fetch_and_downcase(hash,1)
- 能以ary[index]形式获取元素
- 获取的元素可以执行downcase方法
require "net/http"
require "uri"
url = URI.parse("https://meowsqaq.github.io/blog/")
http = Net::HTTP.start(url.host,url.port)
p url.scheme
p url.host
p url.port
p url.path
p url.to_s