Design Pattern - Abstract Factory
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, ...
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
:
- CÁC NGUYÊN TẮC TRONG DESIGN PATTERN