Tổng quan về Design Pattern

23 Dạng Thức Triển Khai Design Patterns trong OOP

Posted by Box XV on June 1, 2020. 30 min read.

Design Patterns là các dạng thức triển khai code được các lập trình viên OOP giàu kinh nghiệm đúc kết, giúp nhanh chóng xử lý nhiều vấn đề phổ biến mà chúng ta thường gặp trong quá trình viết code. Các giải pháp này được ghi nhận qua thời gian dài và được chiêm nghiệm bởi rất nhiều nhà phát triển phần mềm.

Design Patterns

Giới thiệu Bộ Tứ GoF (Gang of Four)

Năm 1994, có bốn tác giả lớn là Erich Gamma, Richard Helm, Ralph Johnson, và John Vlissides đã đồng xuất bản một cuốn sách có tên là Design Patterns - Elements of Reusable Object-Oriented Software (Dịch nôm na là: Các dạng thức triển khai - Những yếu tố đặc trưng của code OOP có thể tái sử dụng). Cuốn sách này đã đặt nền móng mở đầu cho thuật ngữ Design Patterns trong việc phát triển phần mềm.

Các tác giả này được biết đến với tên gọi là Bộ Tứ hay GoF (Gang of Four). Theo họ cho biết, Design Patterns chủ yếu được xây dựng dựa theo các tiêu chí sau của thiết kế hướng đối tượng:

  • Lập trình hướng đến 1 interface (giao diện) chứ không phải là triển khai mở rộng interface đó.
  • Ưu tiên việc tổ hợp Object hơn so với việc sử dụng hình thức kế thừa.

Ứng dụng của Design Patterns

Design Patterns có 2 ứng dụng chính trong việc phát triển phần mềm.

1. Tạo ra một nền tảng chung cho các nhà phát triển

Các Patterns cung cấp một giải pháp chung mang tính chất tiêu chuẩn mặt bằng và đặc trưng cho nhiều trường hợp. Ví dụ, Singleton được các nhà phát triển biết đến và sử dụng làm giải pháp chung để tạo ra một Object đơn nguyên duy nhất của một Class. Và họ cũng sẽ nói ra cái tên này khi được hỏi về giải pháp thực hiện một công việc như vậy.

2. Có thể được xem là các giải pháp tốt nhất

Các Patterns cũng được phát triển qua thời gian dài và mang đến những giải pháp tốt nhất cho nhiều vấn đề thường gặp khi phát triển phần mềm. Việc học các dạng thức triển khai này sẽ giúp cho các nhà phát triển phần mềm chưa có kinh nghiệm có thể học được cách thiết kế phần mềm dễ dàng hơn.

Có tất cả bao nhiêu dạng thức đã được công nhận

Nguyên gốc theo cuốn Design Patterns - Elements of Reusable Object-Oriented Software thì có tất cả 23 dạng thức triển khai và có thể được chia ra làm 3 nhóm là Khởi Tạo, Kiến Trúc, và Hành Vi - dựa trên công dụng của các dạng thức. Tuy nhiên thì ở thời điểm hiện tại con số này đã có một chút sự tăng tiến và chúng ta còn có thêm một nhóm Patterns mới là J2EE Patterns. Chúng ta sẽ cùng nhau tìm hiểu chi tiết hơn trong các bài viết tiếp theo.

  • Các Patterns Khởi Tạo Creational Patterns
    • Các Patterns này cung cấp giải pháp khởi tạo các Object trong khi ẩn đi logic của tiến trình khởi tạo, thay vì khởi tạo Object trực tiếp bằng toán tử new. Cách khởi tạo này giúp đem lại sự linh động tốt hơn trong việc quyết định những Object này cần được tạo ra trong trường hợp cụ thể.
  • Các Patterns Kiến Trúc Structural Patterns
    • Các Patterns này quan tâm tới vấn đề tổ hợp các Class và Object. Trong đó, tính kế thừa được áp dụng để tổ hợp các giao diện và định nghĩa cách thức tổ hợp các Object để có thêm tính năng mới.
  • Các Patterns Hành Vi Behavioral Patterns
    • Các Patterns này quan tâm chủ yếu tới yếu tố giao tiếp giữa các Object.
  • Các Patterns J2EE
    • Các Patterns này đặc biệt quan tâm tới tầng biểu thị và được đặt nhận diện bởi Sun Java Center.
  • Factory Pattern
  • Abstract Factory
  • Singleton Pattern
  • Builder Pattern
  • Prototype Pattern
  • Adapter Pattern
  • Bridge Pattern
  • Filter Pattern
  • Composite Pattern
  • Decorator Pattern
  • Facade Pattern
  • Flyweight Pattern

Nhân tiện dịch tới đây thì mình mới nhớ ra một vấn đề quan trọng, đó là các ví dụ triển khai được sử dụng trong các bài viết tiếp theo sẽ là triển khai trên Java. Lý do mà Tutorialspoint họ chọn Java để làm Tút có lẽ cũng dễ hiểu bởi vì sự phổ biến của ngôn ngữ này và quan trọng hơn cả đó là cú pháp rườm rà, cứng nhắc, khó có thể có sự nhầm lẫn gì giữa người viết và người đọc code.

Vì vậy nên nếu như bạn chưa từng nói chuyện với máy tính bằng Java bao giờ thì rất có thể bạn sẽ cần một khóa cơ bản cấp tốc trước khi đọc bài viết tiếp theo đấy nhé.

Factory Pattern

Factory là một trong những dạng thức triển khai được sử dụng nhiều nhất và được xếp vào nhóm các dạng thức Khởi Tạo.

Trong phép triển khai Factory, chúng ta tạo ra các object mà không để mở logic khởi tạo cho phía client (đoạn code gửi yêu cầu và sử dụng object được khởi tạo). Thêm vào đó, việc tham chiếu tới object được khởi tạo sẽ được thực hiện thông qua một interface (giao diện) chung thay vì sử dụng class cụ thể.

Áp dụng triển khai

Design Patterns

  • Chúng ta sẽ tạo ra 01 interface chung có tên là Shape cho các class mô tả hình 2D (hình tròn, tam giác, hình vuông) và các class cụ thể triển khai interface này.
  • Ở bước tiếp theo, 01 class có tên là Factory sẽ được định nghĩa.
  • Cuối cùng là main của chương trình sẽ sử dụng Factory để yêu cầu khởi tạo 1 Shape (hình 2D). main sẽ truyền vào thông tin về kiểu Shape muốn khởi tạo (circle / triangle / square - hình tròn / tam giác / hình vuông).

Về mặt quản lý code, chúng ta sẽ có 01 package được đặt tên là shapefactory. Package này sẽ chứa:

  • 01 class Factory mở public
  • 01 interface Shape mở public
  • 03 class triển khai Shape để default

Điều này có nghĩa là code client ở phía bên ngoài package sẽ hoàn toàn không biết tới 03 class triển khai Shape mà chỉ có thể gọi Factory để khởi tạo các Shape và tham chiếu tới các object được tạo ra thông qua interface.

Bước 1

Tạo 01 interface có tên là Shape mở public.

shapefactory/Shape.java

package shapefactory;

public interface Shape {
   void draw();
}

Bước 2

Tạo các class cụ thể triển khai interface với access modifier đặt default, không mở public.

shapefactory/Circle.java

package shapefactory;

class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Một hình tròn.");
   }
}

shapefactory/Triangle.java

