Ruby Ruby 面向对象编程之 SOLID

rocLv · 2019年04月22日 · 最后由 zzz6519003 回复于 2020年02月02日 · 3463 次阅读

在网上用中文搜Ruby SOLID,出来的很多文章都是英文,不过这也许是我用谷歌搜的结果。

SOLID 原则,是面向对象编程的几个重要概念的英文首字母缩写,也是面向对象编程中最基础的几个概念。最早是由《代码清洁之道》的作者 Bob Martin 和《修改代码的艺术》的作者 Michael Feathers 提出来。

但是,基础,并不意味着很多人都掌握,其实,不掌握的还是很多。对于掌握了的人来说,能用好的也不多。为什么会这样呢?也许因为对我们人类来说,它却恰好是反模式

SOLID 原则是什么

SOLID 不是一个原则,他是一组面向对象设计原则的简称,他代表下面 5 种设计原则:

  • S ingle Responsibility Principle 单一职责原则
  • O pen/Closed Principle 开闭原则
  • L iskov Substitution Principle 里氏替换原则
  • I nterface Segregation Principle 接口隔离原则
  • D ependency Inversion Principle 依赖倒置原则

以上就是 SOLID 中的 5 种面向对象设计原则,下面分别看看他们具体指的是什么。

单一职责原则,即 Single Responsibility Principle

单一职责原则是修改一个类的理由从来不应超过一个。

这条原则虽然看上去简单,但是实施起来非常难。因为你很难去界定一个类的职责。

这个职责的确定有一些技巧,比如说你在实现这个类的时候,不需要去关注其他的类;还有就是你修改这个类的时候,如果影响到这个类的其他职责的实现,那说明我们需要拆分这个类了。

比如说 Rails 中 Model 的职责,主要是用来处理 Ruby 对象和数据库中的相应的表的关系。现在我们的写 REST API 的时候,都需要为用户生成一个 Token。通常的写法会是:

class User < ActiveRecord::Base
...

   def generate_token
     # generate unique token
   end

...
end

但是很显然,生成 Token 这个职责和 User 本身的关系并不大, 因此我们应该把它单独提出来。

关闭原则(Open/Closed Principle)

类或者方法,应该对扩展是开放的,对修改是关闭的

有没有一种感觉,突然发现 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并不依赖于低阶的对象AndroidIphone。 所以它也同时满足了依赖倒置原则。

最后,他们之间的关系如图:

参考链接:

一直感觉 rails 的 model 不符合单一职责原则,但是对数据库操作更方便了?

比如干了这些事

  • 数据库的各种操作
  • 表内一条数据的实体
  • validation
  • callback
heroyct 回复

ActiveRecord.

use_micro_usb_for_charging 写到 charge 类没毛病吧

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册