Adapter Pattern (mẫu thiết kế tiếp hợp)  
 

(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


 
 

 
     
 
Công nghệ khác:


AJAX - sự kết hợp kỳ diệu của công nghệ webSOA: Ngôi sao mới trong lĩnh vực phần mềm
AOP: Aspect Oriented ProgrammingGrid computing - Part 1
Use Case DiagramUnit Test với phát triển phần mềm hiện đại
  Xem tiếp    
 
Lịch khai giảng của hệ thống
 
Ngày
Giờ
T.Tâm
TP Hồ Chí Minh
Hà Nội
 
   
New ADSE - Nhấn vào để xem chi tiết
Mừng Sinh Nhật Lần Thứ 20 FPT-APTECH
Nhấn vào để xem chi tiết
Bảng Vàng Thành Tích Sinh Viên FPT APTECH - Nhấn vào để xem chi tiết
Cập nhật công nghệ miễn phí cho tất cả cựu sinh viên APTECH toàn quốc
Tiết Thực Vì Cộng Đồng
Hội Thảo CNTT
Những khoảnh khắc không phai của Thầy Trò FPT-APTECH Ngày 20-11