Ikuti Rantai Tanggung Jawab

Saya baru saja beralih ke Mac OS X dari Windows dan saya senang dengan hasilnya. Tapi sekali lagi, saya hanya menghabiskan waktu lima tahun yang singkat di Windows NT dan XP; sebelumnya saya hanya seorang pengembang Unix selama 15 tahun, kebanyakan menggunakan mesin Sun Microsystems. Saya juga cukup beruntung untuk mengembangkan perangkat lunak di bawah Nextstep, pendahulu Mac OS X berbasis Unix yang subur, jadi saya sedikit bias.

Selain antarmuka pengguna Aqua yang indah, Mac OS X adalah Unix, yang bisa dibilang sebagai sistem operasi terbaik yang pernah ada. Unix memiliki banyak fitur keren; salah satu yang paling terkenal adalah pipa , yang memungkinkan Anda membuat kombinasi perintah dengan menyalurkan output satu perintah ke input lain. Misalnya, Anda ingin membuat daftar file sumber dari distribusi sumber Struts yang memanggil atau mendefinisikan metode bernama execute(). Inilah salah satu cara untuk melakukannya dengan pipa:

grep "jalankan (" `temukan $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}'

The grepperintah mencari file untuk ekspresi reguler; di sini, saya menggunakannya untuk menemukan kemunculan string execute(dalam file yang digali oleh findperintah. grepKeluaran disalurkan ke awk, yang mencetak token pertama — dipisahkan oleh titik dua — di setiap baris grepkeluaran (batang vertikal menandakan pipa). Token itu adalah nama file, jadi saya berakhir dengan daftar nama file yang berisi string execute(.

Sekarang saya memiliki daftar nama file, saya dapat menggunakan pipa lain untuk mengurutkan daftar:

grep "jalankan (" `temukan $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}' | sort

