Skip to content

Latest commit

 

History

History
853 lines (630 loc) · 19.7 KB

File metadata and controls

853 lines (630 loc) · 19.7 KB

8.类和模块

con8.1类是什么

类(Class)是面向对象中一个重要的术语。

8.1.1类和实例

类表示对象的种类,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

8.1.2继承

通过扩展已定义的类来创建新类称为继承

继承后创建的新类称为子类(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

8.2创建类

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

8.2.1 创建class语句

class 类名
    类的定义
end
# 类名的首字母必须大写

8.2.2 initialize方法

使用new方法生成新的对象时,Initialize方法会被调用,同时new方法的参数也会原封不动的传给Initialize方法,因此初始化对象时需要的处理一般都写在这个方法中。

harry = Helloworld.new("Harry")
# 该行代码是新建一个Helloworld类的对象,并将“Harry”参数传至myname
ruby1  = Helloworld.new
#  如果未指定参数,会使用默认参数

8.2.3实例对象和实例方法

通过@name = myname这行程序,作为参数传切进来的对象会被赋值给@name,我们把以@开头的变量称为实例变量。引用为初始化的实例变量时返回值为nil。

    def initialize(myname = "Ruby")        # initialize方法
        @name = myname                    # 初始化实例变量
    end

8.2.4存取器

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

8.2.5 特殊变量self

在实例方法中,可以用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

8.2.6 类方法

方法的接受者就是类本身(类对象)的方法称为类方法。

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")

8.2.7 常量

在class语句中可以定义常量

class Helloworld
    Version = "1.0"
end

p Helloworld::Version

8.2.8 类变量

以@@开头的变量称为,类变量。类变量是该类所有实例的共享变量,这一点与常量相似,不同的是类变量的值可以多次修改。但是类变量不能使用存取器,所以需要直接定义。

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

8.2.9 限制方法的调用

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

8.3 扩展类

8.3.1 在原有类的基础上添加方法

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

8.3.2 继承

# 继承语法:
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]

8.4 alias 与 undef

8.4.1 alias

有时我们希望给已存在的方法设置别名,这时候就需要用到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

8.4.2 undef

undef用于删除已定义的方法。与alias一样,参数可以指定方法名或者符号名

undef 方法名  # 直接使用方法名
undef :方法名 # 使用符号名

在子类中希望删除父类定义的方法时可以使用undef

8.5 单例类 (singleton class / eigenclass)

# hello只是str1的方法
str1 = "Ruby"
str2 = "ruby"

class << str1
    def hello
        "Hello #{self}"
    end
end
p str1.hello
# p str2.hello   # 会报错(NoMethodError)

Ruby中所有的类都是Class类的对象,因此Class类的实例方法以及类对象所添加的单例方法都是类方法。

8.6 模块是什么

模块是Ruby的特色功能之一。如果说类表现的是食物的实体(数据)及其行为(处理),那么模块表现的就只是事物的行为部分。模块鱼类有以下两点不同:

  • 模块不能拥有实例
  • 模块不能被继承

8.7 模块的使用方法

8.7.1 利用Mix-in扩展功能

Mix-in就是将模块混合到类中。在定义类时使用Include,模块中的方法、常量就能被类使用。

Mix-in可以灵活地解决下面的问题

  • 虽然两个类拥有相似的功能,但是不希望它们作为相同的类来考虑
  • Ruby不支持父类的多重继承,因此无法对已经继承的类添加共通的功能
module MyMoudle
    # 共同的方法等
end

class Myclass1
    include MyMoudle
    # Myclass1中独有的方法
end

class Myclass2
    include MyMoudle
    # Myclass2中独有的方法
end

8.7.2 提供命名空间

**命名空间(namespace)**就是对方法、常量、类等名称进行区分及管理的单位。模块提供各自独立的命名空间,因此不同模块的的同名方法会被程序认为是两个不同的方法,同名常量也会被认为不同常量,因此通过在模块中定义名称,可以解决命名冲突的问题。

我们使用“模块名.方法名”的形式来调用在模块中定义的方法,这样的方法称为模块函数。

# 常量
p Math::PI
p Math.sqrt(2)

puts "include:"
include Math
p PI
p sqrt(2)

8.8创建模块

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")

8.8.1 常量

和类一样,在模块中定义的常量可以通过模块名来访问。

8.8.2 方法的定义

和类一样可以直接在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

8.9 Mix-in

被包含的模块的作用,类似于虚拟的父类。

虽然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        # 返回类的父类

8.9.1 查找方法的规则

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]

8.9.2 extend方法

利用Object#extend方法,可以实现批量定义单例方法。extends方法可以使单例类包含模块,并把模块的功能扩展到对象中

module Number
    def number(n)
        "第#{n}#{self}"
    end
end

str = "Girlfriend"
str.extend(Number)

p str.number(99)

include 可以帮助我们突破继承的限制,通过模块扩展类的功能。

extend可以帮助我们跨过类,直接通过模块扩展对象的功能。

8.9.3 类与Mix-in

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

8.10 面向对象程序设计

8.10.1对象是什么

面向对象语言中的“对象”就是指数据(或者说数据的集合)及操作该数据的方法的组合

8.10.2 面向对象的特征

  • 封装(encapsulation)
    • 对象管理的数据不能直接从外部进行操作,数据的更新、查询等操作都必须通过调用对象的方法来完成。通过封装,可以防止因把非法数据设置给对象而使程序产生异常的情况发生。Ruby对象在默认情况下是强制被封装的,虽然有attr_accessor这样简单定义访问级别的方法,但也不能过度使用
  • 多态(polymorphism)
    • 各个对象都有自己独有的消息解释权。同名方法属于多个对象(不同对象的处理结果也不一样)

8.10.3 鸭子类型(duck typing)

能像鸭子一样走路,能像鸭子一样叫的,那一定就是鸭子

对象的特征并不是由其种类(类及其继承关系)决定的,而是由对象本身具有什么样的行为(拥有什么方法)决定的。

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)

fetch_and_downcase对传进来的参数只有两个要求:

  • 能以ary[index]形式获取元素
  • 获取的元素可以执行downcase方法

8.10.4 面向对象的例子

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