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: Member
Issues 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: Member
More 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 Triangle
2. 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 Notification
Conclusion
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!
Created at
2024-09-21 15:38:03 +0700
Related blogs
One Design Pattern a Week: Week 3
Welcome back to my "One Design Pattern a Week" series!
Try to solve this real problem: Incompatible InterfacesImagine you're developing a large web ap...
Software Engineer
Software Engineer
Design Pattern
Design Pattern
2024-09-21 15:53:29 +0700
How Google achieves seamless SSO across multiple domains like Gmail and Youtube?
Hey there! Ever wondered how you can log into Gmail and then magically find yourself logged into YouTube, Google Drive, and all other Google services ...
Software Engineer
Software Engineer
2024-09-24 22:52:06 +0700