- vừa được xem lúc

Những Design Patterns mà bạn nên biết

0 0 6

Người đăng: LamDN

Theo Viblo Asia

Khi xây dựng một ứng dụng web, bạn sẽ thường sử dụng framework hay libraries hỗ trợ. Mặc dù bản thân chúng đã có cấu trúc và các rules rõ ràng nhưng trong nhiều trường hợp bạn vẫn phân vân không biết nên viết code ở đâu hay như thế nào để có thể tái sử dụng và dễ dàng maintain sau này. Vì vậy, dưới đây sẽ là một số design patterns phổ biến mà bạn nên biết. Trong bài viết mình sử dụng Ruby cho các ví dụ, với những ngôn ngữ khác sẽ có cách thể hiện khác nhưng tư tưởng là không thay đổi.

image.png

Service Object

Trong mô hình MVC, controller là trung gian giao tiếp giữa model và view. Vì vậy bản thân nó sẽ chứa nhiều logic, tuy nhiên chúng ta chỉ nên để controller chỉ nên làm đúng nhiệm vụ của nó là giao tiếp còn những logic khác nên được tách ra một service:

class UsersController def create user = User.new(user_params) if user.save UserMailer.verify_email(user) render json: { status: :success, user: user.as_json, } else render json: { status: :error, errors: user.error.messages } end end
end

Khi sử dụng service:

class CreateUserService def initialize user_params @user = User.new(user_params) end def execute if user.save send_verify_email { status: :success, user: user.as_json, } else { status: :error, errors: user.errors.messages, } end end private attr_reader :user def send_verify_email UserMailer.verify_email(user) end
end
class UsersController def create render json: CreateUserService.new(user_params).execute end
end

ServiceResponse

Trong một dự án có thể sẽ có rất nhiều service, mỗi service lại trả về các kết quả với những format khác nhau. Đó là lúc bạn nên sử dụng ServiceResponse để thống nhất dữ liệu trả về của các service. Viết lại ví dụ ở trên với service response:

class ServiceResponse def self.success(message: nil, payload: {}) { status: :success, message: message, payload: payload, } end def self.error(message: nil, payload: {}) { status: :error, message: message, payload: payload, } end
end
class CreateUserService def initialize user_params @user = User.new(user_params) end def execute return error_creating unlees user.save send_verify_email success end private attr_reader :user def success ServiceResponse.success(payload: user.as_json} end def error_creating ServiceResponse.error(payload: user.errors.messages} end def send_verify_email UserMailer.verify_email(user) end
end

Finder

Là nơi thao tác trực tiếp với ORM. Nó bao gồm các logic liên quan đến tìm kiếm, filter dữ liệu. Sử dụng finders là một giải pháp giúp hạn chế fat model, và dễ dàng tái sử dụng.

class EmployeesController < ApplicationController def index @employess = current_user.employees @employess = @employees.in_in(params[:id]) if params[:id].present? end
end class Admin::EmployeesController < Admin::BaseController def index @employess = current_admin.employees @employees = @employees.by_name(params[:name]) if params[:name].present? @employees = @employees.where(is_blocked: true) if params[:blocked].present? end
end

Khi sử dụng finder:

class EmployeesFinder def initialize(manager, params) @manager = manager @params = params end def execute employees = manager.employees employees = filter_by_id(employees) employees = filter_by_name(employees) employees = filter_by_blocked(employees) order(employees) end private attr_reader :manager, :params def filter_by_id(employees) return employees if params[:id].blank? employees.ransack(id_in: params[:id]).result end def filter_by_name(employees) return employees if params[:name].blank? employees.ransack(name_cont: params[:name]).result end def filter_by_blocked(employees) return employees unless params[:blocked] employees.where(is_blocked: true) end def order(employees) return employees unless params[:sort] employees.ransack(sort: params[:sort]).result end
end
class EmployeesController < ApplicationController def index @employess = EmployeesFinder.new(current_user, params).execute end
end class Admin::EmployeesController < Admin::BaseController def index @employess = EmployeesFinder.new(current_admin, params).execute end
end

Decorator

Khi muốn hiển thị các thông tin khác của một record, hoặc đơn giản là một format khác của một thông tin có sẵn ví dụ như các trường date time, bạn hoàn toàn có thể thêm một method vào trong model tương ứng. Tuy nhiên, điều này sẽ làm model của bạn ngày một phình to. Đây là lúc bạn nên sử dụng decorator.

class User < ApplicationRecord def formatted_created_at created_at.strftime("YYYY/MM/DD") end
end
<div class="user"> <p class="user-name"><%= @user.name %></p> <span class="user-created-at"><%= @user.formatted_created_at %></span>
</div>

Sau khi sử dụng decorator:

class UserDecorator def formatted_created_at created_at.strftime("YYYY/MM/DD") end
end
<div class="user"> <p class="user-name"><%= @user.name %></p> <span class="user-created-at"><%= @user.decorate.formatted_created_at %></span>
</div>

Presenter

Khác với decorator, presenter được sử dụng để hạn chế logic trong controller hoặc ở ngoài view. Hãy cùng xem qua ví dụ sau đây:

class PostsController < ApplicationController before_action :load_post, only: :show def show @related_posts = Post.related_posts_for(@post) @latest_comments = @post.comments.latest.limit(10) end def load_post @post = Post.find params[:id] end
end

