惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
Jina AI
Jina AI
S
SegmentFault 最新的问题
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
The Cloudflare Blog
MyScale Blog
MyScale Blog
F
Full Disclosure
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
A
Arctic Wolf
T
Tor Project blog
WordPress大学
WordPress大学
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
V2EX - 技术
V2EX - 技术
C
CERT Recently Published Vulnerability Notes
L
Lohrmann on Cybersecurity
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
S
Securelist
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
PCI Perspectives
PCI Perspectives
雷峰网
雷峰网
J
Java Code Geeks
G
GRAHAM CLULEY
D
DataBreaches.Net
C
CXSECURITY Database RSS Feed - CXSecurity.com
Attack and Defense Labs
Attack and Defense Labs
美团技术团队
Security Archives - TechRepublic
Security Archives - TechRepublic
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
Docker
Cloudbric
Cloudbric
L
LangChain Blog

Nic Lin's Blog

謝明真 - 高效領導力的課後筆記 NFT 開發實戰!基礎智能合約入門 (3) NFT 開發實戰!基礎智能合約入門 (2) NFT 開發實戰!基礎智能合約入門 (1) 如何自我檢測 log4j CVE 漏洞 Rails 如何在資料寫入時記錄來源 IP 位置 如何經營工程師 Youtube 頻道 - Part 8 營收篇 如何經營工程師 Youtube 頻道 - Part 7 酸民文化篇 如何經營工程師 Youtube 頻道 - Part 5 設備器材篇 如何經營工程師 Youtube 頻道 - Part 4 後製剪輯篇 如何經營工程師 Youtube 頻道 - Part 3 文案企劃篇 如何經營工程師 Youtube 頻道 - Part 2 設備器材篇 如何經營工程師 Youtube 頻道 - Part 1 制訂頻道方向篇 如何經營工程師 Youtube 頻道 - Part 0 Rails 中避免 race condition 的最佳實踐(二) Rails 中避免 race condition 的最佳實踐(一) 10 分鐘整合 google sheet 做自動化開發功能週報 經營 Side Project 300 天所帶來的收穫及挑戰 我的 Youtube 影片製作流程 API 設計時必須注意的 HTTP header 底線問題 如何提升你的程式可讀性之實務技巧(三) 如何提升你的程式可讀性之實務技巧(二) 如何提升你的程式可讀性之實務技巧(一) Ruby 中使用 freeze 優化效能的時機 避免 React 中的 useEffect 無限 render 在 Rails 內輕量使用 Vue Component 的最佳實踐 如何在區域網路用 Docker 架設有 SSL 的 Gitlab 從被問到問人,那些我常問的面試問題 [Rails] 如何漂亮寫出可維護的 query (Maintainable Rails Query) 在已知長度情況下優化 slice 的性能 [ReactNative] 如何在 iOS APP 上主動要求用戶評分 Rails 的 scope 為什麼用 lambda? Proc 與 lambda 不同之處 淺談 Active Record 的 Lazy load 特性 Rails 專案搭配 Github Actions 進行 RSpec 自動化測試 JavaScript 中 require, import 的差別及效能 React 效能優化基本招 ES6 箭頭函式 (Arrow functions) 2 個月擁有 6000 用戶 Side project 這樣做(一) 如何讓自己成為失敗的軟體工程師 如何用 Rack::Attack 阻擋 DDOS / 惡意流量 用 OpenSSL 自簽開發用 HTTPS SSL 憑證 為機器加上登入訊息,在 ubuntu 設置登入歡迎詞 Ruby Memoization 性能優化之記憶化 淺談 SSH agent forwarding 和 proxy command 的安全風險與應用 [Rails] Service / Library / Concern 的差異 避免過度的 Defensive Programming 防禦性程式設計 1:1 攪亂器,如何用 Ruby 做可逆推序號 Rails 中的欄位及方法命名原則 [Rails] 用 puma-dev 作為本地開發伺服器 (支援 https 自簽憑證) 將 Rails 專案從手動部屬遷移使用 Capistrano 自動化部屬 工程師提昇自己的教學和簡報技術的方法 [筆記] Rails 3.2 升級 Rails 6.beta 經驗分享 Class method 氾濫帶來什麼問題 RDBMS 課程心得與筆記 常用的 Rails 開發規範 Rest-Client 如何做 Basic Authentication 驗證 [Rails] 何為 tld_lebgth? 遵循 Semantic Versioning 軟體開發語意化版本管理 請直接在 MySQL 裡面直接用 utf8mb4 取代 utf8 如何解決在 awesome print 中遇到 ActionController::Parameters unable to convert unpermitted 如何在 Mac 上升級 PostgreSQL 並遷移資料 如何解決 Mysql2::Error: Incorrect string value 讀書心得 - 「信任因子:信任如何影響大腦運作、激勵員工、達到組織目標」 我是如何寫部落格筆記的 讀書心得 - 「先問,為什麼?:顛覆慣性思考的黃金圈理論,啟動你的感召領導力」 [Rails] 解決 Reset Password 帶來的 token 洩漏問題 我的軟體工程師生涯:如何挑選適合你的公司 Rails 中的 delegate 用法 淺述 SSR SPA 優缺點 Rails 非同步工作請用 Global ID [React] Class Component 傳遞 props 的 2 種方式 好用的隱私權政策 URL 自動生成 Rails 5.1 之後的 tag helper Rails 5.2 Encrypted Credentials 最近面試被給的建議和書單 一般架構需要用到 K8S 嗎 透過 commit SHA 找 github Pull request 從零搭建,如何讓 Rails 跑在 Kubernetes(k8s)(二) 從零搭建,如何讓 Rails 跑在 Kubernetes(k8s)(一) React Stateless Functional Components 搞懂 React 中的 state 和 props 在以太坊智能合約上是可以預測隨機數的 在台灣租屋必須注意的事 Rails 5 簡單雙向加解密 如何用 ABA 培養自律型員工 不要在 rake task 中定義 method, 請用 RAKE::DSL rails 非hash只想用array輸出page 如何處理陣列裡有重複的值 [Rails] 如何重設你的專案名稱 Ruby on Rails install on Mac 安裝步驟 使用 Friendly_id 與 Babosa 美化你的Rails 網址 Junior Rails 兩個月實戰心得 Devise使用Google實作登入 [iterm2] 如何新增alias 一個新鮮人找尋Rails工作的面試經驗 如何讓兩個資料表建立關聯 routing 的 namespace strong parameter user story 的格式 user story 是什麼?
物件導向基本原則 SOLID (Ruby Sample)
Nic Lin · 2018-11-18 · via Nic Lin's Blog

