Thông dịch viên không cần lương

Có bao giờ bạn tự hỏi các ứng dụng dịch như google translate, hay các ngôn ngữ thông dịch như javascript hoạt động như thế nào chưa? Đó là một trong những ứng dụng rất quan trọng của Interpreter. Nó như một thông dịch viên cần mẫn, luôn cố gắng dịch mọi ngôn ngữ đầu vào và đưa ra kết quả chính xác, và đặc biệt nó có thể sống mà không cần phải được trả lương.

Sơ đồ lớp

Liệu có phải bên trong các ứng dụng dịch ngôn ngữ sẽ code kiểu như thế này không?

if (inputLanguage = "en") {
    if(outputLanguage = "vi") {
        if(inputText = "hello") {
            return "xin chao";
        }
    }
}

Chắc chắn là không phải rồi, họ cần một thiết kế mềm dẻo và có khả năng mở rộng hơn thế này rất nhiều.

Đây là sơ đồ lớp mình tham khảo ở wiki, cá nhân mình thấy nó hơi khó hiểu, nhưng nó xuất hiện ở nhiều tài liệu khác nữa nên mình sẽ bám theo nó nhé.

Các lớp thành phần trong sơ đồ bao gồm:

  1. Client: là lớp sử dụng interpreter design pattern
  2. Context: là lớp được tạo bởi client và được truyền vào interpreter, nó chưa dữ liệu đầu vào và kết quả đầu ra.
  3. AbstractExpression: là lớp cơ sở chứa hàm interpret, Client sẽ sử dụng lớp này thay vì các lớp cài đặt cụ thể
  4. TerminalExpression: là lớp thừa kế AbstractExpression và cài đặt hàm interpret, trong thực tế sẽ có rất nhiề lớp TerminalExpression
  5. NonterminalExpression: là lớp thừa kế AbstractExpression, tuy nhiên nó sẽ có nhiều lớp con và gọi các lớp con này trong hàm cài đặt interpret

Mục tiêu ra đời

Với cú pháp phức tạp if else như ví dụ ở trên thì rất khó để chúng ta có thể tạo ra được một ứng dụng từ điển trong thực tế, vậy nên interpreter ra đời hướng đến các mục tiêu:

  1. Tạo ra một mô hình mà người sử dụng chỉ cần quan tâm đến dữ liệu đầu vào và nhận dữ liệu đầu ra
  2. Tạo ra các lớp cài đặt hàm interpret, giảm bớt các câu lệnh rẽ nhánh if else, từ đó tăng khả năng mở rộng về sau này

Ví dụ

Trở lại với ứng dụng dịch, chúng ta sẽ cung cấp tính năng dịch từ tiếng Anh sang tiếng Vệt và ngược lại từ tiếng Việt sang tiếng Anh, hãy thiết kế sơ đồ lớp một chút nhé

Sơ đồ lớp

  1. Lớp LanguageTranslator: đóng vai trò là lớp Client, sẽ tạo ra đối tượng LanguageTranslateRequest và truyền nó vào cho các lớp interpreter. Lớp này sẽ chứa một danh sách các interprerter tương ứng với ngôn ngữ đầu vào và đầu ra.
  2. LanguageTranslateRequest: chứa thông tin ngôn ngữ đầu vào, ngôn ngữ đầu ra và văn bản cần dịch, nó tương ứng với lớp Context trong mô hình tiêu chuẩn
  3. LanguageInterperter: là interface cơ sở chứa hàm interpreter, hơi khác một chút so với mô hình tiêu chuẩn, đó là hàm này chỉ yêu cầu 1 văn bản đầu vào và trả lại văn bản kết quả đầu ra chứ không cần sử dụng đến đối tượng LanguageTranslateRequest, lớp này tương ứng với AbstractExpression
  4. LanguageEnglishToVietnameseInterpreter: là lớp chịu trách nhiệm dịch từ tiếng anh sang tiếng Việt, lớp này tương ứng với TermialExpression
  5. LanguageVietnameseToEnglishInterpreter: là lớp chịu trách nhiệm dịch từ tiếng Việt sang tiếng anh, lớp này cũng tương ứng với TermialExpression

Một chút code

Source code đầy đủ các bạn có thể tham khảo tại Github, ở đây mình sẽ chỉ viết ra một số đoạn chính thôi nhé:

Lớp LanguageTranslator
public class LanguageTranslator {

    private final Map<LanguageType, Map<LanguageType, LanguageInterperter>> interperters;

public String translate(LanguageTranslateRequest request) {
        return interperters
                .get(request.getInputLanguageType())
                .get(request.getOutputlLanguageType())
                .interpreter(request.getText());
    }

    public static void main(String[] args) {
        LanguageTranslator translator = new LanguageTranslator();
        String englishText = "hello";
        String translatedVietnameseText = translator.translate(
            new LanguageTranslateRequest(
                    LanguageType.EN, 
                    LanguageType.VI, englishText
            )
        );

        System.out.println("English text: " + englishText);
        System.out.println("Vietnamese text: " + translatedVietnameseText);

        String vietnamesesText = "chao buoi sang";
        String translatedEnglishText = translator.translate(
                new LanguageTranslateRequest(
                        LanguageType.VI, 
                        LanguageType.EN, vietnamesesText
                )
            );

        System.out.println("Vietnamese text: " + vietnamesesText);
        System.out.println("English text: " + translatedEnglishText);
    }
}
lớp LanguageTranslateRequest
public class LanguageTranslateRequest {

    private final LanguageType inputLanguageType;
    private final LanguageType outputlLanguageType;
    private final String text;
}
Interface LanguageInterperter
public interface LanguageInterperter {

    String interpreter(String input);

}
Lớp LanguageEnglishToVietnameseInterpreter
public class LanguageEnglishToVietnameseInterpreter implements LanguageInterperter {

    private Map<String, String> dictionary;

    @Override
    public String interpreter(String input) {
        return dictionary.get(input);
    }
}
Lớp LanguageVietnameseToEnglishInterpreter
public class LanguageEnglishToVietnameseInterpreter implements LanguageInterperter {

    private Map<String, String> dictionary;

    @Override
    public String interpreter(String input) {
        return dictionary.get(input);
    }
}

Theo mô hình này, mỗi cặp ngôn ngữ sẽ có một lớp dịch riêng biệt, chính vì vậy mà chúng ta có thể thêm bao nhiêu cặp ngôn ngữ tuỳ ý mà không làm ảnh hưởng tới chương trình đang chạy. Thêm vào nữa chúng ta có thể chia thành nhiều đội cùng phát triển đồng thời mà không làm ảnh đến nhau.

Tổng kết

Nếu bạn đang có ý định viết một ứng dụng dịch, hay một ngôn ngữ lập trình mới, interpreter sẽ là một lựa chọn rất tuyệt vời cho bạn, ngoài ra interpreter cũng được ứng dụng để thông dịch dữ liệu từ dạng A thành dạng B giữa các tầng, ví chuyển đổi đối tượng sang mảng byte hay ngược lại, đây đều là những thứ khá quen thuộc với chúng ta.

Tham khảo

  1. wiki
  2. ví dụ