الگوهای طراحی جاوا
زنجيره پاسخگويی را دنبال نماييد
اجراهای COR طرف سرور و طرف كلاينت را مرور كنيد
نويسنده: David Geary
Java world
مترجم: نازنين حقيقی
خلاصه
الگوی زنجيره پاسخگويی CoR يا Chain of Resposibility فرستنده و گيرنده يك درخواست را از طريق قرار گيری يك زنجيره از آبجكتها بين آنها جدا مینمايد. در اين مقاله David Greay در مورد الگوی CoR و دو اجرای آن الگو در API های جاوا يكی از جاوای طرف كلاينت وديگری از جاوای طرف سرور بحث می كند.
من اخيرا از ويندوز به Mac OS X روی آوردهام و از نتايج هيجان زده شدهام. از طرف ديگر، تنها مدت كوتاه پنج سال برای ويندوز NT و XP صرف كردم. پيش از آن برای مدت پانزده سال صرفا يك طراح يونيكس، اغلب روی دستگاههای سان مايكروسيستمز بودم. من همچنين آنقدر خوش شانس بودم كه تحت Nextstep نسخه سابق لوكس Mac OS X كه مبتنی بر يونيكس بود، نرمافزار طراحی مینمايم، بنابراين كمی متعصبم.
Mac OS X جدا از اينترفيس كاربر Aqua زيبايش، يونيكس است كه مسلما بهترين سيستم عامل موجود میباشد. يونيكس ويژگيهای عالی زيادی دارد. يكی از شناخته شدهترين ويژگیهايش pipe است، كه به شما امكان میدهد مجموعههايی از فرامين را از طريق تبديل خروجی يك فرمان به ورودی فرمان ديگر ايجاد كنيد. برای مثال فرض كنيد میخواهيد از توزيع منبع Struts فايلهای منبع را كه يك متد به نام ()execute را فعال میسازند يا مشخص میكنند ليست نماييد. در اينجا يك راه انجام آن با يك pipe را میبينيد:
grep "ececute(" `find $STRUTS_SRC_DIR -name "*.java"` | awk -F: '{print $1}'
فرمان grep عبارتهای معمول را در فايلها جستجو میكند. در اينجا، من از آن استفاده میكنم تا ميزان وقوع رشته execute را بيابم ( در فايلهای بدست آمده با فرمان find). خروجی grep درون awk منتقل میشود، كه اولين علامت كه حدودش توسط يك كولون مشخص میشود- را در هر خط خروجی grep چاپ می نمايد ( يك خط عمودی يك pipe را نشان میدهد). آن علامت يك نام فايل است، بنابراين در آخر ليستی از نام فايلهايی كه رشته )excute را در بردارند بدست میآورم .
اكنون كه ليستی از نام فايلها را دارم، میتوانم از pipe ديگری برای دستهبندی ليست استفاده نمايم:
grep "execute(" `find $STRUTS_SRC_DIR -name "*.java"' | awk -F: '{print $1}' | sort
اين بار، ليست نام فايلها را به Sort منتقل كردهام. اگر بخواهيد بدانيد چند فايل رشته )excute را در بر دارند چه؟ اين كار با استفاده از pipe ديگری اسان میشود:
grep "execute(" `find $STRUTS_SRC_DIR -name "*.java"' | awk -F: '{print $1}' | sort -u | wc -1
فرمان wc كلمات، خطوط و بايتها را میشمارد. در اين وضعيت من گزينه يك را مشخص كردم تا خطوط را بشمارد. همچنين يك گزينه -u را به sort اضافه نمودم تا از يكی بودن هر نام فايل مطمئن شوم ( گزينه -u كپی ها را جدا میكند).
pipe ها قدرتمندند، زيرا به شما امكان میدهند فعالانه زنجيرهای از فعاليتها را بسازيد. سيستمهای نرمافزاری اغلب معادل pipe ها را به كار میگيرند (برای مثال، فيلترهای e-mail يا مجموعهای از فيلترها برای Servlet). درون pipeها و فيلترها يك الگوی طراحی قرار میگيرد: CoR يا Chain of Resposibility.
معرفی CoR
الگوی Chain of Resposibility از زنجيرهای از آبجكتها برای رسيدگی به يك درخواست استفاده میكند، كه معمولا يك رويداد است. آبجكتهای داخل زنجيره، درخواست را در طول زنجيره می فرستند تا اينكه از آبجكتها رويداد را ايجاد میكنند. پس از اينكه رويداد انجام شد، پردازش متوقف میشود.
تصوير 1 نشان میدهد چگونه الگوی CoR درخواستها را پردازش میكند.
تصوير1- الگوی Chain of Resposibilityدر design Patterns، نويسندگان Chain of Resposibility را به شكل زير توصيف می كنند:
از مرتبط ساختن فرستنده يك درخواست به گيرندهاش از طريق دادن امكان به بيش از يك آبجكت جهت رسيدگی به درخواست جلوگيری كنيد. آبجكتهای دريافت كننده را زنجير كنيد و درخواست را در طول زنجير منتقل سازيد تا اينكه يك آبجكت به آن رسيدگی كند.
الگوی Chain of Resposibility عملی است اگر:
میخواهيد فرستنده و گيرنده يك درخواست را جدا سازيد،
چندين آبجكت، كه در زمان اجرا مشخص شدهاند، برای رسيدگی به يك درخواست در نظر گرفته شدهاند،
شما نمی خواهيد كنترل كنندههای handler را به طور واضح در كدتان مشخص نماييد.
اگر از الگوی CoR استفاده میكنيد به ياد داشته باشيد:
هر آبجكت در زنجيره، تنها به يك درخواست رسيدگی میكند.
ممكن است به برخی درخواستها رسيدگی نشود.
البته اين محدوديتها برای يك اجرای كلاسيك CoR هستند. در عمل، اين قواعد ناديده گرفته میشوند. برای مثال، فيلترهای Servlet يك اجرای CoR هستند كه به فيلترهای متعدد امكان پردازش يك درخواست HTTP را میدهند.
شكل 2 يك نمودار كلاس الگوی CoR را نشان میدهد.
تصوير2. دياگرام كلاس Chain of Resposibility
معمولا، كنترل كنندههای درخواست، پسوندهای يك كلاس مبنا هستند كه مرجعی به كنترل كننده بعدی در زنجيره را نگه میدارد (كه به عنوان Successor شناخته می شوند). كلاس مبنا ممكن است ()handleRequest را به شكل زير اجرا نمايد:
public abstract class HandlerBase {
...
public void handlerRequest(SomeRequestObject sro){
if(successor != null)
successor.handlerRequest(sro);
}
}
بنابراين، برای پيش فرض، كنترل كنندهها درخواست را به كنترل كننده بعدی در زنجيره انتقال میدهند. يك پسوند معين از HandlerBase ممكن است اينگونه به نظر آيد:
public class SpamFilter extends HandlerBase {
public void handlerRequest(SomeRequestObject mailMessage){
if(isSpam(mailMessage)){ //If the message is spam
// take spam-related action. Do not forward message.
}
else { // Message is not spam.
super.handlerRequest(mailMessage); // Pass message to the next filter in the chain.
}
}
}
چنانچه پيام اسپم باشد SpamFilter به درخواست (احتمالا رسيد e-mail جديد) رسيدگی می نمايد و در نتيجه درخواست جلوتر نمیرود. در غير اينصورت، پيامهای معتبر به كنترل كننده بعدی منتقل میشوند، كه احتمالا فيلتر e-mail ديگری است كه منتظر است تا آنها را جدا كند. در نهايت فيلتر آخر در زنجيره ممكن است پيام را پس از اينكه از طريق عبور از فيلترهای متعدد مورد تاييد قرار میگيرد، ذخيره سازد.
توجه داشته باشيد فيلترهای e-mail فرضی كه در بالا بحث شد با هم متناقضند: نهايتا يك فيلتر به يك درخواست رسيدگی میكند. ممكن است شما تصميم بگيريد آنرا از طريق دادن امكان به فيلترهای متعدد جهت رسيدگی به يك درخواست درهم بريزيد، كه در مقايسه بهتر از pipe های يونيكس است. در هر حال، موتور اصلی الگوی CoR است.
در اين مقاله، من در مورد دو اجرای الگوی Chain of Resposibility صحبت می كنم: فيلترهای Servlet، يك اجرای متداول CoR كه به فيلترهای متعدد امكان رسيدگی به يك درخواست را میدهد و مدل رويداد اصلی AWT يا Abstract Window Toolkit، يك اجرای كلاسيك كم طرفدار CoR كه در نهايت با آن مخالفت شد.
فيلترهای Servlet
در Java 2 Platform ابتدای كار Java 2 Enterprise Edition (J2EE)l برخی محفظههای Servlet يك ويژگی مناسب كه به عنوان زنجيرهای كردن Servlet شناخته میشد را ارائه نمودند، كه از آن طريق شخص میتوانست لزوما ليستی از فيلترها را برای Servlet به كار برد. فيلترهای Servlet رايج هستند، زيرا برای ايمنی، فشردهسازی، ثبت گردش كار و مواردی بيشتر قابل استفادهاند و البته، شما میتوانيد زنجيرهای از فيلترها را بسازيد تا برخی يا تمام آن كارها را بسته به شرايط زمان اجرا انجام دهد.
با ظهور نسخه Java Servlet Specification 2.3 فيلترها مولفههايی استاندارد شدند. برخلاف CoR كلاسيك، فيلترهای Servlet به آبجكتهای (فيلترها) متعدد در يك زنجيره اجازه میدهند به يك درخواست رسيدگی نمايند.
فيلترهای Servlet موارد الحاقی قدرتمندی برای J2EE هستند. همچنين، از نقطه نظر الگوهای طراحی، فيلترهای Servlet تحول جالبی ارائه میدهند. اگر میخواهيد درخواست يا پاسخ را تغيير دهيد، علاوه بر CoR از Decorator Pattern استفاده كنيد. شكل 3 نشان میدهد فيلترهای Servlet چگونه كار میكنند.
تصوير3- فيلترهای Servlet در زمان اجرا
يك فيلتر Servlet ساده
شما بايد جهت فيلتر كردن يك Servlet سه كار انجام دهيد:
يك Servlet را اجرا كنيد
يك فيلتر را پياده سازيد.
فيلتر و Servlet را مرتبط سازيد.
مثالهای 1-3 هر سه مرحله را به طور متوالی انجام میدهند:
مثال 1. يك 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");
}
}
مثال 2. يك فيلتر
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() { }
}
مثال 3. توصيف گر استقرار
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.3.dtd">
<web-app>
<filter>
<filter-name>auditFilter</filter-name>
<filter-class>AuditFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>auditFilter</filter-name>
<servlet-name>/filteredServlet</servlet-name>
</filter-mapping>
<!-- Servlet Configuration -->
<servlet>
<servlet-name>filteredServlet</servlet-name>
<servlet-class>FilteredServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>filteredServlet</servlet-name>
<url-pattern>/filteredServlet</url-pattern>
</servlet-mapping>
...
</web-app>اگر با URL/Filteredservlet به Servlet دسترسی پيدا میكنيد، auditfilter پيش از Servlet در درخواست وارد میشود. AuditFilter.dofilter به فايل log محفظه Servlet مینويسد و ()chain.dofilter را فرا میخواند تا درخواست را بفرستد. فيلترهای Servlet ملزم نيستند ()chain.dofilter را فراخوانند. اگر اينكار را نكنند درخواست فرستاده نمیشود. من میتوانم فيلترهای بيشتری را اضافه كنم كه به ترتيبی كه در فايل XML قبلی معرفی میشوند فعال خواهند شد.
اكنون كه شما يك فيلتر ساده را ديدهايد، بياييد فيلتر ديگری را بررسی كنيم كه پاسخ HTTP را تغيير میدهد.
"پاسخ را با الگوی Decorator فيلتر كنيد"
برخلاف فيلتر قبلی، برخی فيلترهای Servlet بايد درخواست يا پاسخ HTTP را تغيير دهند. جالب اينكه آن كار الگوی Decorator را وارد عمل میكند. من در مورد الگوی Decorator در دو مقاله قبلی Java Design Patterns بحث كردم: Amaze Your Developer Friends with Design Patterns و Decorator Your Java Code.
مثال 4 فيلتری را ليست میكند كه يك جستجو و جايگزينی ساده را در ساختار پاسخ انجام میدهد. آن فيلتر پاسخ Servlet را Decorator میكند و Decorator را به Servlet انتقال میدهد. وقتی Servlet كار نوشتن به پاسخ Decorator شده را تمام میكند فيلتر يك جستجو و جايگزينی درون محتوای پاسخ انجام میدهد.
مثال 4. يك فيلتر جستجو و جايگزينی
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;
}
}فيلتر قبلی بدنبال پارامترهای ابتدايی فيلتر با نام Search و replace میگردد. اگر آنها مشخص شوند، فيلتر اولين وقوع مقدار پارامتر Search را با مقدار پارامتر Replace جايگزين میكند.
()SearchAndReplace Filter.doFilter آبجكت پاسخ را با يك decorator)wrapper) كه جای پاسخ را میگيرد میپوشاند. وقتی ()chain.dofilter، ()SearchAndReplaceFilter.doFilter را جهت فرستادن درخواست فرا میخواند، ()chain.doFilter به جای پاسخ اصلی Wrapper را منتقل میسازد. در خواست به Servlet فرستاده میشود، كه پاسخ را ايجاد میكند.
وقتی ()chain.doFilter باز میگردد، درخواست در Servlet انجام میشود، بنابراين من دست به كار میشوم. اول پارامترهای فيلتر Search و Replace را چك میكنم. اگر موجود باشند رشته مربوط به Wrapper پاسخ را پيدا میكنم كه محتوای پاسخ است. سپس جايگزينی را انجام می دهم و آن را دوباره روی پاسخ پرينت میكنم.
مثال 5 كلاس StringWrapper را ليست مینمايد.
مثال 5. يك 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 كه پاسخ HTTP را در مثال 4 decorator میكند پسوندی از HttpServletResponseWrapper است كه زحمت ايجاد يك كلاس مبنای decorator برای decorate كردن پاسخهای HTTP را برای ما كم میكند. HttpServletResponseWrapper در نهايت اينترفيس ServletResponse را اجرا میكند، بنابراين نمونههای HttpServletResponseWrapper میتواند به هر متد كه در انتظار آبجكت ServletResponse است منتقل شود. به همين دليل است كه ()SearchAndReplace Filter.doFilter میتواند (chain.dofilter(request,wrapper را به جای (chain.dofilter(request,response فراخواند. اكنون كه يك Wrapper فيلتر و پاسخ داريم، بگذاريد فيلتر را با يك الگوی URL مرتبط سازيم و الگوهای جستجو جايگزينی را مشخص كنيم:
مثال 6. يك توصيف گر استقرار
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.3.dtd">
<web-app>
...
<filter>
<filter-name>searchAndReplaceFilter</filter-name>
<filter-class>SearchAndReplaceFilter</filter-class>
<init-param>
<param-name>search</param-name>
<param-value>Blue Road Inc.</param-value>
</init-param>
<init-param>
<param-name>replace</param-name>
<param-value>Red Rocks Inc.</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>searchAndReplaceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>اكنون من فيلتر جستجو و جايگزينی را از طريق آن ارتباط با الگوی */URL به تمام درخواستها مرتبط نموده و مشخص كردهام كه RedRocksInc جايگزين اولين وقوع Blue Road Inc خواهد شد. بياييد آن را روی اين صفحه JSP) JavaServerPages) امتحان كنيم:
مثال 7. يك صفحه JSP
به Blue Road Inc خوش آمديد.
شكل 4 خروجی صفحه JSP قبلی را نشان میدهد. توجه كنيد كه RedRocksInc جايگزين Blue Road Inc شده است.
تصوير 4- استفاده از يك فيلتر جستجو و جايگزينی.البته، فليتر جستجو و جايگزينی من به جز نشان دادن اينكه چگونه فيلترها میتوانند پاسخ را با يك Wrapper تغيير دهند كاربرد عملی اندكی دارد. به هر صورت، پيدا كردن فيلترهای Servlet مناسب قابل استفاده بطور رايگان آسان است. برای مثال ، Tomcat 4.1.x با فيلترهای زير میآيد:
يك فيلتر فشرده سازی كه پاسخهای بزرگتر از يك مدخل (كه میتوانيد به عنوان يك پارامتر فيلتر قرار دهيد) را zip میكند، يك فيلتر HTML و يك فيلتر كه اطلاعات در مورد يك درخواست را كپی میكند. شما میتوانيد آن فيلترها را تحت دايركتوری Tomcat's examples پيدا كنيد.
فيلترهای Servlet يك تغيير الگوی متداول CoR را نشان میدهند كه آبجكتهای متعدد در زنجيره ممكن است به يك درخواست رسيدگی نمايند. بگذاريدالگوی CoR را با نگاهی كوتاه به يك اجرای كلاسيك CoR كه با آن مخالفت شد خاتمه دهيم.
مدل رويداد AWT
AWT در اصل از الگوی CoR برای كنترل رويداد استفاده نمود. اين نحوه عمل آن است:
import java.applet.Applet;
import java.awt.*;
public class MouseSensor extends Frame {
public static void main(String[] args) {
MouseSensor ms = new MouseSensor();
ms.setBounds(10,10,200,200);
ms.show();
}
public MouseSensor() {
setLayout(new BorderLayout());
add(new MouseSensorCanvas(), "Center");
}
}
class MouseSensorCanvas extends Canvas {
public boolean mouseUp(Event event, int x, int y) {
System.out.println("mouse up");
return true; // Event has been handled. Do not propagate to container.
}
public boolean mouseDown(Event event, int x, int y) {
System.out.println("mouse down");
return true; // Event has been handled. Do not propagate to container.
}
}برنامه كاربردی قبلی يك بخش ايجاد میكند و آنرا به برنامه كاربردی اضافه مینمايد. آن بخش رويدادهای mouse up and down را به ترتيب از طريق اجرای mouseUp و ()mouseDown كنترل میكند. توجه كنيد كه آن متدها يك مقدار boolean را بر می گردانند: true مشخص میكند كه رويداد انجام شده است و در نتيجه نبايد به محفظه مولفه منتقل شود. false بدين معنی است كه رويداد به طور كامل انجام نشده است و بايد منتقل گردد. رويدادها طبقهبندی مولفه را طی میكنند، تا وقتی كه يك مولفه آنرا انجام دهد، چنانچه هيچ مولفهای علاقمند نباشد به رويداد توجهی نمی شود. اين يك اجرای كلاسيك Chain of Responsibility است.
استفاده از الگوی CoR برای اجرای رويداد محكوم به شكست شد، زيرا اجرای رويداد نياز به مولفههای زير مجموعهسازی دارد. بدليل اينكه يك اينترفيس كاربر گرافيكی معمولی (GUI) از مولفههای بسيار استفاده میكند و بيشتر اجزا حداقل به يك رويداد (و برخی به رويدادهای بسيار) علاقمندند، طراحان AWT میبايست زير مجموعههای متعدد را اجرا مینمودند. به طور كلی، ملزم نمودن وراثت به اجرای يك ويژگی با كاربرد بسيار بالا طراحی ضعيفی است، زير ا منجر به افزايش زير مجموعهها میشود.
مدل رويداد اصلی AWT درنهايت با Observer Pattern جايگزين شد كه به نام مدل delegation شناخته می شود كه الگوی CoR را حذف نمود. در اينجا چگونگی عملكرد آنرا میبينيم.
import java.awt.*;
import java.awt.event.*;
public class MouseSensor extends Frame {
public static void main(String[] args) {
MouseSensor ms = new MouseSensor();
ms.setBounds(10,10,200,200);
ms.show();
}
public MouseSensor() {
Canvas canvas = new Canvas();
canvas.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
System.out.println("mouse down");
}
public void mouseReleased(MouseEvent e) {
System.out.println("mouse up");
}
});
setLayout(new BorderLayout());
add(canvas, "Center");
}
}مدل delegation كه يك مولفه انجام رويداد را به آبجكت ديگری محول میكند نياز به گسترش كلاسهای مولفه ندارد كه راه حل بسيار سادهتری است. با مدل رويداد delegation، متدهای رويداد Void را بر میگردانند زير رويدادها ديگر به طور اتوماتيك به محفظه مولفه منتقل نمیشوند.
الگوی CoR برای رويدادهای AWT قابل استفاده نبود زيرا رويدادهای GUI ساختار بسيار مناسبی دارند. بدليل اينكه رويدادهای بسيار زيادی وجود دارد و مولفهها آنها را اغلب اجرا میكنند، مدل CoR منجر به افزايش زير مجموعهها میشود- يك GUI عادی به راحتی میتواند بيش از 50 زير مجموعه مولفه برای هدف واحد اجرای رويدادها داشته باشد. در نهايت، از انتقال رويدادها به طور اندكی استفاده شد. معمولا رويدادها توسط مولفهای كه از آنها منشا گرفتهاند انجام میشوند.
از طرف ديگر، الگوی CoR مورد بسيار مناسبی برای فيلترهای Servlet است. در مقايسه با رويدادهای GUI درخواستهای HTTP به ندرت اتفاق میافتند، بنابراين الگوی CoR بهتر میتواند آنها را انجام دهد و انتقال رويداد برای فيلترها بسيار مناسب است زيرا شما میتوانيد آنها را تركيب كنيد- بسيار شبيه pipeهای يونيكس كه در ابتدای اين مقاله در مورد آن بحث شد- تا اثرات بسيار زيادی پديد آيد.
لينك آخر
الگوی Chain of Responsibility به شما امكان جداسازی فرستنده يك رويداد از گيرندهاش بوسيله زنجيرهای از آبجكتها را میدهد كه كانديد انجام رويداد هستند. با الگوی كلاسيك CoR، يكی از آبجكتها يا هيچ كدامشان در زنجيره رويداد را انجام میدهد. اگر آبجكتی رويداد را انجام ندهد آنرا به آبجكت بعدی در زنجيره می فرستد. در اين مقاله، ما دو اجرای الگوی CoR را بررسی نموديم: مدل رويداد اصلی AWT و فيلترهای Servlet.
Copyright 2004 IDG News Service.All right reserved.
Copyright 2004, PC World Iran, All rights reserved.