前幾天面試被問到這個,對這些掌握度是一知半解實在受不了,必須做個筆記瞭解一下

SOLID 是 OOP 以下幾個原則的縮寫

  • S : Single Responsibility Principle (單一職責原則)
  • O : Open Closed Principle (開放封閉原則)
  • L : Liskov Substitution Principle (里氏替換原則)
  • I : Interface Segregation Principle (介面隔離原則)
  • D : Dependency Inversion Principle (依賴反轉原則)

Single Responsibility Principle (單一職責原則)

『你只有一個理由需要更改這個class,如果有一個以上的理由就表示:這個class負責超過一個以上的責任。』

先看以下違反 SRP 的例子:

class DealProcessor
  def initialize(deals)
    @deals = deals
  end

  def process
    @deals.each do |deal|
      Commission.create(deal: deal, amount: calculate_commission)
      mark_deal_processed
    end
  end

  private

  def mark_deal_processed
    @deal.processed = true
    @deal.save!
  end

  def calculate_commission
    @deal.dollar_amount * 0.05
  end
end

透過這個 class 我們可以處理交易的佣金支付,但如果要不斷增加複雜的策略或規則,都必須進到這個 class 裡面來做修改。

所以更好的作法應該是把計算佣金的部分單獨抽出來做一個 class,這樣日後修改維護或寫測試就能有更佳的彈性。

class DealProcessor
  def initialize(deals)
    @deals = deals
  end

  def process
    @deals.each do |deal|
      mark_deal_processed
      CommissionCalculator.new.create_commission(deal)
    end
  end

  private

  def mark_deal_processed
    @deal.processed = true
    @deal.save!
  end
end

class CommissionCalculator
  def create_commission(deal)
    Commission.create(deal: deal, amount: deal.dollar_amount * 0.05)
  end
end

這樣一來,負責標記交易和處理佣金計算的責任就拆分開來了,就是解耦的部分。

Open Closed Principle (開放封閉原則)

概念:讓程式「對擴充開放」「對修改封閉」

看一下以下這個違反 OCP 原則的範例:

class UsageFileParser
  def initialize(client, usage_file)
    @client = client
    @usage_file = usage_file
  end

  def parse
    case @client.usage_file_format
      when :xml
        parse_xml
      when :csv
        parse_csv
    end

    @client.last_parse = Time.now
    @client.save!
  end

  private

  def parse_xml
    # parse xml
  end

  def parse_csv
    # parse csv
  end
end

透過這個範例可以發現,如果今天我想要加入一個 yaml 的解析,那就會直接改動 UsageFileParser,在下面的 private 加入一個 parse_yaml 方法,並且在 public 的 parse 中還要多一個 case。

我們應該希望在不變動原本的內容下新增新的解析器,這樣一來可以直接讓原本的 class 有更佳的覆用性,並且也同時遵守 SRP 原則,每個 class 只負責他該負責的。

所以我們應該把每一種解析器單獨抽出來,並且改寫如下

class UsageFileParser
  def initialize(client, parser)
    @client = client
    @parser = parser
  end

  def parse(usage_file)
    parser.parse(usage_file)
    @client.last_parse = Time.now
    @client.save!
  end
end

class XmlParser
  def parse(usage_file)
    # parse xml
  end
end

class CsvParser
  def parse(usage_file)
    # parse csv
  end
end

這樣一來,我就可以透過

UsageFileParser.new(a_client, XmlParser) # 解析 xml
UsageFileParser.new(b_client, CsvParser) # 解析 csv

如果還要加入 yaml, 也只需要再做一個 YamlParser 的 class 就可以使用。

這樣一來就達到「對擴充開放」「對修改封閉」了。

Liskov Substitution Principle (里氏替換原則)

Liskov 替換原則的定義是:『子類別必須能夠替代基礎類別』

我覺得這是比較難搞懂得一項原則,我自己的理解有兩種(不衝突)

  1. 不要繼承不必要的遺產,沒用到而去繼承反而是種累贅甚至會搞壞了整個系統也不一定。
  2. 確保底層的實做會遵守上層介面所定義的行為。

看下面的例子,有一個 Person 類別,而 Student 和 Women 分別繼承其 class

class Person
  def talk
   ''
  end
  
  def height
   ''
  end
end


class Student < Person
  def talk
    '我是一年一班 Nic'
  end
  
  def height
    '155cm'
  end
end

class Women < Person
  def talk
    { say: "我是蒼井空,來自日本" }
  end
  
  def height
   '160cm'
  end
end
women = Women.new
student = Student.new

def introduce_by_person(person)
  puts "Hi, I'm #{person.height} height and I say #{person.talk}"
end

introduce_by_person(women) # Hi, I'm {:cm=>"160"} height and I say {:say=>"我是蒼井空,來自日本"}
introduce_by_person(student) # Hi, I'm 155cm height and I say 我是一年一班 Nic

因為遵循了父類別設定 talkheight,所以呼叫 introduce_by_person 方法時,也都能正確輸出。

不過這邊可以注意到的是 Women 中並沒有遵循 LSP 原則,他沒有按照原本 weight 的定義修改,原本應該是輸出 String,但卻改為 Hash 這將造成程式會出現「不可預測」性,換句話說就是可能產生不可預知或是不容易察覺的 bugs

