SOLID in RAILS
Single Responsibility Principle
Single Responsibility Principle (SRP) menyatakan bahwa setiap class hanya boleh memiliki satu responsibility saja. Sebuah class harus memiliki tanggung jawab penuh untuk mengerjakan satu fungsi atau satu tugas saja. Variable dan method yang ada harus relevan dengan fungsi dan tugas dari class tersebut.
Not SRP
class InvoiceMailer
def initialize(order)
@order = order
end
def generate_report
# Used order object to generate report
end
def send_mail
# send email to @order.user
end
end
InvoiceMailer.new(order).generate_report
InvoiceMailer.new(order).send_mail
SRP
class InvoiceReport
def initialize(order)
@order = order
end
def generate
# Used order object to generate report
end
end
class InvoiceMailer
def initialize(order, report)
@order = order
@report = report
end
def send_mail
# send email to @order.user
end
end
report = InvoiceReport.new(order).generate
InvoiceMailer.new(order, report)
Open/Closed Principle
Open Closed Principle (OCP) menyatakan bahwa, setiap class, method, function, modules harus terbuka untuk ekstensi (open for extension), dan tertutup untuk modifikasi (closed for modification).
Open for extension artinya setiap class dapat di-extend hanya jika ada penambahan atau perubahan requirement. Closed for modification artinya class yang dibuat dan sudah di-test tidak boleh diubah, kecuali jika ditemukan bugs.
Not OCP
class InvoiceReport
def initialize(order, type)
@order = order
@type = type
end
def generate
case @type
when :csv
# Generate CSV report
when :pdf
# Generate PDF report
else
raise NotImplementedError
end
end
end
report = InvoiceReport.new(order, :pdf).generate
OCP
class InvoiceReport
def initialize(order, klass)
@order = order
@klass = klass
end
def generate
@klass.new(@order).generate
end
end
class PdfGenerator
def initialize(order)
@order = order
end
def generate
# Generate PDF Report
end
end
class CsvGenerator
def initialize(order)
@order = order
end
def generate
# Generate CSV Report
end
end
report_pdf = InvoiceReport.new(order, PdfGenerator).generate
report_csv = InvoiceReport.new(order, CsvGenerator).generate
Liskov Substitution Principle
Liskov Substitution Principle (LSP) menyatakan bahwa parent class harus dapat digantikan oleh setiap derived class-nya tanpa menyebabkan error pada program. Dengan kata lain, setiap derived class harus memiliki semua behaviour yang terdapat pada parent class-nya.
Not LSP
# Violation of the Liskov Substitution Principle in Ruby
class UserStatistic
def initialize(user)
@user = user
end
def posts
@user.blog.posts
end
end
class AdminStatistic < UserStatistic
def posts
user_posts = super
string = ''
user_posts.each do |post|
string += "title: #{post.title} author: #{post.author}\n" if post.popular?
end
string
end
end
LSP
# Correct use of the Liskov Substitution Principle in Ruby
class UserStatistic
def initialize(user)
@user = user
end
def posts
@user.blog.posts
end
end
class AdminStatistic < UserStatistic
def posts
user_posts = super
user_posts.select { |post| post.popular? }
end
def formatted_posts
posts.map { |post| "title: #{post.title} author: #{post.author}" }.join("\n")
end
end
Interface Segregation Principle
Interface Segregation Principle (ISP) menyatakan bahwa setiap class hanya boleh mengimplementasikan interface yang benar-benar akan digunakan. Dengan kata lain, sebuah class dilarang untuk mengimplementasikan interface yang memiliki method yang tidak digunakan oleh class tersebut.
Not ISP
# Violation of the Interface Segregation Principle in Ruby
class CoffeeMachineInterface
def select_drink_type
# select drink type logic
end
def select_portion
# select portion logic
end
def select_sugar_amount
# select sugar logic
end
def brew_coffee
# brew coffee logic
end
def clean_coffee_machine
# clean coffee machine logic
end
def fill_coffee_beans
# fill coffee beans logic
end
def fill_water_supply
# fill water logic
end
def fill_sugar_supply
# fill sugar logic
end
end
class Person
def initialize
@coffee_machine = CoffeeMachineInterface.new
end
def make_coffee
@coffee_machine.select_drink_type
@coffee_machine.select_portion
@coffee_machine.select_sugar_amount
@coffee_machine.brew_coffee
end
end
class Staff
def initialize
@coffee_machine = CoffeeMachineInterface.new
end
def serv
@coffee_machine.clean_coffee_machine
@coffee_machine.fill_coffee_beans
@coffee_machine.fill_water_supply
@coffee_machine.fill_sugar_supply
end
end
ISP
# Correct use of the Interface Segregation Principle in Ruby
class CoffeeMachineUserInterface
def select_drink_type
# select drink type logic
end
def select_portion
# select portion logic
end
def select_sugar_amount
# select sugar logic
end
def brew_coffee
# brew coffee logic
end
end
class CoffeeMachineServiceInterface
def clean_coffee_machine
# clean coffee machine logic
end
def fill_coffee_beans
# fill coffee beans logic
end
def fill_water_supply
# fill water logic
end
def fill_sugar_supply
# fill sugar logic
end
end
class Person
def initialize
@coffee_machine = CoffeeMachineUserInterface.new
end
def make_coffee
@coffee_machine.select_drink_type
@coffee_machine.select_portion
@coffee_machine.select_sugar_amount
@coffee_machine.brew_coffee
end
end
class Staff
def initialize
@coffee_machine = CoffeeMachineServiceInterface.new
end
def serv
@coffee_machine.clean_coffee_machine
@coffee_machine.fill_coffee_beans
@coffee_machine.fill_water_supply
@coffee_machine.fill_sugar_supply
end
end
Dependency Inversion Principle
Dependency Inversion Principle (DIP) menyatakan bahwa high-level module tidak boleh bergantung pada low-level module. Keduanya harus bergantung pada abstractions. Abstractions tidak boleh bergantung pada implementations. Implementations harus bergantung pada abstractions.
Not DIP
class PaymentUser
def initialize(order)
@order = order
end
def pay
Stripe.pay(@order)
end
end
DIP
class PaymentUser
def initialize(order, payment_processor)
@order = order
@payment_processor = payment_processor
end
def pay
payment_processor.pay(@order)
end
end