概念最重要

昨天继续补充学习了关于 Ruby 的一些基础知识,继续整理一下。

继承和多重继承

之所以会出现「继承」这样的功能,主要目的是遵循 DRY (Don’t Repeat Yourself)的原则。

比如说已经定义一个 类 A,具有的方法有 a1,a2 和 a3。现在要新定义一个 类 B,类 B 的属性跟 类 A 一样,也同样具有方法 a1 ~ a3,不同的是类 B 还需要一个方法 a4。这个时候,按照 DRY 的原则,我们只应该需要追加定义方法 a4 就好了,其他已经定义过的不需要重新定义。这个时候就可以用到「继承」的概念。类 B 继承于 类A,这样 类 B 就具有了 类 A 的全部属性和全部方法,然后可以再次追加或者重写某些方法。

假如现在的情况是已经定义了 类 A 和类 B,要新定义一个类 C,这个类 C 需要同时具有类 A 和类 B 的方法。直观的想法是,我们要使得类 C 同时继承于类 A 和类 B,这就是所谓的「多重继承」。多重继承在某些程度上带来了便利,但是因为每一个类都可能有多个父类,使得继承链变得非常复杂。

Ruby 是不支持多重继承的。

module 模块的解决方法

那 Ruby 是如何解决类似于多重继承的问题的呢?其实 Ruby 用了 Maxin 的方法实现了类似多重继承的功能。这里要引入一个概念,就是 Module,即模块。

模块其实是一种特殊的类,是不能实例化的类。 上述的例子,我们将类 B 写成模块 B,然后使类 C 继承于类 A,再将模块 B 注入到类 C 中,这样类 C 就同时具有了 A 和 B 的方法,这种将模块注入到类中的用法就叫做 maxin。

通过 maxin,避免了多重继承,使得继承链依然清晰明了,而模块引用也实现了类似多重继承的功能。

module 作为命名空间的使用

Module 除了解决多重继承的问题,还有一个重要的作用,就是 namespace,即命名空间。

在 Rails 中的应用非常常见,比如说一个 User 的模块,分为用户和管理员的角色,两者的所拥有的权限是不同的,也就是方法是不同的,换言之,我们需要有两个 controller 对其分别管理。

在目录 app/controllers 下我们有一个 UsersController ,是对应普通用户。在目录 app/controllers/admin 下还有一个 UsersController ,对应的是管理员。观察管理员的 controller

class Admin::UsersController < ApplicationController
  # some stuff..
end

Admin::UsersController 的意思是,当下 UserController 这个类是在 Admin 这个模块下的,跟前台的 UsersController 虽然同名,却是不同的两个类。这时候,模块 module 就起到了命名空间的作用,可以将一些类分开来管理。

include 和 extend

在 Ruby 中,includeextend 都是 maxin 的方法。不同的是 include 注入的是实例方法,extend 注入的是类方法。

include 和 prepend

includeprepend 都是注入实例方法,但两者得到的继承链不同。比如说

# example1
class User
  include admin
  # ...
end

# example2
class User
  prepend admin
  # ...
end

第一个例子,在继承链中,include 进来的 Admin 是变成 User 这个类的父类,也就是说继承链是这样的: [User, Admin, …]。

对于第二个例子,用了 prepend,在继承链上,模块 Admin 会在 User 之前,也就是这样: [Admin, User, …]。

值得注意的是,在对一个实例调用某个方法时,Ruby 查找这个方法的顺序是按照继承链的顺序查找的。

private 和 protected

在一个类里面定义方法,默认的属性是 public,也就是说定义的这个方法是可以被外部访问和调用的。比如说在 Rails 项目中,对于管理员角色的判定,一般可以在 User 这个 Model 下定义个方法

class User < ApplicationRedcord
  def is_admin?
    is_admin
  end
end

这个方法是可以在 Controller 里被调用的,也就是说 is_admin? 这个方法属性是 public

如果想让定义的方法局限在本类里面,不被外部访问到,就可以用 private 或者 protected

两者的区别在于,对于 protected 的方法,外部无法访问,但是该类的子类是可以访问到的。而对于 private 的方法,是最严格的,外部完全无法访问,包括该类的子类也同样无法访问。

目前,private 最常见的使用场景是在 controller 里定义 强参数 的时候

class UserController < ApplicationAction
  # some stuff...
  
  private
  def user_params
    params.require(:user).permit(:username)
  end
end

还有一些现在还很模糊的内容,需要在不断的实践来继续理解。

学习任何一项领域的技能,就是理解这个领域的所有关键概念,然后捋清各个概念之间的联系。如何理解新的概念和建立概念之间的联系呢?实践实践再实践,总结总结再总结。

今天继续学习 Rails 部分的一些基础概念。

· rails