package shapefactory;

class Triangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Một hình tam giác.");
   }
}

shapefactory/Square.java

package shapefactory;

class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Một hình vuông.");
   }
}

Bước 3

Tạo 01 class Factory để sản xuất các object thực thể với thông tin được cung cấp từ code client.

shapefactory/Factory.java

package shapefactory;

public class Factory {
   public Shape createShape(String type) {
      if (type == null)                        return null;
      if (type.equalsIgnoreCase("circle"))     return new Circle();
      if (type.equalsIgnoreCase("triangle"))   return new Triangle();
      if (type.equalsIgnoreCase("square"))     return new Square();
      else                                     return null;
   }
}

Bước 4

Sử dụng Factory trong code client ở main để yêu cầu khởi tạo các object bằng cách truyền vào thông tin về loại hình của Shape.

PatternDemo.java

import shapefactory.Factory;
import shapefactory.Shape;

public class FactoryPatternDemo {
   public static void main(String[] args) {
      Factory shapeFactory = new Factory();

      // Yêu cầu khởi tạo một `object` hình tròn và gọi `draw()` để vẽ
      Shape circle = shapeFactory.createShape("circle");
      circle.draw();

      // Yêu cầu khởi tạo một `object` hình tam giác và gọi `draw()` để vẽ
      Shape triangle = shapeFactory.createShape("triangle");
      triangle.draw();

      // Yêu cầu khởi tạo một `object` hình vuông và gọi `draw()` để vẽ
      Shape square = shapeFactory.createShape("square");
      square.draw();
   }
}

Bước 5

Kiểm chứng lại kết quả được in ra ở console.

Một hình tròn.
Một hình tam giác.
Một hình vuông.

Abstract Factory

Abstract Factory là dạng thức được đưa ra để làm việc xoay quanh trọng tâm tạo ra một “siêu” Factory đóng vai trò tạo ra các Factory khác. Abstract Factory cũng được xếp vào nhóm các dạng thức Khởi Tạo.

Trong phép triển khai Abstract Factory, 01 interface được sử dụng để đảm nhiệm vai trò tạo ra 01 Factory của các object liên quan mà không cần chỉ ra đặc định class của các object đó. Mỗi Factory được khởi tạo sẽ có thể giúp chúng ta khởi tạo các object thực thể như đã biết trong bài Factory Pattern.

Áp dụng triển khai

Design Patterns

  • Chúng ta sẽ tạo ra 01 interface Shape mở public.
  • 04 class triển khai interface này.
  • Ở bước tiếp theo, 01 class AbstractFactory sẽ được tạo ra.
  • Kế đến là 02 class Factory mở rộng AbstractFactory.
  • Sau đó là một phương thức static để khởi tạo các object Factory.
  • Cuối cùng, trong mainPatternDemo, chúng ta sử dụng phương thức static để tạo ra các Factory. Rồi sau đó truyền vào thông tin về kiểu object hình học muốn tạo ra (triangle / square).

Design Patterns

Về mặt quản lý code, chúng ta sẽ có 01 package được đặt tên là shapefactory. Package này sẽ chứa các thành phần public tới code client bao gồm interface Shapeclass AbstractFactory. Tất cả các thành phần còn lại bao gồm 04 class triển khai Shape02 class mở rộng AbstractFactory sẽ đều sử dụng access modifierdefault.

Do đó, toàn bộ tiến trình logic để khởi tạo các Factory cũng như các Shape đều không để mở về phía code client và các tham chiếu đều được thực hiện thông qua abstract classinterface.

Bước 1

Tạo 01 interface Shape mở public.

shapefactory/Shape.java

package shapefactory;

public interface Shape {
   void draw();
}

Bước 2

Tạo các class triển khai giao diện Shape.

shapefactory/NormalTriangle.java

package shapefactory;

class NormalTriangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Một hình tam giác bình thường.");
   }
}

shapefactory/NormalSquare.java

package shapefactory;

class NormalSquare implements Shape {
   @Override
   public void draw() {
      System.out.println("Một hình vuông bình thường.");
   }
}

shapefactory/RoundedTriangle.java

package shapefactory;

class RoundedTriangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Một hình tam giác có các góc bo tròn.");
   }
}

shapefactory/RoundedSquare.java

package shapefactory;

class RoundedSquare implements Shape {
   @Override
   public void draw() {
      System.out.println("Một hình vuông có các góc bo tròn.");
   }
}

Bước 3

Tạo 01 class abstract để khái quát hóa các Factory.

shapefactory/AbstractFactory.java

package shapefactory;

public abstract class AbstractFactory {
   public abstract Shape createShape(String type);
}

Bước 4

Tạo các class mở rộng AbstractFactory giúp khởi tạo các Shape với thông tin được cung cấp.

shapefactory/NormalFactory.java

package shapefactory;

class NormalFactory extends AbstractFactory {
   @Override
   public Shape createShape(String type) {
      if (type == null)                        return null;
      if (type.equalsIgnoreCase("triangle"))   return new NormalTriangle();
      if (type.equalsIgnoreCase("square"))     return new NormalSquare();
      else                                     return null;
   }
}

shapefactory/RoundedFactory.java

package shapefactory;

class RoundedFactory extends AbstractFactory {
   @Override
   public Shape createShape(String type) {
      if (type == null)                        return null;
      if (type.equalsIgnoreCase("triangle"))   return new RoundedTriangle();
      if (type.equalsIgnoreCase("square"))     return new RoundedSquare();
      else                                     return null;
   }
}

Bước 5

Thêm phương thức static cho AbstractFactory để khởi tạo các object Factory cụ thể.

shapefactory/AbstractFactory

package shapefactory;

public abstract class AbstractFactory {
   public static AbstractFactory createFactory(boolean rounded) {
      if (rounded)      return new RoundedFactory();
      else              return new NormalFactory();
   }

   abstract public Shape createShape(String type);
}

Bước 6

Sử dụng phương thức static vừa rồi để khởi tạo các objectFactory. Sau đó, sử dụng các Factory để khởi tạo các object hình học bằng cách truyền vào thông tin về kiểu Shape.

PatternDemo.java

import shapefactory.AbstractFactory;
import shapefactory.Shape;

public class DesignPatterns {
   public static void main(String[] args) {
      // Tạo ra một Factory cho các hình 2D bình thường
      AbstractFactory normalFactory = AbstractFactory.createFactory(false);

      // Yêu cầu khởi tạo 1 hình tam giác bình thường và gọi `draw()`
      Shape normalTriangle = normalFactory.createShape("triangle");
      normalTriangle.draw();

      // Yêu cầu khởi tạo 1 hình vuông bình thường và gọi `draw()`
      Shape normalSquare = normalFactory.createShape("square");
      normalSquare.draw();

      // Tạo ra một Factory cho các hình 2D bo tròn góc
      AbstractFactory roundedFactory = AbstractFactory.createFactory(true);

      // Yêu cầu khởi tạo 1 hình tam giác bo góc và gọi `draw()`
      Shape roundedTriangle = roundedFactory.createShape("triangle");
      roundedTriangle.draw();

      // Yêu cầu khởi tạo 1 hình vuông bình thường và gọi `draw()`
      Shape roundedSquare = roundedFactory.createShape("square");
      roundedSquare.draw();
   }
}

Bước 7

Kiểm chứng lại kết quả được in ra ở console.