Kali ini, saya telah menyalurkan daftar nama file ke sort. Bagaimana jika Anda ingin tahu berapa banyak file yang berisi string tersebut execute(? Mudah dengan pipa lain:

 grep "jalankan (" `temukan $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}' | sort -u | wc -l 

The wcperintah menghitung kata, baris, dan byte. Dalam hal ini, saya menentukan -lopsi untuk menghitung baris, satu baris untuk setiap file. Saya juga menambahkan -uopsi untuk sortmemastikan keunikan untuk setiap nama file ( -uopsi menyaring duplikat).

Pipa sangat berguna karena memungkinkan Anda menyusun rantai operasi secara dinamis. Sistem perangkat lunak sering kali menggunakan pipa yang setara (misalnya, filter email atau satu set filter untuk servlet). Inti dari pipa dan filter terletak pada pola desain: Chain of Responsibility (CoR).

Catatan: Anda dapat mengunduh kode sumber artikel ini dari Sumber.

Pengenalan CoR

Pola Chain of Responsibility menggunakan rantai objek untuk menangani permintaan, yang biasanya merupakan sebuah peristiwa. Objek dalam rantai meneruskan permintaan sepanjang rantai sampai salah satu objek menangani kejadian tersebut. Pemrosesan berhenti setelah suatu acara ditangani.

Gambar 1 mengilustrasikan bagaimana pola CoR memproses permintaan.

Dalam Design Patterns , penulis mendeskripsikan pola Chain of Responsibility seperti ini:

Hindari menggabungkan pengirim permintaan ke penerima dengan memberi lebih dari satu objek kesempatan untuk menangani permintaan tersebut. Rantai objek penerima dan teruskan permintaan di sepanjang rantai sampai objek menanganinya.

Pola Rantai Tanggung Jawab berlaku jika:

  • Anda ingin memisahkan pengirim dan penerima permintaan
  • Beberapa objek, ditentukan saat runtime, adalah kandidat untuk menangani permintaan
  • Anda tidak ingin menentukan penangan secara eksplisit dalam kode Anda

Jika Anda menggunakan pola CoR, ingat:

  • Hanya satu objek dalam rantai yang menangani permintaan
  • Beberapa permintaan mungkin tidak ditangani

Pembatasan tersebut, tentu saja, untuk implementasi CoR klasik. Dalam praktiknya, aturan tersebut dibengkokkan; misalnya, filter servlet adalah implementasi CoR yang memungkinkan beberapa filter untuk memproses permintaan HTTP.

Gambar 2 menunjukkan diagram kelas pola CoR.

Biasanya, penangan permintaan adalah ekstensi dari kelas dasar yang mempertahankan referensi ke penangan berikutnya dalam rantai, yang dikenal sebagai successor. Kelas dasar mungkin menerapkan handleRequest()seperti ini:

kelas abstrak publik HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (successor! = null) successor.handleRequest (sro); }}

Jadi secara default, penangan meneruskan permintaan ke penangan berikutnya dalam rantai. Perpanjangan konkret HandlerBasemungkin terlihat seperti ini:

public class SpamFilter extends HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Jika pesannya adalah spam // lakukan tindakan terkait spam. Jangan teruskan pesan. } else {// Pesan bukan spam. super.handleRequest (mailMessage); // Teruskan pesan ke filter berikutnya dalam rantai. }}}

The SpamFiltermenangani permintaan (mungkin penerimaan email baru) jika pesan adalah spam, dan karena itu, permintaan tidak bergerak lebih jauh; jika tidak, pesan tepercaya diteruskan ke penangan berikutnya, mungkin filter email lain yang ingin menyingkirkannya. Akhirnya, filter terakhir dalam rantai mungkin menyimpan pesan setelah lolos dikumpulkan dengan berpindah melalui beberapa filter.

Perhatikan bahwa filter email hipotetis yang dibahas di atas saling eksklusif: Pada akhirnya, hanya satu filter yang menangani permintaan. Anda dapat memilih untuk membalikkannya dengan membiarkan beberapa filter menangani satu permintaan, yang merupakan analogi yang lebih baik untuk pipa Unix. Either way, mesin yang mendasarinya adalah pola CoR.

Dalam artikel ini, saya membahas dua implementasi pola Chain of Responsibility: servlet filter, implementasi CoR populer yang memungkinkan beberapa filter untuk menangani permintaan, dan model acara Abstract Window Toolkit (AWT) asli, implementasi CoR klasik yang tidak populer yang akhirnya tidak digunakan lagi .

Filter servlet

Di Platform Java 2, Enterprise Edition (J2EE), beberapa kontainer servlet menyediakan fitur praktis yang dikenal sebagai rantai servlet, di mana pada dasarnya seseorang dapat menerapkan daftar filter ke servlet. Filter servlet populer karena berguna untuk keamanan, kompresi, logging, dan banyak lagi. Dan, tentu saja, Anda dapat membuat rangkaian filter untuk melakukan beberapa atau semua hal tersebut bergantung pada kondisi waktu proses.

Dengan munculnya Spesifikasi Servlet Java versi 2.3, filter menjadi komponen standar. Tidak seperti CoR klasik, filter servlet memungkinkan banyak objek (filter) dalam rantai untuk menangani permintaan.

Filter servlet adalah tambahan yang ampuh untuk J2EE. Juga, dari sudut pandang pola desain, mereka memberikan sentuhan yang menarik: Jika Anda ingin mengubah permintaan atau respon, Anda menggunakan pola Dekorator selain CoR. Gambar 3 menunjukkan cara kerja filter servlet.

Filter servlet sederhana

Anda harus melakukan tiga hal untuk memfilter servlet:

  • Menerapkan servlet
  • Terapkan filter
  • Kaitkan filter dan servlet

Contoh 1-3 melakukan ketiga langkah secara berurutan:

Contoh 1. Seorang servlet

import java.io.PrintWriter; import javax.servlet.*; import javax.servlet.http.*; public class FilteredServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException { PrintWriter out = response.getWriter(); out.println("Filtered Servlet invoked"); } } 

Example 2. A filter

import java.io.PrintWriter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; public class AuditFilter implements Filter { private ServletContext app = null; public void init(FilterConfig config) { app = config.getServletContext(); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, javax.servlet.ServletException { app.log(((HttpServletRequest)request).getServletPath()); chain.doFilter(request, response); } public void destroy() { } } 

Example 3. The deployment descriptor

    auditFilter AuditFilter  <filter-mapping>auditFilter/filteredServlet</filter-mapping>   filteredServlet FilteredServlet   filteredServlet /filteredServlet  ...  

If you access the servlet with the URL /filteredServlet, the auditFilter gets a crack at the request before the servlet. AuditFilter.doFilter writes to the servlet container log file and calls chain.doFilter() to forward the request. Servlet filters are not required to call chain.doFilter(); if they don't, the request is not forwarded. I can add more filters, which would be invoked in the order they are declared in the preceding XML file.

Now that you've seen a simple filter, let's look at another filter that modifies the HTTP response.

Filter the response with the Decorator pattern

Unlike the preceding filter, some servlet filters need to modify the HTTP request or response. Interestingly enough, that task involves the Decorator pattern. I discussed the Decorator pattern in two previous Java Design Patterns articles: "Amaze Your Developer Friends with Design Patterns" and "Decorate Your Java Code."

Example 4 lists a filter that performs a simple search and replace in the body of the response. That filter decorates the servlet response and passes the decorator to the servlet. When the servlet finishes writing to the decorated response, the filter performs a search and replace within the response's content.

Example 4. A search and replace filter

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SearchAndReplaceFilter implements Filter { private FilterConfig config; public void init(FilterConfig config) { this.config = config; } public FilterConfig getFilterConfig() { return config; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, javax.servlet.ServletException { StringWrapper wrapper = new StringWrapper((HttpServletResponse)response); chain.doFilter(request, wrapper); String responseString = wrapper.toString(); String search = config.getInitParameter("search"); String replace = config.getInitParameter("replace"); if(search == null || replace == null) return; // Parameters not set properly int index = responseString.indexOf(search); if(index != -1) { String beforeReplace = responseString.substring(0, index); String afterReplace=responseString.substring(index + search.length()); response.getWriter().print(beforeReplace + replace + afterReplace); } } public void destroy() { config = null; } } 

The preceding filter looks for filter init parameters named search and replace; if they are defined, the filter replaces the first occurrence of the search parameter value with the replace parameter value.

SearchAndReplaceFilter.doFilter() wraps (or decorates) the response object with a wrapper (decorator) that stands in for the response. When SearchAndReplaceFilter.doFilter() calls chain.doFilter() to forward the request, it passes the wrapper instead of the original response. The request is forwarded to the servlet, which generates the response.

When chain.doFilter() returns, the servlet is done with the request, so I go to work. First, I check for the search and replace filter parameters; if present, I obtain the string associated with the response wrapper, which is the response content. Then I make the substitution and print it back to the response.

Example 5 lists the StringWrapper class.

Example 5. A decorator

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class StringWrapper extends HttpServletResponseWrapper { StringWriter writer = new StringWriter(); public StringWrapper(HttpServletResponse response) { super(response); } public PrintWriter getWriter() { return new PrintWriter(writer); } public String toString() { return writer.toString(); } } 

StringWrapper, which decorates the HTTP response in Example 4, is an extension of HttpServletResponseWrapper, which spares us the drudgery of creating a decorator base class for decorating HTTP responses. HttpServletResponseWrapper ultimately implements the ServletResponse interface, so instances of HttpServletResponseWrapper can be passed to any method expecting a ServletResponse object. That's why SearchAndReplaceFilter.doFilter() can call chain.doFilter(request, wrapper) instead of chain.doFilter(request, response).

Sekarang setelah kita memiliki filter dan pembungkus respons, mari kaitkan filter dengan pola URL dan tentukan pola penelusuran dan ganti: