當實作時發現某個物件可以同時符合兩個不同的情境,只需要做出一些調整,我們應該修改當下的物件,還是進行拆分呢?
# 單一(Render - 算繪)功能
class Rectangle
def render; end
end
class Ellipse
def render; end
end
# --> 預期,只能做 "render"
# 單一(Render - 算繪)職責
class Rectangle
def render; end
# 其他能力,但是跟算繪相關
def color; end
end
class Ellipse
def render; end
def color; end
end
Based on Sample
class CourseHoursCalculator
# ...
def remaining_hours(course, user = single_user)
# used_hours -> used_hours_data -> DB
# Method call chains --> 描述如何做事
# --> call method --> 本身就是一種 API Call
# --> Public Method 就是物件對外的 API
(initial_hours(course, user) - used_hours(course, user)).round(2)
end
# ...
private
def used_hours(course, user)
used_hours_data[[user.id, course.id]].to_i
end
def used_hours_data
@used_hours_data ||= UsedCourse.where(user_id:, year:).group('used_courses.user_id', 'used_courses.course_id').sum('used_courses.hours')
end
end
TypeScript 對 Interface 的看法
// 規格 -(形成)-> 約定
interface Counter {
count: number
}
// 實作細節
class VisitorCounter {
public count: number = 0
}
const SimpleCounter: Counter = { count: 0 }
const ClassCounter: Counter = new VisitorCounter()
// TypeScript 對「資料結構」的看法
interface Counter {
count: number
}
// 等同於(定義資料結構)
type Counter = {
count: number
}
// User Story 描述行為 ==> 訪客拜訪次數統計
// 行為 -> 規格(需求) ==> 可以計算次數(遞增)
// 規格 -> 約定的行為 ==> Object has method "increment"
interface Counter {
increment(amount: number): number
}
// 實現約定的行為 -> 實作
class VisitorCounter implements Counter {
// Compiler 要求實現方法
increment(amount: number): number {
return this.count += amount
}
}
// 常態 -> 把 interface 的概念隱藏或忽視
class VisitorCounter {
increment(amount: number): number {
return this.count += amount
}
}
# Duck Typing
# implements "Counter"
class VisitorCounter
def increment(amount)
self.count += amount
end
end
# 使用方
def open(counter)
# counter 有 "increment" 方法就可以 -> Duck Typing
counter.increment(1)
end
# RBS 定義介面
interface Counter
def increment: (Integer) -> Integer
end
# 使用方
def open: (Counter) -> void
延伸:跟測試的關係
# Controller
def index
# Input Data -> params
# Input Boundary -> ActionController::Parameters(不明確)
# 對 Rails 來說,Input Boundary (interface) 是一種 Hash-like 的物件(實作跟 Hash 一樣的 Interface)
h = {} # Ruby Hash
h[:id] # Hash 的 [] 方法
params[:id] # ActionController::Parameters 定義 [] 方法,實作 Hash 介面
# ...
# Output Data -> instance variables
# Output Boundary -> render
end
// 某框架
class VisitorController {
// Input Boundary -> index() -> 約定了 VisitorIndexInput 類型資料
index(data: VisitorIndexInput): void {
// ...
}
}
// implements http.HandlerFunc
// Presenter Layer
func (ctrl *VisitorController) IndexHandler(w http.ResponseWriter, r *http.Request) {
// http.ResponseWriter -> Output Boundary --> Layer Presenter (RESTful/gRPC)
// *http.Request -> Input Boundary
// -> http === interface
// Input Boundary -> increment()
// Output Boundary -> res (pointer)
res := ctrl.countUseCase.increment(1)
// Encoder ==> Presenter
// json data ==> View Model
encoder := json.NewEncoder()
// View -> JSON
encoder.Encode(w, res)
}
狀態(instance variable) | 行為 (methods) | |
---|---|---|
Entity/Aggregate | Yes | Yes |
Service | No | Yes |
Data Transfer Object -> 有資料沒有行為(資料 != 狀態)
DTO –> Hash / JSON
狀態 -> 有 Context 的 Data
分析 Sample Code
# 單一使用者 Use Case
class CourseHoursController
def index
# 準備資料
@available = AvailableCourse.where(user_id:, year:)
@used = UsedCourse.where(user_id:, year:)
@courses = Course.all
@calculator = CourseHoursCalculator.new
# 進行計算
# Domain Model
@calculator
.remaining_hours(@courses, @available, @used)
# To View
.as_json(only: %i[remaining_hours]) #...
end
end
# 所有使用者 Use Case
class Admin::CourseHoursController
def index
# 準備資料
@user_ids = User.where('extract(year from join_date) <= ?', @year)
# User.before_year(@year)
# Concern --> included by User
@courses = Course.all
@availables = AvailableCourse.where(user_id:, year:).group('user_id', 'course_id')
@usedes = UsedCourse.where(user_id:, year:).group('used_courses.user_id', 'used_courses.course_id')
@calculator = CourseHoursCalculator.new
# 進行計算
@user_ids.map do |user_id|
{
user_id:,
courses: @calculator.remaining_hours(@courses, @available[user_id], @used[user_id])
end
end
end
# Service Object
class CourseHoursCalculator
def remaining_hours(courses, available, used)
courses.map do |course|
{
id: course.id,
remaining_hours: remaining_hours(available[course.id], used[course.id])
extra_hours: ....
# ...
}
end
end
private
# ...
end
這是一個賣線上課程的網站
計算哪些線上課程使用者買了多少時數,用了多少時數,還剩下多少時數
class CourseHoursCalculator
def initialize(user: nil, year: nil)
@single_user = user
@year = year || Time.zone.today.year
@users = User.where('extract(year from join_date) <= ?', @year)
@user_id = user&.id || @users.select(:id)
end
def user_remaining_hours
Course.all.map do |course|
{ id: course.id, remaining_hours: remaining_hours(course) }
end
end
def remaining_hours?(course, user = single_user)
initial_hours(course, user).positive?
end
def remaining_hours(course, user = single_user)
(initial_hours(course, user) - used_hours(course, user)).round(2)
end
def manage_statistics_hours
users.map do |user|
{
user_id: user.id,
courses: course_hours(user)
}
end
end
private
def used_hours_data
@used_hours_data ||= UsedCourse.where(user_id:, year:).group('used_courses.user_id', 'used_courses.course_id').sum('used_courses.hours')
end
def initial_hours_data
@initial_hours_data ||= AvailableCourse.where(user_id:, year:).group('user_id', 'course_id').sum('initial_hours + extra_hours')
end
def extra_hours_data
@extra_hours_data ||= AvailableCourse.where(user_id:, year:).group('user_id', 'course_id').sum('extra_hours')
end
def used_hours(course, user)
used_hours_data[[user.id, course.id]].to_i
end
def initial_hours(cource, user)
initial_hours_data[[user.id, cource.id]] || 0
end
def course_hours(user)
Course.all.map do |course|
{
id: course.id,
extra_hours: extra_hours_date[[user.id, course.id]] || 0,
initial_hours: remaining_hours?(course, user) ? initial_hours(course, user) : 0,
used_hours: used_hours(course, user),
remaining_hours: remaining_hours?(course, user) ? remaining_hours(course, user) : 0
}
end
end
end
作為工程師在職涯發展的過程中,該如何準備才能夠更加順利呢?
Oct 25, 2023在 Production 環境時,會發現一些資料的錯誤也沒有拋出例外,該如何追蹤?
Sep 27, 2023@user = User.find(params[:id]) # => User Object x 1 # user has many posts @posts = @user.posts # ORM # SELECT * FROM posts WHERE user_id = ? ---> N + "1" # ... @posts.each do |post| # Iterate - Post Object x N puts post.comments.count # SELECT COUNT(*) FROM comments WHERE post_id = ? ---> "N" + 1 end @user = User.find(params[:id]) # => User Object x 1
Jun 28, 2023or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up