Một hình tam giác bình thường.
Một hình vuông bình thường.
Một hình tam giác với các góc bo tròn.
Một hình vuông với các góc bo tròn.

Singleton Pattern

Singleton là một trong số những dạng thức triển khai đơn giản nhất của OOP và được xếp vào nhóm các dạng thức Khởi Tạo. Singleton giúp chúng ta đảm bảo chỉ có 01 object đơn duy nhất của 01 class đặc định được tạo ra trong suốt thời gian phần mềm hoạt động. Class này có cung cấp một phương thức để truy xuất object đơn nguyên này và không cho phép khởi tạo object mới tương tự ở bất kỳ nơi nào khác.

Áp dụng triển khai

Design Patterns

  • Chúng ta sẽ tạo ra 01 class có tên là Thing. Bên trong class này sẽ có hàm khởi tạo được khóa private và một thuộc tính static để lưu tham chiếu của object duy nhất được tạo ra.
  • Thing có cung cấp một phương thức static để chia sẻ tham chiếu tới object duy nhất cho phần code client sử dụng.
  • Cuối cùng là mainPatternDemo sẽ sử dụng Thing để hỏi truy xuất tới object duy nhất và hiển thị tin nhắn của object đó.

Bước 1

Tạo 01 class singleton có tên là Thing.

singleton/Thing.java

package singleton;

public class Thing {
   private static final Thing instance = new Thing();

   private Thing() {
      // khóa `private` phương thức khởi tạo với `new` từ bên ngoài `class`
   }

   public static Thing getInstance() {
      return instance;
   }

   public void showMessage() {
      System.out.println("Tin nhắn từ `object` duy nhất của `Thing`!");
   }
}

Bước 2

Truy xuất tới object duy nhất và hiển thị tin nhắn.

PatternDemo.java

import singleton.Thing;

public class PatternDemo {
   public static void main(String[] args) {
      // Lỗi biên dịch vì phương thức khởi tạo không khả dụng
      // Thing only = new Thing();

      // Truy xuất object đơn nguyên duy nhất
      Thing only = Thing.getInstance();

      // Hiển thị tin nhắn
      only.showMessage();
   }
}

Bước 3

Kiểm chứng lại kết quả được in ra ở console.

Tin nhắn t`object` duy nhất của `Thing`!

Builder Pattern

Builder Pattern giúp chúng ta xây dựng một object phức tạp từ những object đơn giản qua từng bước. Builder Pattern được xếp vào nhóm các pattern Khởi .

Một class Builder luôn luôn xây dựng object thực thể cuối cùng qua từng bước. Bản thân Builder luôn luôn độc lập và không lệ thuộc vào các object khác.

Áp dụng triển khai

Design Patterns

  • Chúng ta sẽ tạo 01 abstract class Item để biểu thị hàng hóa và các class để mô tả các thực thể.
  • Giao diện Packing thể hiện cách đóng gói của sản phẩm.
  • Sau đó, chúng ta tạo ra 01 class Meal có chứa một mảng Item.
  • Và tạo 01 class Builder để thực hiện công việc tổ hợp nên các object Meal khác nhau bằng việc kết hợp các Item.
  • Cuối cùng là code main sử dụng Builder để xây dựng một Meal.

Về mặt quản lý code, chúng ta sẽ có 01 package có tên là mealbuilder. Phần code client trên main sẽ chỉ cần tham chiếu tới BuilderMeal do đó sẽ chỉ có 02 class này được mở public. Tất cả các thành phần còn lại của package sẽ đều được đặt access modifierdefault.

Bước 1

Tạo abstract class Iteminterface Packing.

mealbuilder/Item.java

package mealbuilder;

abstract class Item {
   private final String  name;
   private final float   price;
   private final Packing packing;

   Item(
      String  name,
      float   price,
      Packing packing
   ) {
      this.name    = name;
      this.price   = price;
      this.packing = packing;
   }

   public String getName() {
      return name;
   }

   public float getPrice() {
      return price;
   }

   public Packing getPacking() {
      return packing;
   }
}

mealbuilder/Packing.java

package mealbuilder;

interface Packing {
   public String pack();
}

Bước 2

Tạo các class triển khai của Packing.

mealbuilder/Wrapper.java

package mealbuilder;

class Wrapper
implements Packing {
   @Override
   public String pack() {
      return "Wrapper";
   }
}

mealbuilder/Bottle.java

package mealbuilder;

class Bottle
implements Packing {
   @Override
   public String pack() {
      return "Bottle";
   }
}

Bước 3

Tạo các class Burger mở rộng Item.

mealbuilder/Burger.java

package mealbuilder;

public abstract class Burger extends Item {
   Burger(
      String  name,
      float   price,
      Packing packing
   ) {
      super(name, price, packing);
   }
}

mealBuilder/BurgerForVeg.java

package mealbuilder;

class BurgerForVeg extends Burger {
   BurgerForVeg() {
      super("Bugger for Veg", 25.0f, new Wrapper());
   }
}

mealBuilder/BurgerNonVeg.java

package mealbuilder;

class BurgerNonVeg extends Burger {
   BurgerNonVeg() {
      super("Burger non-Veg", 50.5f, new Wrapper());
   }
}

Bước 4

Tạo các class Drink mở rộng Item tương tự như Burger.

mealbuilder/Drink.java

package mealbuilder;

public abstract class Drink extends Item {
   Drink(
      String  name,
      float   price,
      Packing packing
   ) {
      super(name, price, packing);
   }
}

mealbuilder/DrinkCoke.java

package mealbuilder;

class DrinkCoke extends Drink {
   DrinkCoke() {
      super("Coke", 30.0f, new Bottle());
   }
}

mealbuilder/DrinkPepsi.java

package mealbuilder;

class DrinkPepsiextends Drink {
   DrinkPepsi() {
      super("Pepsi", 35.0f, new Bottle());
   }
}

Bước 5

Tạo class Meal chứa các object Item.

mealbuilder/Meal.java

package mealbuilder;

import java.util.ArrayList;
import java.util.List;

public class Meal {
   private List<Item> itemList = new ArrayList<Item>();

   public void addItem(Item newItem) {
      itemList.add(newItem);
   }

   public float getCost() {
      float totalCost = 0.0f;
      for (Item i : itemList) { totalCost += i.getPrice(); }
      return totalCost;
   }

   public void showItems() {
      for (Item i : itemList) {
         System.out.println(i.getName());
         System.out.println("   + Packing: " + i.getPacking().pack());
         System.out.println("   + Price: " + i.getPrice());
      }
   }
}

Bước 6

Tạo class Builder giúp xây dựng các object Meal.

mealbuilder/Builder.java

package mealbuilder;

public class Builder {
   public Meal prepareMealForVeg() {
      Meal mealForVeg = new Meal();
      mealForVeg.addItem(new BurgerForVeg());
      mealForVeg.addItem(new DrinkCoke());
      return mealForVeg;
   }

   public Meal prepareMealNonVeg() {
      Meal mealNonVeg = new Meal();
      mealNonVeg.addItem(new BurgerNonVeg());
      mealNonVeg.addItem(new DrinkPepsi());
      return mealNonVeg;
   }
}

Bước 7

Code main tại PatternDemo để chạy thử pattern.

PatternDemo.java

import mealbuilder.Builder;
import mealbuilder.Meal;

