0%
Reading Settings
Font Size
18px
Line Height
1.5
Letter Spacing
0.01em
Font Family
Table of contents
One Design Pattern a Week: Week 2
Software Engineer
Software Engineer
Design Pattern
Design Pattern
Welcome back to my "One Design Pattern a Week" series!
Try to solve this real problem: Object Creation
Imagine you're developing a large web application that needs to support multiple types of user accounts such as Admin, Guest, and Member. Each type of user requires different initializations, configurations, and permissions.
If you used a factory library like factory-bot, you may be familiar with this syntax:
If you used a factory library like factory-bot, you may be familiar with this syntax:
// language: ruby create :user, name: "John" create :admin_user, name: "Jane"
Here are some potential issues:
- Complex Creation Logic: The creation logic for different user types can become complex and difficult to manage if implemented in a single place.
- Code Duplication: Without a proper structure, you might end up duplicating code when creating different types of user accounts.
Take a moment to think about how you would solve this problem. How can you create different types of user accounts in a systematic and manageable way?
An example of bad design
// language: ruby
class User
attr_accessor :name, :role
def initialize(name, role)
@name = name
@role = role
end
end
def create_user(type, name)
case type
when :admin
user = User.new(name, 'Admin')
# Additional admin-specific initialization
when :guest
user = User.new(name, 'Guest')
# Additional guest-specific initialization
when :member
user = User.new(name, 'Member')
# Additional member-specific initialization
else
raise "Unknown user type: #{type}"
end
user
end
# Usage
admin = create_user(:admin, 'Alice')
guest = create_user(:guest, 'Bob')
member = create_user(:member, 'Charlie')
puts admin.role # Output: Admin
puts guest.role # Output: Guest
puts member.role # Output: MemberIssues with this implementation:
- Poor Maintainability: The create_user method becomes hard to maintain as the logic for creating different types of users grows. Any change in the initialization logic must be manually updated in multiple places.
- Code Duplication: Common initialization logic for different user types may be duplicated, leading to code bloat.
- Violation of Single Responsibility Principle: The client code is responsible for both the creation and the business logic, making it harder to manage.
- Scalability: Adding a new user type requires modifying the create_user method, which increases the risk of introducing bugs.
Factory Pattern
The Factory pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. This is particularly useful when the exact type of the object to be created isn't known until runtime.
Key Characteristics:
- Encapsulation of Object Creation: Encapsulates the object creation process to separate it from the code that uses the objects.
- Flexibility: Makes it easy to introduce new types of objects without changing the existing code.
- Single Responsibility Principle: Adheres to the Single Responsibility Principle by separating the object creation logic from the business logic.
Implementing Factory Pattern in Ruby
Let's implement a simple factory for creating different types of user accounts.
// language: ruby
class User
attr_accessor :name, :role
def initialize(name, role)
@name = name
@role = role
end
end
class Admin < User
def initialize(name)
super(name, 'Admin')
# Additional admin-specific initialization
end
end
class Guest < User
def initialize(name)
super(name, 'Guest')
# Additional guest-specific initialization
end
end
class Member < User
def initialize(name)
super(name, 'Member')
# Additional member-specific initialization
end
end
class UserFactory
def self.create_user(type, name)
case type
when :admin
Admin.new(name)
when :guest
Guest.new(name)
when :member
Member.new(name)
else
raise "Unknown user type: #{type}"
end
end
end
# Usage
admin = UserFactory.create_user(:admin, 'Alice')
guest = UserFactory.create_user(:guest, 'Bob')
member = UserFactory.create_user(:member, 'Charlie')
puts admin.role # Output: Admin
puts guest.role # Output: Guest
puts member.role # Output: MemberMore Practical Examples
1. Shape Creation
A shape creation system can use the Factory pattern to create different types of shapes such as Circle, Square, and Triangle.
// language: ruby
class Shape
def draw
raise 'Abstract method called'
end
end
class Circle < Shape
def draw
puts "Drawing a Circle"
end
end
class Square < Shape
def draw
puts "Drawing a Square"
end
end
class Triangle < Shape
def draw
puts "Drawing a Triangle"
end
end
class ShapeFactory
def self.create_shape(type)
case type
when :circle
Circle.new
when :square
Square.new
when :triangle
Triangle.new
else
raise "Unknown shape type: #{type}"
end
end
end
# Usage
circle = ShapeFactory.create_shape(:circle)
circle.draw # Output: Drawing a Circle
square = ShapeFactory.create_shape(:square)
square.draw # Output: Drawing a Square
triangle = ShapeFactory.create_shape(:triangle)
triangle.draw # Output: Drawing a Triangle2. Notification System
A notification system can use the Factory pattern to create different types of notifications such as Email, SMS, and Push.
// language: ruby
class Notification
def send_message(message)
raise 'Abstract method called'
end
end
class EmailNotification < Notification
def send_message(message)
puts "Sending Email: #{message}"
end
end
class SMSNotification < Notification
def send_message(message)
puts "Sending SMS: #{message}"
end
end
class PushNotification < Notification
def send_message(message)
puts "Sending Push Notification: #{message}"
end
end
class NotificationFactory
def self.create_notification(type)
case type
when :email
EmailNotification.new
when :sms
SMSNotification.new
when :push
PushNotification.new
else
raise "Unknown notification type: #{type}"
end
end
end
# Usage
email = NotificationFactory.create_notification(:email)
email.send_message("Hello via Email") # Output: Sending Email: Hello via Email
sms = NotificationFactory.create_notification(:sms)
sms.send_message("Hello via SMS") # Output: Sending SMS: Hello via SMS
push = NotificationFactory.create_notification(:push)
push.send_message("Hello via Push Notification") # Output: Sending Push Notification: Hello via Push NotificationConclusion
The Factory pattern is a powerful tool for managing object creation, ensuring that the creation logic is encapsulated and making it easy to introduce new types of objects without modifying existing code.
Happy coding!
Related blogs
Hello Golang: My First Steps to the Language
I’ve worked with Ruby in several projects, which is defined as "a programmer’s best friend" for its clean and English-like syntax. While my back-end experience is rooted in the Ruby on Rails framework, I prefer TypeScript for building CLI tools and s...
Software Engineer
Software Engineer
Make Our Utils Functions Immutable
I chose JavaScript for this blog because it has both mutable and immutable objects and methods, while language like Haskell enforces immutability everywhere1. Mutable vs Immutable Objects and MethodsWhen we say an object is immutable, we mean i...
Software Engineer
Software Engineer