在网上用中文搜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。所以它也同时满足了依赖倒置原则。