public class PatternDemo {
   public static void main(String[] args) {
      Builder mealBuilder = new Builder();

      Meal mealForVeg = mealBuilder.prepareMealForVeg();
      System.out.println("=== Meal for Veg ========");
      mealForVeg.showItems();
      System.out.println("Total cost: " + mealForVeg.getCost());

      Meal mealNonVeg = mealBuilder.prepareMealNonVeg();
      System.out.println("=== Meal non-Veg ========");
      mealNonVeg.showItems();
      System.out.println("Total cost: " + mealNonVeg.getCost());
   }
}

Bước 8

Kiểm chứng lại thông tin được in ra ở console.

=== Meal for Veg ========
Bugger for Veg
   + Packing: Wrapper
   + Price: 25.0
Coke
   + Packing: Bottle
   + Price: 30.0
Total cost: 55.0
=== Meal non-Veg ========
Burger non-Veg
   + Packing: Wrapper
   + Price: 50.5
Pepsi
   + Packing: Bottle
   + Price: 35.0
Total cost: 85.5

Prototype Pattern

Prototype Pattern thường được sử dụng để nhân bản một object với lưu ý về mặt hiệu năng xử lý. Protype Pattern được xếp vào nhóm các pattern Khởi Tạo.

Prototype Pattern sử dụng một giao diện kiểu mẫu để tạo ra một bản sao clone của một object. Pattern này được sử dụng khi mà việc khởi tạo một object trực tiếp gặp nhiều khó khăn và tiêu tốn tài nguyên về thời gian.

Ví dụ, một object được tạo ra sau một lần mở kết nối tới CSDL có thể được cho là đắt đỏ. Chúng ta có thể ghi đệm lại object này vào một cache, và lần tới khi yêu cầu được gửi đến, chúng ta có thể tạo ra một bản sao clone và trả về, đồng thời cập nhật CSDL, thay vì truy xuất lại thông tin từ CSDL và khởi tạo lại object từ đầu.

Áp dụng triển khai

Design Patterns

  • Chúng ta sẽ tạo 01 abstract class Shape.
  • Và các class mô tả thực thể mở rộng Shape.
  • Ở bước tiếp theo, 01 class Cache ghi đệm các object đã được tạo ra sẽ được định nghĩa.
  • Cuối cùng là code mainPatternDemo sẽ sử dụng Cache để yêu cầu lấy các object Shape.

Về mặt quản lý code, chúng ta sẽ có 01 package tên là shapeprototype. Code sử dụng ở phía client sẽ chỉ có thể tham chiếu tới CacheShape được mở public. Các thành phần còn lại của shapeprototype đều được đặt access modifierdefault.

Bước 1

Tạo 01 abstract class Shape triển khai interface Cloneable.

shapeprototype/Shape.java

package shapeprototype;

public abstract class Shape
implements Cloneable {
   private String id;
   protected String type;

   public abstract void draw();

   public String getType() {
      return type;
   }

   public void setId(String id) {
      this.id = id;
   }

   public String getId() {
      return id;
   }

   @Override
   public Object clone() {
      Object clone = null;

      try {
         clone = super.clone();
      }
      catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }

      return clone;
   }
}

Bước 2

Tạo các class mô tả thực thể mở rộng Shape.

shapeprototype/Circle.java

package shapeprototype;

class Circle extends Shape {
   public Circle() {
      type = "circle";
   }

   @Override
   public void draw() {
      System.out.println("Một hình tròn.");
   }
}

shapeprototype/Triangle.java

package shapeprototype;

class Triangle extends Shape {
   public Triangle() {
      type = "triangle";
   }

   @Override
   public void draw() {
      System.out.println("Một hình tam giác.");
   }
}

shapeprototype/Square.java

package shapeprototype;

class Square
extends Shape {
   public Square() {
      type = "square";
   }

   @Override
   public void draw() {
      System.out.println("Một hình vuông.");
   }
}

Bước 3

Tạo 01 class Cache để truy vấn các object Shape từ CSDL và ghi đệm vào một.

shapeprototype/Cache.java

package shapeprototype;

import java.util.Hashtable;

public class Cache {
   private static Hashtable<String, Shape> cachedShapes = new Hashtable<String, Shape>();

   public static Shape getShape(String id) {
      return (Shape) cachedShapes.get(id).clone();
   }

   public static void load() {
      Circle circle = new Circle();
      circle.setId("1");
      cachedShapes.put(circle.getId(), circle);

      Triangle triangle = new Triangle();
      triangle.setId("2");
      cachedShapes.put(triangle.getId(), triangle);

      Square square = new Square();
      square.setId("3");
      cachedShapes.put(square.getId(), square);
   }
}

Bước 4

Tại PatternDemo, sử dụng Cache để tạo ra các bản sao của các object Shape được ghi đệm trước đó trong Hashtable.

PatternDemo.java

import shapeprototype.Cache;
import shapeprototype.Shape;

public class PatternDemo {
   public static void main(String[] args) {
      Cache.load();

      Shape clonedCircle = (Shape) Cache.getShape("1");
      System.out.println("=== Cloned " + clonedCircle.getType());
      clonedCircle.draw();

      Shape clonedTriangle = (Shape) Cache.getShape("2");
      System.out.println("=== Cloned " + clonedTriangle.getType());
      clonedTriangle.draw();

      Shape clonedSquare = (Shape) Cache.getShape("3");
      System.out.println("=== Cloned " + clonedSquare.getType());
      clonedSquare.draw();
   }
}

Bước 5

Kiểm chứng lại thông tin được in ra ở console.

=== Cloned circle
Một hình tròn.
=== Cloned triangle
Một hình tam giác.
=== Cloned square
Một hình vuông.

Adapter Pattern

Adapter Pattern hoạt động như một cầu nối giữa 2 giao diện không tương thích. Adapter Pattern được xếp vào nhóm các pattern Kiến Trúc.

Adapter Pattern sử dụng 01 class đơn để đảm nhiệm việc kết nối các giao diện độc lập hoặc không tương thích.

Áp dụng triển khai

Design Patterns

  • Chúng ta có 01 interface MediaPlaying01 class mô tả thực thể MediaPlayer triển khai giao diện đó. Mặc định thì MediaPlayer có thể phát các tệp định dạng Mp3.
  • Ngoài ra chúng ta còn có 01 interface AdvancedPlaying và các class triển khai giao diện bổ sung này. Các class này có thể phát các tệp Vlc hoặc Mp4.
  • Bây giờ thì chúng ta muốn MediaPlayer có thể phát được các tệp định dạng khác nữa ngoài Mp3 mặc định.
  • Để làm được điều này, chúng ta cần tạo 01 class MediaAdapter triển khai interface MediaPlaying và sử dụng các object AdvancedPlaying để phát tệp được yêu cầu.
  • MediaPlayer sẽ sử dụng MediaApdater bằng cách truyền vào đó định dạng media muốn phát mà không cần biết tới các class thực thụ được sử dụng để phát tệp.
  • Cuối cùng là PatternDemo với code main sẽ sử dụng MediaPlayer để phát các định dạng media khác nhau.

Về mặt quản lý code, chúng ta có 01 package adapterpattern. Code client trong main sẽ chỉ tham chiếu duy nhất tới MediaPlayer và trỏ interface MediaPlaying. Do đó chúng ta sẽ chỉ có duy nhất class MediaPlayerinterface MediaPlaying để mở public. Tất cả các thành phần còn lại của package đều sẽ được đặt access modifierdefault.

Bước 1

Tạo các interface để phát tệp bao gồm MediaPlayingAdvancedPlaying.

