在网上用中文搜Ruby SOLID
,出来的很多文章都是英文,不过这也许是我用谷歌搜的结果。
SOLID 原则,是面向对象编程的几个重要概念的英文首字母缩写,也是面向对象编程中最基础的几个概念。最早是由《代码清洁之道》的作者 Bob Martin 和《修改代码的艺术》的作者 Michael Feathers 提出来。
但是,基础,并不意味着很多人都掌握,其实,不掌握的还是很多。对于掌握了的人来说,能用好的也不多。为什么会这样呢?也许因为对我们人类来说,它却恰好是反模式
。
SOLID 不是一个原则,他是一组面向对象设计原则的简称,他代表下面 5 种设计原则:
S
ingle Responsibility Principle 单一职责原则O
pen/Closed Principle 开闭原则L
iskov Substitution Principle 里氏替换原则I
nterface Segregation Principle 接口隔离原则D
ependency Inversion Principle 依赖倒置原则以上就是 SOLID 中的 5 种面向对象设计原则,下面分别看看他们具体指的是什么。
单一职责原则是修改一个类的理由从来不应超过一个。
这条原则虽然看上去简单,但是实施起来非常难。因为你很难去界定一个类的职责。
这个职责的确定有一些技巧,比如说你在实现这个类的时候,不需要去关注其他的类;还有就是你修改这个类的时候,如果影响到这个类的其他职责的实现,那说明我们需要拆分这个类了。
比如说 Rails 中 Model 的职责,主要是用来处理 Ruby 对象和数据库中的相应的表的关系。现在我们的写 REST API 的时候,都需要为用户生成一个 Token。通常的写法会是:
class User < ActiveRecord::Base
...
def generate_token
# generate unique token
end
...
end
但是很显然,生成 Token 这个职责和 User 本身的关系并不大,因此我们应该把它单独提出来。
类或者方法,应该对扩展是开放的,对修改是关闭的
有没有一种感觉,突然发现 Rails 项目不用维护了:》
这个原则的主要作用是希望进一步提升我们的代码的模块化。开关原则主要是通过各种设计模式来实现,例如策略模式。
以我们生活中的手机充电器为例:
class Charger
def charge
case phone_type
when android_phone
use_type_c_for_charging
when iphone
use_lightning_for_charging
end
end
private
def use_type_c_for_charging
# charging
end
def use_ligntning_for_charging
# charging
end
end
假如说我们希望支持旧手机,那我们必须修改这个类:
class Charger
def charge
case phone_type
when android_phone
use_type_c_for_charging
when iphone
use_lightning_for_charging
when old_android_phone
use_micro_usb_for_charging
end
end
private
def use_type_c_for_charging
# charging
end
def use_ligntning_for_charging
# charging
end
def use_micro_usb_for_charging
# charging
end
end
很显然这违反了开关原则。为了使得我们的代码遵守开关原则,我们可以使用以下代码:
class Charger
def initialize(phone)
@phone = phone
end
def charge
@phone.charge
end
end
class Android
def charge
p "Android phone is charging..."
end
end
class Iphone
def charge
p "Iphone is charging..."
end
end
vivo = Android.new
charger = Charger.new(vivo)
charger.charger
即便后续手机接口又变了,我们可以再声明一个类来实现 charge 的功能,而我们的 Charger 类并不需要改变。
L
iskov Substitution Principle里氏替换原则是指任意父类,都可以用它的子类来替换,而且不会出现异常或者错误的结果。
关于里氏替换原则比较经典的例子是矩形和正方形的例子。
class Rectangle
def initialize(width, length)
@width = width
@length = length
end
def caculate_area
@width * @length
end
end
class Square < Rectangle
def initialize(length)
@width = length
@length = length
end
end
假如说我们有段代码:
rec = Rectangle.new(5, 4)
rec.caculate_area #=> 20
但是如果我们用它的子类,Square 来替换 Rectangle 的时候,我们程序就会报错。
这意味着我们违反了里氏替换原则。所以说这样的抽象是不正确的抽象。
正确的抽象可以让 Rectangle 和 Square 都继承于某个类,比如说类 Shape. 为了让我们的类 Shape 看上去更有用,我故意增加了一个方法,inspect。
class Shape
def inspect
puts "I am a #{self.class}."
end
end
class Rectangle < Shape
attr_accessor :width, :length
def caculate_area
@width * @length
end
end
class Square < Shape
attr_accessor :width
def caculate_area
@width ** 2
end
end
可能很多同学会问,这个原则的意义在哪?这个原则的意义在于,遵守里氏替换原则,有助于我们遵守开关原则。
I
nterface Segregation Principle接口隔离原则说的是客户端不应该被迫依赖于它不使用的方法。简单来说就是更小和更具体的瘦接口比庞大臃肿的胖接口好。
Ruby 是种动态语言,接口隔离原则主适用于于 Java 等静态语言而言。Ruby 方法的参数不需要指定参数的类型。在 Ruby 中比较出名的是鸭子类型:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
所以在动态语言中,你幸福了,可以不用了解这个原则。如果你对这个原则依然感兴趣的话,可以记住三个字: “瘦接口”。
D
ependency Inversion Principle依赖倒置原则说的是高层模块 (比如说业务逻辑) 不应该依赖底层模块(比如说数据库查询和 IO),两者都应该依赖其抽象。
依赖倒置原则可以通过鸭子类型和依赖倒置原则共同实现,其实在讲开关原则的时候举的例子,其实也适合本例。
class Charger
def initialize(phone)
@phone = phone
end
def charge
@phone.charge
end
end
class Android
def charge
p "Android phone is charging..."
end
end
class Iphone
def charge
p "Iphone is charging..."
end
end
vivo = Android.new
charger = Charger.new(vivo)
charger.charger
这里高阶的实例charger
并不依赖于低阶的对象Android
和 Iphone
。所以它也同时满足了依赖倒置原则。