Design Pattern - Abstract Factory
Bài đăng này đã không được cập nhật trong 3 năm
Tiếp theo bài viết Design Pattern - Factory, chúng ta sẽ tiếp tục tìm hiểu sâu hơn về các cách sử dụng khác của Factory với Ruby.
Parameterized Factory
Một vấn đề lập trình viên thường gặp phải đó là việc phải mở rộng chuơng trình của mình để tương thích với nhiều yêu cầu hoặc nhiều loại data, object hơn. Chẳng hạn với chương trình Class Pond ở bài viết trước, chúng ta muốn có thêm các lớp thực vật cây cỏ trong chuơng trình như:
class Algae
def initialize(name)
@name = name
end
def grow
puts("The Algae #{@name} soaks up the sun and grows")
end
end
class WaterLily
def initialize(name)
@name = name
end
def grow
puts("The water lily #{@name} floats, soaks up the sun, and grows")
end
end
thì ta buộc phải thêm tham số vào Class Pond.
class Pond
def initialize(number_animals, number_plants)
@animals = []
number_animals.times do |i|
animal = new_animal("Animal#{i}")
@animals << animal
end
@plants = []
number_plants.times do |i|
plant = new_plant("Plant#{i}")
@plants << plant
end
end
def simulate_one_day
@plants.each {|plant| plant.grow }
@animals.each {|animal| animal.speak}
@animals.each {|animal| animal.eat}
@animals.each {|animal| animal.sleep}
end
end
Chúng ta cũng cần sửa lại các subclass nữa
class DuckWaterLilyPond < Pond
def new_animal(name)
Duck.new(name)
end
def new_plant(name)
WaterLily.new(name)
end
end
class FrogAlgaePond < Pond
def new_animal(name)
Frog.new(name)
end
def new_plant(name)
Algae.new(name)
end
end
Có vẻ chúng ta đang gặp một vấn đề ở đây, đó là nếu như yêu cầu không phải là bổ sung thêm 1 loại mà là 10 hay 20 loại thì sao ?
Có một giải pháp tốt hơn là đó là sử dụng 1 Factory duy nhất nhưng nó sẽ nhận tham số truyền vào để sử dụng method tuơng ứng như sau:
class Pond
def initialize(number_animals, number_plants)
@animals = []
number_animals.times do |i|
animal = new_organism(:animal, "Animal#{i}")
@animals << animal
end
@plants = []
number_plants.times do |i|
plant = new_organism(:plant, "Plant#{i}")
@plants << plant
end
end
# ...
end
class DuckWaterLilyPond < Pond
def new_organism(type, name)
if type == :animal
Duck.new(name)
elsif type == :plant
WaterLily.new(name)
else
raise "Unknown organism type: #{type}"
end
end
end
Cách này giúp chúng ta giảm được số lượng code trong source, thay vì viết thêm method mới, ta chỉ việc sửa đổi 1 method duy nhất trong subclass tuơng ứng.
Classes Are Just Objects, Too
Như ví dụ trên, mặc dù đã loại bỏ được việc phải thêm method nhưng chúng ta vẫn phải thêm các subclass tương ứng cho từng loại, như class DuckWaterLilyPond
hoặc FrogAlgaePond
. Việc này khá bất tiện và khó quản lý nếu ta cần phải mô phỏng nhiều loại thực, động vật trong Pond.
Có một cách khá đơn giản, đó là cứ coi các class Frog, Duck, WaterLily và Algae chỉ là những objects. Chúng ta có thể loại bỏ các subclass và thay vào đó là lưu các class thực, động vật vào các biến instance của class Pond.
class Pond
def initialize(number_animals, animal_class, number_plants, plant_class)
@animal_class = animal_class
@plant_class = plant_class
@animals = []
number_animals.times do |i|
animal = new_organism(:animal, "Animal#{i}")
@animals << animal
end
@plants = []
number_plants.times do |i|
plant = new_organism(:plant, "Plant#{i}")
@plants << plant
end
end
def simulate_one_day
@plants.each {|plant| plant.grow}
@animals.each {|animal| animal.speak}
@animals.each {|animal| animal.eat}
@animals.each {|animal| animal.sleep}
end
def new_organism(type, name)
if type == :animal
@animal_class.new(name)
elsif type == :plant
@plant_class.new(name)
else
raise "Unknown organism type: #{type}"
end
end
end
Với cách này, số lượng subclass đã được loại bỏ hoàn toàn, nhưng class Pond vẫn không quá phức tạp.
Mở rộng chương trình
Với mô hình Pond, chương trình của chúng ta vẫn cần mở rộng để có thể mô phỏng nhiều loại môi trường sống khác, có thể đó là mô hình của một khu rừng jungle
chẳng hạn. Môi trường jungle
cũng cần có các động thực vật riêng, chúng ta cần xây dựng class tigers
và trees
.
class Tree
def initialize(name)
@name = name
end
def grow
puts("The tree #{@name} grows tall")
end
end
class Tiger
def initialize(name)
@name = name
end
def eat
puts("Tiger #{@name} eats anything it wants.")
end
def speak
puts("Tiger #{@name} Roars!")
end
def sleep
puts("Tiger #{@name} sleeps anywhere it wants.")
end
end
Tiếp theo, ta cần class cho môi trường jungle
. Có thể thấy jungle
và pond
rất tương đồng, vì thế chúng ta có thể sử dụng lại class Pond
cho jungle
này. Việc này khá đơn giản, ta chỉ cần đổi tên class Pond
sang 1 tên chung, Habitat
chẳng hạn, sau đó dùng lại code như sau:
jungle = Habitat.new(1, Tiger, 4, Tree)
jungle.simulate_one_day
pond = Habitat.new( 2, Duck, 4, WaterLily)
pond.simulate_one_day
Tuy nhiên, class mới Habitat
này lại không có sự liên kết hay ràng buộc giữa động, thực vật với nhau. Ví dụ, Tiger
và Lily
thì không bao giờ sống trong cùng một môi trường được.
unstable = Habitat.new( 2, Tiger, 4, WaterLily)
Giải pháp cho vấn đề này là thay vì xác định cụ thể các sinh vật sống trong môi trường Habitat
, ta sẽ đi qua một đối tượng duy nhất, đối tượng này sẽ tạo ra một tập các sinh vật tương thích với môi trường sống.
Đối tượng duy nhất này được gọi là Abstract Factory.
Với yêu cầu trên, chúng ta sẽ xây dựng 2 abstract factory.
class PondOrganismFactory
def new_animal(name)
Frog.new(name)
end
def new_plant(name)
Algae.new(name)
end
end
class JungleOrganismFactory
def new_animal(name)
Tiger.new(name)
end
def new_plant(name)
Tree.new(name)
end
end
Và class Habitat
sẽ được thay đổi lại như sau:
class Habitat
def initialize(number_animals, number_plants, organism_factory)
@organism_factory = organism_factory
@animals = []
number_animals.times do |i|
animal = @organism_factory.new_animal("Animal#{i}")
@animals << animal
end
@plants = []
number_plants.times do |i|
plant = @organism_factory.new_plant("Plant#{i}")
@plants << plant
end
end
# Rest of the class...
end
jungle = Habitat.new(1, 4, JungleOrganismFactory.new)
jungle.simulate_one_day
pond = Habitat.new( 2, 4, PondOrganismFactory.new)
pond.simulate_one_day
Mô hình UML cho Abstract Factory pattern
Tham khảo
Github (updating):https://github.com/ducnhat1989/design-patterns-in-ruby
Sách: “DESIGN PATTERNS IN RUBY” của tác giả Russ Olsen
Bài viết liên quan:
All rights reserved