adapterpattern/MediaPlaying.java

package adapterpattern;

public interface MediaPlaying {
   public MediaPlaying play(String mediaType, String fileName);
}

adapterpattern/AdvancedPlaying.java

package adapterpattern;

interface AdvancedPlaying {
   public AdvancedPlaying playVlc(String fileName);
   public AdvancedPlaying playMp4(String fileName);
}

Bước 2

Tạo các class triển khai interface AdvancedPlaying.

adapterpattern/VlcPlayer.java

package adapterpattern;

class VlcPlayer implements AdvancedPlaying {
   @Override
   public AdvancedPlaying playVlc(String fileName) {
      System.out.println("Đang phát tệp Vlc. Tên tệp: " + fileName);
      return this;
   }

   @Override
   public AdvancedPlaying playMp4(String fileName) {
      // do nothing
      return this;
   }
}

adapterpattern/Mp4Player.java

package adapterpattern;

public class Mp4Player implements AdvancedPlaying {
   @Override
   public AdvancedPlaying playVlc(String fileName) {
      // do nothing
      return this;
   }

   @Override
   public AdvancedPlaying playMp4(String fileName) {
      System.out.println("Đang phát tệp Mp4. Tên tệp: " + fileName);
      return this;
   }
}

Bước 3

Tạo class MediaAdapter triển khai MediaPlaying.

adapterpattern/MediaAdapter.java

package adapterpattern;

public class MediaAdapter implements MediaPlaying {
   AdvancedPlaying advancedPlayer;

   public MediaAdapter(String mediaType) {
      advancedPlayer = initPlayer(mediaType);
   }

   private AdvancedPlaying initPlayer(String mediaType) {
      if (mediaType.equalsIgnoreCase("vlc"))    return new VlcPlayer();
      if (mediaType.equalsIgnoreCase("mp4"))    return new Mp4Player();
      else                                                   return null;
   }

   @Override
   public MediaPlaying play(
      String mediaType,
      String fileName
   ) {
      if (mediaType.equalsIgnoreCase("vlc")) {
         advancedPlayer.playVlc(fileName);
         return this;
      }
      if (mediaType.equalsIgnoreCase("mp4")) {
         advancedPlayer.playMp4(fileName);
         return this;
      }
      else {
         System.out.println("Định dạng không được hỗ trợ");
         return this;
      }
   }
}

Bước 4

Tạo class MediaPlayer triển khai MediaPlaying.

adapterpattern/MediaPlayer.java

package adapterpattern;

public class MediaPlayer implements MediaPlaying {
   @Override
   public MediaPlaying play(
      String mediaType,
      String fileName
   ) {
      if (mediaType.equalsIgnoreCase("mp3")) {
         System.out.println("Đang phát tệp Mp3. Tên tệp: " + fileName);
         return this;
      }
      if (
         mediaType.equalsIgnoreCase("vlc") ||
         mediaType.equalsIgnoreCase("mp4")
      ) {
         MediaAdapter adapter = new MediaAdapter(mediaType);
         adapter.play(mediaType, fileName);
         return this;
      }
      else {
         System.out.println("Kiểu tệp không hợp lệ. Định dạng `" + mediaType + "` không được hỗ trợ.");
         return this;
      }
   }
}

Bước 5

Sử dụng MediaPlayer để phát các định dạng tệp khác nhau.

PatternDemo.java

import adapterpattern.MediaPlayer;

public class PatternDemo {
   public static void main(String[] args) {
      MediaPlayer player = new MediaPlayer();
      player.play("mp3", "beyond the horizon.mp3")
            .play("mp4", "alone.mp4")
            .play("vlc", "far far away.vlc")
            .play("avi", "mind me.avi");
   }
}

Bước 6

Kiểm chứng lại kết quả in ra ở console.

Đang phát tệp Mp3. Tên tệp: beyond the horizon.mp3
Đang phát tệp Mp4. Tên tệp: alone.mp4
Đang phát tệp Vlc. Tên tệp: far far away.vlc
Kiểu tệp không hợp lệ. Định dạng `avi` không được htrợ.

Bridge Pattern

Bridge được sử dụng khi chúng ta cần tách một abstract khỏi class thực thể và khiến cho cả 2 đều trở nên linh động. Bridge được xếp vào nhóm các pattern Kiến Trúc.

Bridge sử dụng một giao diện đóng vai trò như một cầu nối giúp cho các chức năng của class thực thể trở nên độc lập và tách riêng khỏi các class tạo giao diện. Lúc này, cả 2 loại class đều có thể được chỉnh sửa linh động mà không ảnh hưởng lẫn nhau.

Áp dụng triển khai

Design Patterns

Ở đây chúng ta có một phần mềm vẽ các hình phẳng 2D có màu sắc khác nhau. Thông thường để đơn giản thì chúng ta chỉ cần có 01 class baseCircle và tạo ra các object hình tròn có thuộc tính màu sắc khác nhau để vẽ. Lúc này, giao diện được sử dụng để vẽ là phương thức draw() của class Circle.

Tuy nhiên lúc này vấn đề nảy sinh là nếu như chúng ta muốn thay đổi cách hoạt động của draw(), thì việc chỉnh sửa sẽ phải thực hiện trực tiếp trên class Circle. Hoặc ví dụ như chúng ta có ý định chỉnh sửa class Circle thì cũng phải coi chừng cái thuộc tính màu sắc kẻo ảnh hưởng đến draw().

Để tách rời giao diện draw()Circle, một bridge được tạo ra bởi interface Drawing. Các class triển khai DrawingRedDrawingGreen sẽ được Circle ủy thác hoàn toàn tác vụ draw() chi tiết. Như vậy, giờ đây chúng ta có thể yên tâm thực hiện những điều chỉnh cần thiết trên Circle mà không gây ảnh hưởng đến giao diện draw() đang hoạt động và ngược lại.

Bước 1

Tạo interface bridge.

bridgepattern/drawingapi/Drawing.java

package bridgepattern.drawingapi;

public interface Drawing {
   public void draw(int x, int y, int radius);
}

Bước 2

Tạo các class triển khai Drawing.

bridgepattern/drawingapi/DrawingGreen.java

package bridgepattern.drawingapi;

public class DrawingGreen implements Drawing {
   @Override
   public void draw (int radius, int x, int y) {
      System.out.println("=== Đang vẽ hình tròn..");
      System.out.println("Màu sắc: Xanh lá");
      System.out.println("Tọa độ tâm: (" + x + ", " + y + ")");
      System.out.println("Bán kính: " + radius);
   }
}

bridgepattern/drawingapi/DrawingRed.java

package bridgepattern.drawingapi;

public class DrawingRed implements Drawing {
   @Override
   public void draw(int x, int y, int radius) {
      System.out.println("=== Đang vẽ hình tròn..");
      System.out.println("Màu sắc: Đỏ");
      System.out.println("Tọa độ tâm: (" + x + ", " + y + ")");
      System.out.println("Bán kính: " + radius);
   }
}

Bước 3

Tạo abstract Shape sử dụng giao diện Drawing.

bridgepattern/Shape.java

package bridgepattern;

import bridgepattern.drawingapi.Drawing;

abstract public class Shape {
   protected Drawing drawingAPI;

   protected Shape(Drawing api) {
      drawingAPI = api;
   }

   public abstract void draw();
}

Bước 4

Tạo class Circle mở rộng Shape.

