Pola Desain Juru Bahasa di Jawa

1. Ikhtisar

Dalam tutorial ini, kami akan memperkenalkan salah satu pola desain perilaku GoF - Interpreter.

Pertama, kami akan memberikan gambaran umum tentang tujuannya dan menjelaskan masalah yang coba dipecahkannya.

Kemudian, kita akan melihat diagram UML Interpreter dan implementasi contoh praktisnya.

2. Pola Desain Juru Bahasa

Singkatnya, pola tersebut mendefinisikan tata bahasa dari bahasa tertentu dengan cara berorientasi objek yang dapat dievaluasi oleh penerjemah itu sendiri.

Dengan mengingat hal itu, secara teknis kami dapat membuat ekspresi reguler kustom kami, penerjemah DSL kustom, atau kami dapat mengurai salah satu bahasa manusia, membuat pohon sintaks abstrak, dan kemudian menjalankan interpretasi.

Ini hanya beberapa kasus penggunaan potensial, tetapi jika kami berpikir sejenak, kami dapat menemukan lebih banyak kegunaan darinya, misalnya di IDE kami, karena mereka terus menafsirkan kode yang kami tulis dan dengan demikian memberi kami petunjuk yang tak ternilai harganya.

Pola interpreter umumnya harus digunakan jika tata bahasanya relatif sederhana.

Jika tidak, mungkin akan sulit untuk dipelihara.

3. Diagram UML

Diagram di atas menunjukkan dua entitas utama: Konteks dan Ekspresi .

Sekarang, bahasa apa pun perlu diungkapkan dengan cara tertentu, dan kata-kata (ekspresi) akan memiliki beberapa makna berdasarkan konteks yang diberikan.

AbstractExpression mendefinisikan satu metode abstrak yang mengambil kontekssebagai parameter. Berkat itu, setiap ekspresi akan memengaruhi konteks , mengubah statusnya, dan melanjutkan interpretasi atau mengembalikan hasilnya sendiri.

Oleh karena itu, konteksnya akan menjadi pemegang status pemrosesan global, dan akan digunakan kembali selama keseluruhan proses interpretasi.

Jadi apa perbedaan antara TerminalExpression dan NonTerminalExpression ?

Sebuah NonTerminalExpression mungkin memiliki satu atau lebih AbstractExpression lain yang terkait di dalamnya, oleh karena itu dapat diinterpretasikan secara rekursif. Pada akhirnya, proses interpretasi harus diakhiri dengan TerminalExpression yang akan mengembalikan hasilnya.

Perlu dicatat bahwa NonTerminalExpression adalah komposit.

Terakhir, peran klien adalah membuat atau menggunakan pohon sintaksis abstrak yang sudah dibuat , yang tidak lebih dari kalimat yang ditentukan dalam bahasa yang dibuat.

4. Implementasi

Untuk menunjukkan pola dalam tindakan, kita akan membuat sintaks sederhana seperti SQL dengan cara berorientasi objek, yang kemudian akan diinterpretasikan dan mengembalikan hasilnya kepada kita.

Pertama, kita akan mendefinisikan ekspresi Select, From, dan Where , membangun pohon sintaks di kelas klien dan menjalankan interpretasinya.

The Expression antarmuka akan memiliki menafsirkan metode:

List interpret(Context ctx);

Selanjutnya, kami mendefinisikan ekspresi pertama, kelas Select :

class Select implements Expression { private String column; private From from; // constructor @Override public List interpret(Context ctx) { ctx.setColumn(column); return from.interpret(ctx); } }

Ia mendapat nama kolom yang akan dipilih dan Ekspresi beton lain dari jenis Dari sebagai parameter dalam konstruktor.

Perhatikan bahwa dalam metode overridden interpret (), metode ini menetapkan status konteks dan meneruskan interpretasi lebih jauh ke ekspresi lain bersama dengan konteksnya.

Dengan begitu, kami melihat bahwa itu adalah NonTerminalExpression.

Ekspresi lain adalah kelas Dari :

class From implements Expression { private String table; private Where where; // constructors @Override public List interpret(Context ctx) { ctx.setTable(table); if (where == null) { return ctx.search(); } return where.interpret(ctx); } }

Sekarang, dalam SQL klausa where adalah opsional, oleh karena itu kelas ini adalah terminal atau ekspresi non-terminal.

Jika pengguna memutuskan untuk tidak menggunakan klausa where, ekspresi From itu akan diakhiri dengan panggilan ctx.search () dan mengembalikan hasilnya. Jika tidak, itu akan ditafsirkan lebih lanjut.

The Dimana ekspresi lagi memodifikasi konteks dengan menetapkan filter yang diperlukan dan berakhir interpretasi dengan panggilan pencarian:

class Where implements Expression { private Predicate filter; // constructor @Override public List interpret(Context ctx) { ctx.setFilter(filter); return ctx.search(); } }

Sebagai contoh, kelas Konteks menyimpan data yang meniru tabel database.

Perhatikan bahwa ini memiliki tiga kolom kunci yang dimodifikasi oleh setiap subclass Expression dan metode pencarian:

class Context { private static Map
    
      tables = new HashMap(); static { List list = new ArrayList(); list.add(new Row("John", "Doe")); list.add(new Row("Jan", "Kowalski")); list.add(new Row("Dominic", "Doom")); tables.put("people", list); } private String table; private String column; private Predicate whereFilter; // ... List search() { List result = tables.entrySet() .stream() .filter(entry -> entry.getKey().equalsIgnoreCase(table)) .flatMap(entry -> Stream.of(entry.getValue())) .flatMap(Collection::stream) .map(Row::toString) .flatMap(columnMapper) .filter(whereFilter) .collect(Collectors.toList()); clear(); return result; } }
    

Setelah pencarian selesai, konteksnya bersih sendiri, jadi kolom, tabel, dan filter disetel ke default.

Dengan cara itu setiap interpretasi tidak akan mempengaruhi yang lain.

5. Pengujian

Untuk tujuan pengujian, mari kita lihat kelas InterpreterDemo :

public class InterpreterDemo { public static void main(String[] args) { Expression query = new Select("name", new From("people")); Context ctx = new Context(); List result = query.interpret(ctx); System.out.println(result); Expression query2 = new Select("*", new From("people")); List result2 = query2.interpret(ctx); System.out.println(result2); Expression query3 = new Select("name", new From("people", new Where(name -> name.toLowerCase().startsWith("d")))); List result3 = query3.interpret(ctx); System.out.println(result3); } }

Pertama, kita membangun pohon sintaks dengan ekspresi yang dibuat, menginisialisasi konteks dan kemudian menjalankan interpretasinya. Konteksnya digunakan kembali, tetapi seperti yang kami tunjukkan di atas, konteks itu membersihkan dirinya sendiri setelah setiap panggilan penelusuran.

Dengan menjalankan program, outputnya adalah sebagai berikut:

[John, Jan, Dominic] [John Doe, Jan Kowalski, Dominic Doom] [Dominic]

6. Kerugian

Ketika tata bahasanya menjadi lebih kompleks, itu menjadi lebih sulit untuk dipertahankan.

Itu bisa dilihat pada contoh yang disajikan. Akan cukup mudah untuk menambahkan ekspresi lain, seperti Limit , namun tidak akan terlalu mudah untuk mempertahankannya jika kami memutuskan untuk terus memperluasnya dengan semua ekspresi lainnya.

7. Kesimpulan

Pola desain juru bahasa sangat bagus untuk interpretasi tata bahasa yang relatif sederhana , yang tidak perlu berkembang dan meluas.

Dalam contoh di atas, kami menunjukkan bahwa dimungkinkan untuk membuat kueri seperti SQL dengan cara berorientasi objek dengan bantuan pola interpreter.

Terakhir, Anda dapat menemukan penggunaan pola ini di JDK, khususnya di java.util.Pattern , java.text.Format , atau java.text.Normalizer .

Seperti biasa, kode lengkap tersedia di proyek Github.