Design pattern in OOP [Part 2]

Dẫn nhập

Chào mọi người, tiếp tục chủ đề về Design Pattern trong lập trình hướng đối tượng mà tôi đã giới thiệu bài trước, hôm nay tôi sẽ tiếp tục giới thiệu đến các bạn một số Design pattern phổ biến và hữu dụng trong lập trình, bao gồm Factory Pattern, Observe PatternData Access Object Pattern.

Factory Pattern

Đây là một creational pattern - dùng để khởi tạo hiệu quả các đối tượng mà theo tôi là rất phổ biến. Tư tưởng của pattern này là tạo ra các đối tượng có những đặc tính chung thông qua một interface hay một abstract class và giao tiếp với chúng thông qua một Factory class làm nhiệm vụ khởi tạo đối tượng. Đến với diagram của Factory Pattern bên dưới.

Ta thấy các đối tượng như Rectangle, Square hay Circle sẽ được khởi tạo thông qua một interface Shape bao gồm những method chung, những method này sẽ làm những nhiệm vụ khác nhau theo từng đối tượng khác nhau. Một class Factory làm nhiệm vụ khởi tạo các đối tượng này và sử dụng chúng ở nhiều nơi khác nhau trong chương trình.

Hãy đến với implementation bên dưới

  • Tạo một interface Shape
public interface Shape {
   void draw();
}
  • Các class Circle, Square, Rectangle
public class Rectangle implements Shape {

   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

public class Square implements Shape {

   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}
public class Circle implements Shape {

   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}
  • Class ShapeFactory
public class ShapeFactory {
	
   //use getShape method to get object of type shape 
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }		
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
         
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
         
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      
      return null;
   }
}
  • Sử dụng như bên dưới
public class FactoryPatternDemo {

   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();

      //get an object of Circle and call its draw method.
      Shape shape1 = shapeFactory.getShape("CIRCLE");

      //call draw method of Circle
      shape1.draw();

      //get an object of Rectangle and call its draw method.
      Shape shape2 = shapeFactory.getShape("RECTANGLE");

      //call draw method of Rectangle
      shape2.draw();

      //get an object of Square and call its draw method.
      Shape shape3 = shapeFactory.getShape("SQUARE");

      //call draw method of circle
      shape3.draw();
   }
}

Pattern này rất hữu dụng khi bạn tạo một số lượng các đối tượng khác nhau có cùng đặc tính, code sẽ clean và dễ bảo trì sau này.

Observer Pattern

Đây là một pattern hiện nay được sử dụng vô cùng phổ biến trong lập trình. Tư tưởng của nó là tạo ra mối liên hệ one-to-many giữa các đối tượng với nhau, sao cho khi một đối tượng này bị thay đổi thì tất cả các đối tượng phụ thuộc khác, đang lắng nghe sự thay đổi đó, cũng sẽ biết được. Bởi đặc tính này mà nó được sử dụng rất nhiều trong lập trình hiện nay, đặc biệt là lập trình bất đồng bộ. Đến với diagram của nó bên dưới.

Dựa vào diagram bên trên, chúng ta dễ dàng nhìn thấy tư tưởng của Observer pattern này. Class Subject là class thực hiện các tác dụng trung tâm, là core của pattern này. Nó sẽ chứa tất cả Observer - là các đối tượng cần lắng nghe sự thay đổi từ Subject này. Khi khởi tạo 1 Observer, nó sẽ được attach vào Subject để notify nếu có sự thay đổi xảy ra trong Subject. Đến với implementation chi tiết bên dưới.

  • Tạo Subject class
import java.util.ArrayList;
import java.util.List;

public class Subject {
	
   private List<Observer> observers = new ArrayList<Observer>();
   private int state;

   public int getState() {
      return state;
   }

   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }

   public void attach(Observer observer){
      observers.add(observer);		
   }

   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   } 	
}
  • Base Observer class
public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}
  • Các Observer class