bridgepattern/Circle.java

package bridgepattern;

import bridgepattern.drawingapi.Drawing;

public class Circle extends Shape {
   private int x, y, radius;

   public Circle(int x, int y, int radius, Drawing api) {
      super(api);
      this.x = x;
      this.y = y;
      this.radius = radius;
   }

   @Override
   public void draw() {
      drawingAPI.draw(x, y, radius);
   }
}

Bước 5

Sử dụng Shape và các class Drawing để vẽ các hình tròn với màu sắc khác nhau.

PatternDemo.java

import bridgepattern.Circle;
import bridgepattern.drawingapi.DrawingGreen;
import bridgepattern.drawingapi.DrawingRed;
import bridgepattern.Shape;

public class PatternDemo {
   public static void main(String[] args) {
      Shape circleRed = new Circle(100, 100, 10, new DrawingRed());
      circleRed.draw();

      Shape circleGreen = new Circle(100, 100, 10, new DrawingGreen());
      circleGreen.draw();
   }
}

Bước 6

Kiểm chứng lại kết quả in ra ở console.

=== Đang vhình tròn..
Màu sắc: Đỏ
Tọa độ tâm: (100, 100)
Bán kính: 10
=== Đang vhình tròn..
Màu sắc: Xanh lá
Tọa độ tâm: (100, 10)
Bán kính: 100

Filter Pattern

Filter còn có tên gọi khác là Criteria. Cả 2 cách gọi này đều có nghĩa là sàng lọc hay màng lọc. Filter được xếp vào nhóm các pattern Kiến Trúc.

Áp dụng triển khai

Design Patterns

  • Chúng ta có 1 danh sách người dùng được tạo bởi class Person.
  • Để lọc danh sách này theo những tiêu chí khác nhau, chúng ta tạo ra một giao diện Filter và các class triển khai.
  • Code main tại PatternDemo sẽ sử dụng các object Filter để lọc danh sách List<Person> theo các tiêu chí.

Bước 1

Tạo class Person để mô tả người dùng.

filterperson/Person.java

package filterperson;

public class Person {
   private String name;
   private String gender;
   private String status;

   public Person(
      String name,
      String gender,
      String status
   ) {
      this.name = name;
      this.gender = gender;
      this.status = status;
   }

   public String getName() {
      return name;
   }

   public String getGender() {
      return gender;
   }

   public String getStatus() {
      return status;
   }
}

Bước 2

Tạo giao diện Filter.

filterperson/Filter.java

package filterperson;

import java.util.List;

public interface Filter {
   public List<Person> match(List<Person> personList);
}

Bước 3

Tạo các class triển khai giao diện Filter.

filterperson/FilterMale.java

package filterperson;

import java.util.ArrayList;
import java.util.List;

public class FilterMale implements Filter {
   @Override
   public List<Person> match(List<Person> personList) {
      List<Person> males = new ArrayList<Person>();

      for (Person p : personList)
         if (p.getGender().equalsIgnoreCase("male"))
            males.add(p);

      return males;
   }
}

filterperson/FilterFemale.java

package filterperson;

import java.util.ArrayList;
import java.util.List;

public class FilterFemale implements Filter {
   @Override
   public List<Person> match(List<Person> personList) {
      List<Person> females = new ArrayList<Person>();

      for (Person p : personList)
         if (p.getGender().equalsIgnoreCase("female"))
            females.add(p);

      return females;
   }
}

filterperson/FilterSingle.java

package filterperson;

import java.util.ArrayList;
import java.util.List;

public class FilterSingle implements Filter {
   @Override
   public List<Person> match(List<Person> personList) {
      List<Person> singles = new ArrayList<Person>();

      for (Person p : personList)
         if (p.getStatus().equalsIgnoreCase("single"))
            singles.add(p);

      return singles;
   }
}

filterperson/FilterAnd.java

package filterperson;

import java.util.List;

public class FilterAnd implements Filter {
   private Filter firstFilter;
   private Filter secondFilter;

   public FilterAnd(
      Filter firstFilter,
      Filter secondFilter
   ) {
      this.firstFilter = firstFilter;
      this.secondFilter = secondFilter;
   }

   @Override
   public List<Person> match(List<Person> personList) {
      List<Person> firstMatched = firstFilter.match(personList);
      return secondFilter.match(firstMatched);
   }
}

filterperson/FilterOr.java

package filterperson;

import java.util.List;

public class FilterOr implements Filter {
   private Filter firstFilter;
   private Filter secondFilter;

   public FilterOr(
      Filter firstFilter,
      Filter secondFilter
   ) {
      this.firstFilter = firstFilter;
      this.secondFilter = secondFilter;
   }

   @Override
   public List<Person> match(List<Person> personList) {
      List<Person> firstMatched = firstFilter.match(personList);
      List<Person> secondMatched = secondFilter.match(personList);

      for (Person p : secondMatched)
         if (! firstMatched.contains(p))
            firstMatched.add(p);

      return firstMatched;
   }
}

Bước 4

Sử dụng Shape và các class Drawing để vẽ các hình tròn với màu sắc khác nhau.

PatternDemo.java

import filterperson.*;

import java.util.ArrayList;
import java.util.List;

public class PatternDemo {
   public static void main(String[] args) {
      List<Person> personList = new ArrayList<Person>();

      personList.add(new Person("Robert","Male", "Single"));
      personList.add(new Person("John", "Male", "Married"));
      personList.add(new Person("Laura", "Female", "Married"));
      personList.add(new Person("Diana", "Female", "Single"));
      personList.add(new Person("Mike", "Male", "Single"));
      personList.add(new Person("Bobby", "Male", "Single"));

      Filter filterMale = new FilterMale();
      Filter filterFemale = new FilterFemale();
      Filter filterSingle = new FilterSingle();
      Filter filterSingleAndMale = new FilterAnd(filterSingle, filterMale);
      Filter filterSingleOrFemale = new FilterOr(filterSingle, filterFemale);

      System.out.println("MALE:");
      print(filterMale.match(personList));

      System.out.println("FEMALE:");
      print(filterFemale.match(personList));

      System.out.println("SINGLE and MALE:");
      print(filterSingleAndMale.match(personList));

      System.out.println("SINGLE or FEMALE");
      print(filterSingleOrFemale.match(personList));
   }

   public static void print(List<Person> personList) {
      for (Person p : personList) {
         System.out.println(
            "[ Name: " + p.getName() +
            ", Gender: " + p.getGender() +
            ", Status: " + p.getStatus() + " ]"
         );
      }
   }
}

Bước 5

Kiểm chứng lại kết quả in ra ở console.

MALE:
[ Name: Robert, Gender: Male, Status: Single ]
[ Name: John, Gender: Male, Status: Married ]
[ Name: Mike, Gender: Male, Status: Single ]
[ Name: Bobby, Gender: Male, Status: Single ]
FEMALE:
[ Name: Laura, Gender: Female, Status: Married ]
[ Name: Diana, Gender: Female, Status: Single ]
SINGLE and MALE:
[ Name: Robert, Gender: Male, Status: Single ]
[ Name: Mike, Gender: Male, Status: Single ]
[ Name: Bobby, Gender: Male, Status: Single ]
SINGLE or FEMALE
[ Name: Robert, Gender: Male, Status: Single ]
[ Name: Diana, Gender: Female, Status: Single ]
[ Name: Mike, Gender: Male, Status: Single ]
[ Name: Bobby, Gender: Male, Status: Single ]
[ Name: Laura, Gender: Female, Status: Married ]

