SOLID in RAILS

Muhammad Tri Wibowo
3 min readNov 23, 2023

--

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

--

--

No responses yet