Interface Segregation Principle (介面隔離原則)

用戶不應該被迫相依於他們用不到的函示

如果有用不到的函示,應該做成不同的 interface 或是 protocal

有要用到再裝上去就好

舉個例子,有個計算 Fee 的 class

class FeeCalculator
  def calculate(product, user, vat)
    # calculation
  end
end

會用在 ProductController 中

class ProductController
  def show
    @fee = FeeCalculator.new.calculate(product, user, vat)
  end
end

但如果我今天對 Fee 的 class 新增了一個行為,必須將結果存進 database

class FeeCalculator
  def calculate(product, user, vat, save_result)
    # calculation

    if save_result
      # storing result into datebase
    end
  end
end

這時候就會發現,我們只有在 create 時要觸發 save_result,但 Show 不需要啊

那就會變成,每一次使用都還要去新增一個參數傳遞

class ProductController
  def show
    @fee = FeeCalculator.new.calculate(product, user, vat, false)
  end
end

class OrderController
  def create
    @fee = FeeCalculator.new.calculate(product, user, vat, true)
  end
end

但其實 Show 行為他根本不需要瞭解要不要存進 database,他只是想計算 Fee 啊!

所以 save_result 這個行為應該單獨在抽出來變成一個方法或是介面

class FeeCalculator
  def calculate(product, user, vat)
    # calculation
  end

  def save(fee)
    # storing result into db
  end
end

這樣看起來就俐落多了,也遵循 ISP 原則了

class ProductController
  def show
    @fee = FeeCalculator.new.calculate(product, user, vat)
  end
end

class OrderController
  def create
    fee_calculator = FeeCalculator.new
    
    fee = fee_calculator.calculate(product, user, vat)
    fee_calculator.save(fee)
  end
end

Dependency Inversion Principle (依賴反轉原則)

program to an interface, not an implementation

原本上層的類別會依賴下層的類別,就如同要蓋二樓就必須蓋好一樓 但 Dependency-Inversion Principle 的意思就是應該要讓上層和下層都依賴於抽象層,也就是上面重點說的 interface

A 物件程式內部需要使用 B 物件 A, B物件中有依賴的成份

依賴反轉把原本 A 對 B 直接控制權移交給由第三方容器

降低 A 對 B 物件的耦合程度,並讓雙方都倚賴抽象。

來看以下的例子

class Newsperson
  def broadcast(news)
    Newspaper.new.print(news)
  end
end

class Newspaper
  def print(news)
    puts news
  end
end

laura = Newsperson.new
laura.broadcast("Some Breaking News!") # => "Some Breaking News!"

可以看的出來 Newsperson 依賴較低層級的 Newspaper

現在廣播與報紙的內容相關連了,如果我們更改報紙的名稱或是添加更多廣播平台,這兩件事都會導致我們不得不改變 Newsperson,儘管看起來依賴程度非常小。

現在試著重構來遵循 DIP 原則

class Newsperson
  def broadcast(news, platform = Newspaper)
    platform.new.broadcast(news)
  end
end 

class Newspaper
  def broadcast(news)
    do_something_with news
  end
end

class Twitter
  def broadcast(news)
    tweets news
  end
end

class Television
  def broadcast(news)
    live_coverage news
  end
end 

laura = Newsperson.new
laura.broadcast("Breaking news!") #do_something_with "Breaking news!"
laura.broadcast("Breaking news!", Twitter) #tweets "Breaking news!"

現在高級別的 Newsperson 不再依賴低級別的 class,而且對各平台的 class 更容易測試了

重點:系統中模組建議依賴抽象,因為各個模組間不需要知道對方太多細節(實作),知道越多耦合越強。

小結

其實這些 principle 都會互相依賴,通常遵照一兩項規範下去做,也會連帶其他的效果,其實目的都是讓程式更好維護及擴充。

參考