Composite Pattern

Composite được hiểu nôm na là tổng hợp. Composite được sử dụng khi chúng ta muốn làm việc với một nhóm các object như một object đơn duy nhất. Composite được xếp vào nhóm các pattern Kiến Trúc.

Composite tạo ra một class có chứa nhóm các object của chính class đó và đồng thời cung cấp các phương thức để làm việc với nhóm các object này.

Áp dụng triển khai

Design Patterns

  • Chúng ta có một phần mềm mô tả kiến trúc nhân sự của một cửa hàng.
  • 01 class Employee được tạo ra để mô tả thực thể nhân viên.
  • Mỗi object Employee cũng có thể chứa danh sách các nhân viên cấp thấp hơn.
  • Code main trong PatternDemo sẽ sử dụng class Employee để tạo một kiến trúc nhân sự và in ra danh sách tất cả các nhân viên.

Bước 1

Tạo class Employee.

compositepattern/Employee.java

package compositepattern;

import java.util.ArrayList;
import java.util.List;

public class Employee {
   private String name;
   private String role;
   private int salary;
   private List<Employee> subordinateList;

   public Employee(
      String name,
      String role,
      int salary
   ) {
      this.name = name;
      this.salary = salary;
      this.role = role;
      this.subordinateList = new ArrayList<Employee>();
   }

   public void addSub(Employee emp) {
      subordinateList.add(emp);
   }

   public void removeSub(Employee emp) {
      subordinateList.remove(emp);
   }

   public List<Employee> getSubList() {
      return subordinateList;
   }

   public String toString() {
      return "Employee: [ Name: " + name + ", Role: " + role + ", Salary : " + salary+" ]";
   }
}

Bước 2

Sử dụng class Employee để tạo và in danh sách nhân viên.

PatternDemo.java

import compositepattern.Employee;

public class PatternDemo {
   public static void main(String[] args) {
      Employee CEO = new Employee("John","CEO", 30000);

      Employee headSales = new Employee("Robert","Head Sales", 20000);
      CEO.addSub(headSales);

      Employee headMarketing = new Employee("Micheal","Head Marketing", 20000);
      CEO.addSub(headMarketing);

      Employee sale1 = new Employee("Richard","Sales", 10000);
      headSales.addSub(sale1);
      Employee sale2 = new Employee("Rob","Sales", 10000);
      headSales.addSub(sale2);

      Employee clerk1 = new Employee("Laura","Marketing", 10000);
      headMarketing.addSub(clerk1);
      Employee clerk2 = new Employee("Ryan","Marketing", 10000);
      headMarketing.addSub(clerk2);

      // in danh sách tất cả các nhân viên
      System.out.println(CEO);
      for (Employee headEmployee : CEO.getSubList()) {
         System.out.println(headEmployee);
         for (Employee employee : headEmployee.getSubList()) {
            System.out.println(employee);
         }
      }
   }
}

Bước 3

Kiểm chứng lại kết quả được in ra ở console.

Employee: [ Name: John, Role: CEO, Salary : 30000 ]
Employee: [ Name: Robert, Role: Head Sales, Salary : 20000 ]
Employee: [ Name: Richard, Role: Sales, Salary : 10000 ]
Employee: [ Name: Rob, Role: Sales, Salary : 10000 ]
Employee: [ Name: Micheal, Role: Head Marketing, Salary : 20000 ]
Employee: [ Name: Laura, Role: Marketing, Salary : 10000 ]
Employee: [ Name: Ryan, Role: Marketing, Salary : 10000 ]

Decorator Pattern

Decorator cho phép chúng ta bổ sung thêm chức năng mới vào một object đã tồn tại trước đó mà không cần thực hiện chỉnh sửa can thiệp vào kiến trúc của object đó. Decorator được xếp vào nhóm các pattern Kiến Trúc.

Decorator tạo ra một class vỏ bọc bao quanh class nguyên bản và cung cấp thêm các chức năng mở rộng, đồng thời duy trì tính đặc trưng chặt chẽ của các phương thức đã có.

Áp dụng triển khai

Design Patterns

Ở đây chúng ta có một phần mềm vẽ các hình phẳng 2D với các class cơ bản là CircleSquare để vẽ các hình tròn và hình vuông. Bây giờ nhu cầu phát sinh là chúng ta muốn vẽ thêm đường viền cho các hình này, nhưng lại không muốn chỉnh sửa 2 class ban đầu.

Do đó chúng ta sẽ tạo ra một abstract Decorator bao quanh các object hình học nguyên bản. Sau đó class Bordered sẽ triển khai Decorator và thêm vào khả năng vẽ đường viền.

Cuối cùng là code main trong PatternDemo sẽ sử dụng các object Decorator để vẽ hình.

Bước 1

Tạo abstract Shape.

decoratorpatter/Shape.java

package decoratorpattern;

public abstract class Shape {
   public abstract void draw();
}

Bước 2

Tạo 2 class hình học nguyên bản CircleSquare.

decoratorpatter/Circle.java

package decoratorpattern;

public class Circle extends Shape {
   @Override
   public void draw() {
      System.out.println("Shape: Circle");
   }
}

decoratorpatter/Square.java

package decoratorpattern;

public class Square extends Shape {
   @Override
   public void draw() {
      System.out.println("Shape: Square");
   }
}

Bước 3

Tạo abstract Decorator.

decoratorpatter/Decorator.java

package decoratorpattern;

public abstract class Decorator extends Shape {
   protected Shape origin;

   public Decorator(Shape origin) {
      this.origin = origin;
   }

   @Override
   public void draw() {
      origin.draw();
   }
}

Bước 4

Tạo class Bordered mở rộng Decorator.

decoratorpatter/Bordered.java

package decoratorpattern;

public class Bordered extends Decorator {
   public Bordered(Shape origin) {
      super(origin);
   }

   @Override
   public void draw() {
      origin.draw();
      setBorder();
   }

   private void setBorder() {
      System.out.println("Bordered color: Red");
   }
}

Bước 5

Sử dụng các object Decorator để vẽ hình.

PatternDemo.java

import decoratorpattern.Circle;
import decoratorpattern.Shape;
import decoratorpattern.Bordered;
import decoratorpattern.Square;

public class PatternDemo {
   public static void main(String[] args) {
      Shape circle = new Circle();
      System.out.println("=== Circle with normal border");
      circle.draw();

      Shape borderedCircle = new Bordered(new Circle());
      System.out.println("=== Circle with red border");
      borderedCircle.draw();

      Shape borderedSquare = new Bordered(new Square());
      System.out.println("=== Square with red border");
      borderedSquare.draw();
   }
}

Bước 6

Kiểm chứng lại kết quả được in ra ở console.

=== Circle with normal border
Shape: Circle
=== Circle with red border
Shape: Circle
Bordered color: Red
=== Square with red border
Shape: Square
Bordered color: Red

Facade Pattern

Facade được tạm hiểu là mặt tiền, một interface được tạo ra để giao tiếp với code client bên ngoài và ẩn đi hết những thứ phức tạp của một hệ thống phía sau. Facade được xếp vào nhóm các pattern Kiến Trúc.

Áp dụng triển khai

Design Patterns

