(Post 24/03/2006) Adapter Pattern là pattern
giữ vai trò trung gian giữa hai lớp, chuyển đổi giao diện của một hay
nhiều lớp có sẵn thành một giao diện khác, thích hợp cho lớp đang viết.
Điều này cho phép các lớp có các giao diện khác nhau có thể dễ dàng giao
tiếp tốt với nhau thông qua giao diện trung gian, không cần thay đổi code
của lớp có sẵn cũng như lớp đang viết. Adapter Pattern còn gọi là Wrapper
Pattern do cung cấp một giao diện “bọc ngoài” tương thích cho một hệ thống
có sẵn, có dữ liệu và hành vi phù hợp nhưng có giao diện không tương thích
với lớp đang viết.
“Convert the interface of a class into another interface
clients expect. Adapter lets classes work together that couldn't otherwise
because of incompatible interfaces.” [GoF]
1. Định nghĩa
Adapter Pattern là pattern giữ vai trò trung gian giữa
hai lớp, chuyển đổi giao diện của một hay nhiều lớp có sẵn thành một giao
diện khác, thích hợp cho lớp đang viết. Điều này cho phép các lớp có các
giao diện khác nhau có thể dễ dàng giao tiếp tốt với nhau thông qua giao
diện trung gian, không cần thay đổi code của lớp có sẵn cũng như lớp đang
viết. Adapter Pattern còn gọi là Wrapper Pattern do cung cấp một giao
diện “bọc ngoài” tương thích cho một hệ thống có sẵn, có dữ liệu và hành
vi phù hợp nhưng có giao diện không tương thích với lớp đang viết.
Adapter Pattern được dùng rất nhiều khi xây dựng các
thư viện chuẩn: template queue trong STL là một Adapter cho deque hoặc
list (dùng “chuyển” dữ liệu từ deque ra queue), các Adapter dùng phổ biến
trong Java AWT (WindowAdapter, ComponentAdapter, ContainerAdapter, FocusAdapter,
KeyAdapter, MouseAdapter và MouseMotionAdapter), …
Ví dụ: giao diện WindowListner có 7 phương thức. Khi
lớp lắng nghe sự kiện của ta cài đặt giao diện này, cần phải cài đặt tất
cả 7 phương thức. Lớp WindowAdapter cài đặt giao diện WindowListener,
cài đặt sẵn và rỗng cả 7 phương thức. Như vậy nếu lớp lắng nghe sự kiện
của ta thừa kế lớp WindowAdapter, chỉ cài đặt lại một vài phương thức
ta muốn giới hạn.
public interface Windowlistener {
public void windowClosed( WindowEvent e );
public void windowOpened( WindowEvent e );
public void windowIconified( WindowEvent e );
public void windowDeiconified( WindowEvent e );
public void windowActivated( WindowEvent e );
public void windowDeactivated( WindowEvent e );
public void windowClosing( WindowEvent e );
} |
public class WindowAdapter implements WindowListner{
public void windowClosed( WindowEvent e ) { }
public void windowOpened( WindowEvent e ) { }
public void windowIconified( WindowEvent e ) { }
public void windowDeiconified( WindowEvent e ) { }
public void windowActivated( WindowEvent e ) { }
public void windowDeactivated( WindowEvent e ) { }
public void windowClosing( WindowEvent e ) { }
}
|
import javax.swing.*;
import java.awt.event.*;
class Client extends JFrame {
public Client() {
setSize( 200, 200 );
setVisible( true );
addWindowListener( new Closer() );
}
public static void main( String[] args ) {
new Client();
}
class Closer extends WindowAdapter {
public void windowClosing( WindowEvent e ) {
System.exit( 0 );
}
}
}
|
2. Lợi ích
Việc sử dụng Adapter Pattern đem lại các lợi ích sau:
- Cho phép nhiều đối tượng có giao diện giao tiếp khác nhau có thể
tương tác và giao tiếp với nhau.
- Tăng khả năng sử dụng lại thư viện với giao diện không thay đổi
do không có mã nguồn.
3. Trường hợp sử dụng
Người lập trình có thể dùng Adapter Pattern trong những
trường hợp sau:
- Người lập trình muốn sử dụng một lớp đã tồn tại trước đó nhưng giao
diện sử dụng không phù hợp như mong muốn.
- Khi muốn tạo ra những lớp có khả năng sử dụng lại, chúng phối hợp
với các lớp không liên quan hay những lớp không thể đoán trước được
và những lớp này không có những giao diện tương thích.
- Cần phải có sự chuyển đổi giao diện từ nhiều nguồn khác nhau.
- Khi giao diện mong muốn không phải là interface mà là một lớp trừu
tượng hay muốn tiếp hợp nhiều đối tượng cùng một lúc.
4. Cách thực hiện
Các thành phần tham gia Adapter Pattern:
- Adaptee: định nghĩa giao diện không tương thích, cần được tiếp hợp
vào.
- Adapter: lớp tiếp hợp, giúp giao diện không tương thích tiếp hợp
được với giao diện đang làm việc.
- Target: định nghĩa giao diện đang làm việc (domain specific).
- Client: lớp sử dụng các đối tượng có giao diện Target.
- Có hai cách để thực hiện Adapter Pattern:
- Tiếp hợp lớp (Class Adapter Pattern).
- Tiếp hợp đối tượng (Object Adapter Pattern).
a) Tiếp hợp lớp (dùng thừa kế – inheritance)
Trong mô hình tiếp hợp lớp, một lớp mới (Adapter) sẽ
kế thừa lớp có sẵn với giao diện không tương thích (Adaptee), đồng thời
cài đặt giao diện mà người dùng mong muốn (Target). Trong lớp mới, khi
cài đặt các phương thức của giao diện người dùng mong muốn, phương thức
này sẽ gọi các phương thức cần thiết mà nó thừa kế được từ lớp có giao
diện không tương thích.
Tiếp hợp lớp đơn giản nhưng dễ gặp trường hợp đụng độ
tên phương thức. Ví dụ:
public class Adaptee {
public void specificRequest( String text ) {
System.out.println( text.toUpperCase() );
}
}
|
public interface Target {
public void request();
}
|
public class TargetImpl implements Target
{
public void request() {
System.out.println( text );
}
}
|
public class Adapter extends Adaptee implements
Target {
public void request() {
specificRequest( text );
}
}
|
public class Client {
Target[] target = new Target[10];
public void initTarget() {
target[0] = new TargetImpl();
target[1] = new Adapter();
// ...
}
public void operation() {
for ( int i = 0; i < target.length; ++i )
target.request();
}
} |
b) Tiếp hợp đối tượng (dùng tích hợp – composition)
Trong mô hình tiếp hợp đối tượng, một lớp mới (Adapter)
sẽ tham chiếu đến một (hoặc nhiều) đối tượng của lớp có sẵn với giao diện
không tương thích (Adaptee), đồng thời cài đặt giao diện mà người dùng
mong muốn (Target). Trong lớp mới này, khi cài đặt các phương thức của
giao diện người dùng mong muốn, sẽ gọi phương thức cần thiết thông qua
đối tượng thuộc lớp có giao diện không tương thích.
Tiếp hợp đối tượng tránh được vấn đề đa thừa kế, không
có trong các ngôn ngữ hiện đại (Java, C#). Ví dụ:
public abstract class Target {
public abstract void request();
}
|
public class TargetImpl extends Target {
public void request() {
System.out.println( text );
}
}
|
public class Adapter extends Target {
Adaptee obj = new Adaptee();
public void request() {
obj.specificRequest( text );
}
}
|
Tùy theo cách tiếp hợp của đối tượng Adapter, ta có một
số biến thể:
- Pluggable Adapter: cho phép tiếp hợp động với vài lớp. Dĩ nhiên
Adapter chỉ tiếp họp chỉ có thể tiếp hợp đến các lớp mà nó “nhận thấy”,
thông thường Adapter sẽ quyết định lớp nào được tiếp hợp với Target
dựa trên các phương thức constructor hoặc các phương thức thiết lập
đối số…
- Two-way Adapter: hỗ trợ cả hai giao diện Target và Adaptee. Nó cho
phép một đối tượng Adapter xuất hiện như một đối tượng Target hoặc
một đối tượng Adaptee.
5. Các pattern liên quan
- Bridge Pattern: có cấu trúc tương tự nhưng mục tiêu khác (tách một
giao diện khỏi phần cài đặt).
- Decorator Pattern: cung cấp thêm chức năng nhưng không làm thay
đổi giao diện, ở đây một Adapter sẽ phối hợp hai đối tượng khác nhau.
- Proxy Pattern: định nghĩa một giao diện đại diện cho các đối tượng
khác mà không làm thay đổi giao diện của các đối tượng được đại diện,
điều này thực hiện được nhờ các Adapter.
6. Ví dụ minh họa
a) Java
package adaptee;
import javax.swing.*;
public class Adaptee {
public void specificRequest() {
JOptionPane.showMessageDialog( null, "Hello Design Pattern",
"from Adaptee", JOptionPane.OK_OPTION );
}
} |
package adapter;
public class Target {
public void operation() {
System.out.println( "Hello Design Pattern" );
}
} |
package adapter;
import adaptee.Adaptee;
public class Adapter extends Target {
private Adaptee obj = new Adaptee();
public void operation() {
request();
}
private void request() {
obj.specificRequest();
}
} |
package clients;
import adapter.*;
public class AdapterClient {
static public Target Create( String str ) {
if ( str.equals( "Target" ) ) return new Target();
if ( str.equals( "Adapter" ) ) return new Adapter();
return null;
}
static public void main( String[] args ) {
Target target1 = Create( "Target" );
Target target2 = Create( "Adapter" );
target1.operation();
target2.operation();
}
} |
b) C++
#include <iostream>
#include <string>
#include <windows.h>
using namespace std;
namespace adaptee {
class Adaptee {
public:
Adaptee() { }
virtual ~Adaptee() { }
void specificRequest() {
MessageBox( 0, "Hello Design Pattern",
"from Adaptee", MB_OK|MB_SETFOREGROUND );
}
};
}
namespace adapter {
class Target {
public:
Target() { }
virtual ~Target() { }
virtual void operation() const {
cout << "Hello Design Pattern" <<
endl;
}
};
using namespace adaptee;
class Adapter : public Target {
public:
Adapter() { obj = new Adaptee(); }
virtual ~Adapter() { delete obj; }
virtual void operation() const { request(); }
private:
Adaptee* obj;
void request() const {
obj->specificRequest();
}
};
}
using namespace adapter;
Target* Create( const string& sw ) {
if ( sw == "Target" ) return new Target();
if ( sw == "Adapter" ) return new Adapter();
return NULL;
}
int main() {
Target* target1 = Create( "Target" );
Target* target2 = Create( "Adapter" );
target1->operation();
target2->operation();
delete target1;
delete target2;
return 0;
}
|
c) C#: Console Application, thêm reference:
System.Windows.Form.dll
using System;
using System.Windows.Forms;
namespace adaptee {
public class Adaptee{
public void SpecificRequest(){
MessageBox.Show( null, "Hello Design Pattern",
"from Adaptee", MessageBoxButtons.OK );
}
}
}
namespace adapter {
public class Target{
virtual public void Operation() {
Console.Out.WriteLine( "Hello Design Pattern"
);
}
}
public class Adapter : Target{
private adaptee.Adaptee adaptee = new adaptee.Adaptee();
override public void Operation(){
Request();
}
private void Request(){
adaptee.SpecificRequest();
}
}
}
namespace clients {
public class AdapterClient{
public static adapter.Target Create( String str ) {
switch ( str ){
case "Target": return new adapter.Target();
case "Adapter": return new adapter.Adapter();
default: return null;
}
}
public static void Main( string[] args ){
adapter.Target target1 = Create( "Target" );
adapter.Target target2 = Create( "Adapter" );
target1.Operation();
target2.Operation();
}
}
} |
Dương Thiên Tứ
Faclulty FPT-Aptech
|