public class BinaryObserver extends Observer{

   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }

   @Override
   public void update() {
      System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); 
   }
}
public class OctalObserver extends Observer{

   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }

   @Override
   public void update() {
     System.out.println( "Octal String: " + Integer.toOctalString( subject.getState() ) ); 
   }
}
public class HexaObserver extends Observer{

   public HexaObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }

   @Override
   public void update() {
      System.out.println( "Hex String: " + Integer.toHexString( subject.getState() ).toUpperCase() ); 
   }
}
  • Demo
public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();

      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);

      System.out.println("First state change: 15");	
      subject.setState(15);
      System.out.println("Second state change: 10");	
      subject.setState(10);
   }
}

Hiện nay, có nhiều phương pháp lập trình thực hiện hóa pattern này, các bạn có thể tham khảo thêm Reactive Programming

Data Access Object

Đây là pattern được sử dụng phổ biến trong truy xuất dữ liệu từ Database. Tư tưởng của nó là tạo ra nơi cung cấp các method để truy xuất dữ liệu ở mức low-level, tách biết so với phần service - high level với các business phức tạp khác. Nó bao gồm 3 thành phần cơ bản:

  • Data Access Object Interface: Một interface định nghĩa các method để sử dụng một cách tường minh.
  • Data Access Object concrete class : Class implement Data Access Object Interface, sẽ định nghĩa các method một cách cụ thể, tạo kết nối với DB hay map data từ DB vào các Model class.
  • Model Object or Value Object: POJO class để chứa dữ liệu.

Chúng ta đến với diagram và implementation bên dưới:

  • POJO class
public class Student {
   private String name;
   private int rollNo;

   Student(String name, int rollNo){
      this.name = name;
      this.rollNo = rollNo;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public int getRollNo() {
      return rollNo;
   }

   public void setRollNo(int rollNo) {
      this.rollNo = rollNo;
   }
}
  • Data Access Object Interface
import java.util.List;

public interface StudentDao {
   public List<Student> getAllStudents();
   public Student getStudent(int rollNo);
   public void updateStudent(Student student);
   public void deleteStudent(Student student);
}
  • Data Access Object concrete class.
import java.util.ArrayList;
import java.util.List;

public class StudentDaoImpl implements StudentDao {
	
   //list is working as a database
   List<Student> students;

   public StudentDaoImpl(){
      students = new ArrayList<Student>();
      Student student1 = new Student("Robert",0);
      Student student2 = new Student("John",1);
      students.add(student1);
      students.add(student2);		
   }
   @Override
   public void deleteStudent(Student student) {
      students.remove(student.getRollNo());
      System.out.println("Student: Roll No " + student.getRollNo() + ", deleted from database");
   }

   //retrive list of students from the database
   @Override
   public List<Student> getAllStudents() {
      return students;
   }

   @Override
   public Student getStudent(int rollNo) {
      return students.get(rollNo);
   }

   @Override
   public void updateStudent(Student student) {
      students.get(student.getRollNo()).setName(student.getName());
      System.out.println("Student: Roll No " + student.getRollNo() + ", updated in the database");
   }
}
  • Demo
public class DaoPatternDemo {
   public static void main(String[] args) {
      StudentDao studentDao = new StudentDaoImpl();

      //print all students
      for (Student student : studentDao.getAllStudents()) {
         System.out.println("Student: [RollNo : " + student.getRollNo() + ", Name : " + student.getName() + " ]");
      }


      //update student
      Student student =studentDao.getAllStudents().get(0);
      student.setName("Michael");
      studentDao.updateStudent(student);

      //get the student
      studentDao.getStudent(0);
      System.out.println("Student: [RollNo : " + student.getRollNo() + ", Name : " + student.getName() + " ]");		
   }
}

Kết luận

Ở bài viết này, tôi đã tổng hợp và giới thiệu các Design Pattern mà theo tôi là cơ bản và vô cùng phổ biến trong lập trình. Bài viết có tham khảo từ một số nguồn, các bạn có thể tìm đọc ở đây.