Ở đây chúng ta có một phần mềm vẽ các hình phẳng 2D với các class mô tả thực thể là Circle, Triangle, và Square. Một giao diện mặt tiền có tên là Painter được tạo ra để ẩn đi các logic bên trong package. Cuối cùng là code main trên PatternDemo sẽ sử dụng Painter để yêu cầu hình vẽ mà không cần biết tới các class mô tả thực thể ban đầu.

Bước 1

Tạo abstract Shape.

facadepattern/Shape.java

package facadepattern;

public abstract class Shape {
   public abstract void draw();
}

Bước 2

Tạo các class mô tả thực thể hình học 2D.

facadepattern/Circle.java

package facadepattern;

class Circle extends Shape {
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}

facadepattern/Triangle.java

package facadepattern;

class Triangle extends Shape {
   @Override
   public void draw() {
      System.out.println("Triangle::draw()");
   }
}

facadepattern/Square.java

package facadepattern;

class Square extends Shape {
   @Override
   public void draw() {
      System.out.println("Square::draw()");
   }
}

Bước 3

Tạo giao diện mặt tiền Painter.

facadepattern/Painter.java

package facadepattern;

public class Painter {
   private Shape circle;
   private Shape triangle;
   private Shape square;

   public Painter() {
      circle = new Circle();
      triangle = new Triangle();
      square = new Square();
   }

   public void drawCircle() {
      circle.draw();
   }

   public void drawTriangle() {
      triangle.draw();
   }

   public void drawSquare() {
      square.draw();
   }
}

Bước 4

Sử dụng giao diện Painter để gửi yêu cầu vẽ các hình 2D.

PatternDemo.java

import facadepattern.Painter;

public class PatternDemo {
   public static void main(String[] args) {
      Painter painter = new Painter();
      painter.drawCircle();
      painter.drawTriangle();
      painter.drawSquare();
   }
}

Bước 5

Kiểm chứng lại kết quả được in ra tại console.

Circle::draw()
Triangle::draw()
Square::draw()

Flyweight Pattern

Flyweight được sử dụng chủ yếu để giảm thiểu số lượng object cần được khởi tạo để tiết kiệm bộ nhớ đệm và cải thiện hiệu năng hoạt động của phần mềm. Flyweight được xếp vào nhóm các pattern Kiến Trúc.

Flyweight luôn cố gắng tái sử dụng những object cùng loại đã tồn tại trước đó và chỉ khởi tạo object mới khi không tìm thấy object nào phù hợp.

Áp dụng triển khai

Design Patterns

Ở đây chúng ta có một phần mềm vẽ các hình tròn 2D. Phần mềm này sẽ vẽ khoảng 20 hình tròn có màu sắc và vị trí khác nhau. Tuy nhiên nhờ có Flyweight, chúng ta sẽ chỉ cần tạo ra 5 object mô tả thực thể hình tròn tương ứng với 5 màu sắc khả dụng.

Chúng ta có class mô tả thực thể Circle và sau đó là Factory được định nghĩa để thực hiện công việc khởi tạo các object Circle khi cần thiết. Tuy nhiên, ở đây Factory có một HashMap để lưu lại những object Circle đã từng khởi tạo với mỗi màu sắc. Do đó, khi có yêu cầu khởi tạo một object Circle với một màu sắc cụ thể, Factory sẽ kiểm tra xem trước đó đã tạo ra thứ tương tự hay chưa. Nếu như có object phù hợp được tìm thấy trong HashMap đã lưu trữ thì sẽ trả về object đó, còn nếu như chưa có thì khởi tạo object mới và đồng thời lưu lại.

Bước 1

Tạo class Circle mô tả thực thể hình tròn 2D.

flyweightpatter/Circle.java

package flyweightpattern;

public class Circle {
   private String color;
   private int x;
   private int y;
   private int r;

   public Circle(String color) {
      this.color = color;
   }

   public void setX(int x) {
      this.x = x;
   }

   public void setY(int y) {
      this.y = y;
   }

   public void setR(int r) {
      this.r = r;
   }

   public void draw() {
      System.out.println(
         "Circle::Draw() [ Color: " + color +
         ", x: " + x + ", y: " + y +
         ", radius: " + r + " ]"
      );
   }
}

Bước 2

Tạo Factory thực hiện công việc khởi tạo các object Circle.

flyweightpatter/Factory.java

package flyweightpattern;

import java.util.HashMap;

public class Factory {
   private static final HashMap<String, Circle> circleMap = new HashMap<String, Circle>();

   public static Circle getCircle(String color) {
      if (circleMap.containsKey(color)) {
         return circleMap.get(color);
      }
      else {
         System.out.println("Creating circle of color: " + color);
         Circle c = new Circle(color);
         circleMap.put(color, c);
         return c;
      }
   }
}

Bước 3

Sử dụng Factory để yêu cầu lấy các object Circle bằng cách truyền vào thông tin về màu sắc.

PatternDemo.java

import flyweightpattern.Circle;
import flyweightpattern.Factory;

public class PatternDemo {
   private static final String colorList[] = { "Red", "Green", "Blue", "White", "Black" };

   public static void main(String[] args) {
      for (int i=0; i<20; i++) {
         Circle c = Factory.getCircle(randomCollor());
         c.setX(randomX());
         c.setY(randomY());
         c.setR(100);
         c.draw();
      }
   }

   private static String randomCollor() {
      return colorList[(int) (Math.random() * colorList.length)];
   }

   private static int randomX() {
      return (int) (Math.random() * 100);
   }

   private static int randomY() {
      return (int) (Math.random() * 100);
   }
}

Bước 4

Kiểm chứng lại kết quả được in ra ở console.

Creating circle of color: Red
Circle::Draw() [ Color: Red, x: 20, y: 91, radius: 100 ]
Circle::Draw() [ Color: Red, x: 93, y: 33, radius: 100 ]
Creating circle of color: White
Circle::Draw() [ Color: White, x: 49, y: 40, radius: 100 ]
Creating circle of color: Green
Circle::Draw() [ Color: Green, x: 82, y: 60, radius: 100 ]
Circle::Draw() [ Color: White, x: 27, y: 73, radius: 100 ]
Creating circle of color: Black
Circle::Draw() [ Color: Black, x: 66, y: 5, radius: 100 ]
Circle::Draw() [ Color: Red, x: 71, y: 69, radius: 100 ]
Circle::Draw() [ Color: White, x: 39, y: 87, radius: 100 ]
Circle::Draw() [ Color: Black, x: 82, y: 25, radius: 100 ]
Circle::Draw() [ Color: Green, x: 24, y: 69, radius: 100 ]
Circle::Draw() [ Color: Black, x: 83, y: 17, radius: 100 ]
Circle::Draw() [ Color: Red, x: 60, y: 27, radius: 100 ]
Circle::Draw() [ Color: Black, x: 48, y: 73, radius: 100 ]
Circle::Draw() [ Color: Red, x: 99, y: 10, radius: 100 ]
Circle::Draw() [ Color: Red, x: 86, y: 74, radius: 100 ]
Creating circle of color: Blue
Circle::Draw() [ Color: Blue, x: 94, y: 85, radius: 100 ]
Circle::Draw() [ Color: White, x: 87, y: 51, radius: 100 ]
Circle::Draw() [ Color: Red, x: 82, y: 81, radius: 100 ]
Circle::Draw() [ Color: White, x: 94, y: 2, radius: 100 ]
Circle::Draw() [ Color: White, x: 75, y: 74, radius: 100 ]

Tham khảo: