SLA, SRP и DIP

Три принципа OOD

Подготовил Владимир Суворов

Что такое SLA?

SLA - single level of abstraction. Принцип единого уровня абстракций.

Зачем нужно?

Чтобы повысить читаемость кода, мы пишем код на том уровне абстракции, который приемлем для его понимания без умственных затрат на вникание в реализацию.

Когда не SLA?

Когда уровни абстракций смешаны.

Плохо, не по SLA


# spec/features/user_marks_todo_complete_spec.rb
feature "User marks todo complete" do
  scenario "updates todo as completed" do
    sign_in # Норм
    create_todo "Buy milk" # Норм
    # А ниже уже похуже :)
    find(".todos li", text: "Buy milk").click_on "Mark complete"

    expect(page).to have_css(".todos li.completed", text: "Buy milk")
  end

  def create_todo(name)
    click_on "Add new todo"
    fill_in "Name", with: name
    click_on "Submit"
  end
end
					

Плохо, не по SLA


(defn instruction-for-info
 [info instructions]
 (let [default
       (get instructions :default "default message not found")]
   (or
     (->> instructions
       (filter (fn [[k _]] (.startsWith info (name k))))
       first
       last)
     default)))
					

Как исправить?

  • Корректно именовать идентификаторы
  • Выносить сложную логику в отдельные методы

Что получилось?


feature "User marks todo complete" do
  scenario "updates todo as completed" do
    sign_in
    create_todo "Buy milk"

    mark_complete "Buy milk"

    expect(page).to have_completed_todo "Buy milk"
  end

  def create_todo(name)
    click_on "Add new todo"
    fill_in "Name", with: name
    click_on "Submit"
  end

  def mark_complete(name)
    find(".todos li", text: name).click_on "Mark complete"
  end

  def have_completed_todo(name)
    have_css(".todos li.completed", text: name)
  end
end
					

Что получилось?


(defn prefix-match [target-str pairs]
  (some
    (fn [[k v]]
        (if (.startsWith target-str (name k))
            v
            false))
    pairs))

(defn instruction-for-info
 [info instructions]
 (let [default
       (:default instructions "default message not found")]
   (or
     (prefix-match info instructions)
     default)))
					

Что такое SRP?

Принцип единственной обязанности (Single responsibility principle)

Что подразумевает?

Каждый сущность должна иметь одну обязанность и эта обязанность должна быть инкапсулирована.

Пример без SRP


class Student
  attr_accessor :first_term_home_work, :first_term_test,
    :first_term_paper
  attr_accessor :second_term_home_work, :second_term_test,
    :second_term_paper

  def first_term_grade
    (first_term_home_work + first_term_test + first_term_paper) / 3
  end

  def second_term_grade
    (second_term_home_work + second_term_test + second_term_paper) / 3
  end

end
					

Исправили


class Student
  def initialize
    @terms = [ Grade.new(:first), Grade.new(:second) ]
  end

  def first_term_grade
    term(:first).grade
  end

  def second_term_grade
    term(:second).grade
  end

  private

  def term(reference)
    @terms.find {|term| term.name == reference}
  end
end

class Grade
  attr_reader :name, :home_work, :test, :paper
  def initialize(name); @name = name; end
  def grade
    (home_work + test + paper) / 3
  end
end
						

Теперь про Clojure


(defn display-records []
  (let [records (sql/query "SELECT * FROM ...")
        record-string (str/join "\n" (for [r records]
        		(str (:first-name r) (:last-name r) (:id r))))]
    (println record-string)))
					

Теперь про Clojure


(defn fetch-records []
  (sql/query "SELECT * FROM ..."))

(defn record->string [record]
  (str (:first-name record) (:last-name record) (:id record)))

(defn records->string [records]
  (str/join "\n" (map record->string records)))

(defn display-records []
  (-> (fetch-records)
      records->string
      println))
					

Что такое DIP?

Принцип инверсии зависимостей (англ. Dependency Inversion Principle, DIP) — принцип, используемый для уменьшения связанности в компьютерных программах.

А точнее?

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Разберем пример


class Report
  def initialize
    @body = "whatever"
  end

  def print
    XmlFormatter.new.generate @body
  end
end

class XmlFormatter
  def generate(body)
    # convert the body argument into XML
  end
end
					

Исправили


class Report
  def initialize
    @body = "whatever"
  end

  def print(formatter)
    formatter.generate @body
  end
end

class XmlFormatter
  def generate(body)
    # convert the body argument into XML
  end
end
					

Fin