Ở ngoài view:

<h1><%= @post.title %></h1>
<% if current_user == @post.author %> <button>Edit</button>
<% end %>
<p class="post-content"><%= @post.content %></p>
<div class="comments"><%= render @latest_comments %></div>
<%= render @related_posts %>

Với những controller chứa nhiều logic phức tạp, số lượng các biến instance variable được tạo ra sẽ càng nhiều. Nếu sử dụng chúng ở ngoài view lâu dần sẽ khó quản lý và phát triển sau này. Đó cũng là lúc bạn nên dụng presenter:

class PostPresenter attr_reader :post, :current_user def initialize current_user, post @current_user = current_user @post = post end def related_posts Post.related_posts_for(post) end def latest_comments post.comments.latest.limit(10) end def can_edit? current_user == post.author end
end
class PostsController < ApplicationController before_action :load_post, only: :show def show @presenter = PostPresenter.new(current_user, post) end def load_post @post = Post.find params[:id] end
end
<h1><%= @presenter.post.title %></h1>
<%= content_tag :button, "Edit" if @presenter.can_edit? %>
<p class="post-content"><%= @presenter.post.content %></p>
<div class="comments"><%= render @presenter.latest_comments %></div>
<%= render @presenter.related_posts %>

Như bạn thấy, số lượng instance variable đã giảm, logic ở controller và view cũng đã rõ ràng hơn rất nhiều.

Serializer

Khác với presenter, serializer thường được sử dụng để build response cho API. Hãy cùng xem ví dụ sau để hiểu hơn về nó:

class Api::PostsController < Api::BaseController before_action :load_post, only: :show def show render json: { posts: @post.as_json( only: [:id, :title, :content], include: { author: { only: [:id, :name], methods: [:age] }, comments: { only: [:id, :content], author: { only: [:id, :name], methods: [:age] } } } ) } end def load_post @post = Post.find params[:id] end
end

Các thư viện hỗ trợ serializer có thể kể đến như jsonapi-serializeractive_model_serializers. Mỗi thư viện đều có ưu và nhược điểm riêng, tuy hiên cách dùng không quá khác biệt. Ví dụ dưới đây sử dụng Active Model Serializer:

class UserSerializer < ActiveModel::Serializer attributes :id, :name attribute :age do Time.zone.year - object.birthday.year end
end
class CommentSerializer < ActiveModel::Serializer attributes :id, :content belongs_to :author, serializer: UserSerializer
end
class PostSerializer < ActiveModel::Serializer attributes :id, :title, :content belongs_to :author, serializer: UserSerializer has_many :comments
end
class Api::PostsController < Api::BaseController before_action :load_post, only: :show def show render json: @post, serializer: PostSerializer end def load_post @post = Post.find params[:id] end
end

Như bạn có thể thấy, sử dụng serializer giúp cho code trở nên rõ ràng hơn, tăng khả năng tái sử dụng và dễ dàng mở rộng.

Conclusion

Vừa rồi là một số pattern thường được sử dụng trong lập trình web, lựa chọn đúng pattern giúp cho source code bạn rõ ràng thống nhất cũng như dễ dàng maintain và mở rộng sau này.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tìm hiểu Adapter Pattern trong Rails

. Nếu là một web developer chắc hẳn chúng ta đã không ít lần đọc qua về các Design patterns hay cách áp dụng chúng để làm cho code trở nên hướng đối tượng hơn, dễ đọc, dễ hiểu, dễ maintain, dễ mở rộng, … Các design patterns được áp dụng khá nhiều trong các Rails projects như Service Object, Decorato

0 0 35

- vừa được xem lúc

Bill Pugh Singleton trong Java - đơn giản đến không ngờ

Lời mở đầu. Singleton pattern có lẽ là design pattern đơn giản nhất mà hầu như ai cũng biết. Nó giúp tạo một instance duy nhất của class. Thường dùng để tạo các class cho Database, Manager.

0 0 27

- vừa được xem lúc

Mình đã xài Design Pattern Cho mục đích cá nhân như thế nào ? Phần 1:Singleton Và Builder

1. Lời Mở Đầu. xin chào các bạn đã đến với bài viết của mình . Bài viết này được mình ấp ủ từ rất lâu rồi .

0 0 83

- vừa được xem lúc

Golang Design Patterns: Design Patterns là gì?

Design Patterns là gì . .

0 0 23

- vừa được xem lúc

Design patterns là gì? Từ cuộc sống đến lập trình và cách học trong 1 tuần (mẹo) - Kèm tài liệu và source - Series Design patterns

Design patterns là gì? Design patterns chính là một gã khổng lồ, nếu bạn đứng được trên vai thì bạn sẽ nhìn xa hơn. Làm sao bạn đứng được trên vai hãy theo cách của bài viết này, từ CUỘC SỐNG đến PHẦN

0 0 26

- vừa được xem lúc

Design Pattern: Delegation trong Kotlin - cách để nhờ người khác làm bài tập về nhà

Khái niệm. Trước đây mình cũng chưa biết về Delegation Pattern, cho đến khi học Kotlin thì thấy người ta hay dùng keyword by trong lúc khai báo một biến.

0 0 13