ข้ามไปยังเนื้อหา

Adapter

Adapter แปลง interface ของ object ที่มีอยู่ให้เป็น interface ที่ client คาดหวัง ทำให้ class ที่ปกติทำงานร่วมกันไม่ได้เพราะรูปแบบของ method ไม่ตรงกันสามารถทำงานร่วมกันได้

code ของคุณถูกเขียนขึ้นอิงกับ interface ที่สะอาดซึ่งคุณควบคุมเอง จากนั้นคุณต้องเสียบ library ของบุคคลที่สาม โมดูลเก่า หรือ external API บางตัวที่ทำงานได้ถูกต้องแต่เปิดเผยรูปแบบที่ผิด ไม่ว่าจะเป็นชื่อ method ที่ต่างกัน ลำดับ argument ที่ต่างกัน หรือข้อมูลในรูปแบบที่คุณไม่ต้องการ คุณเปลี่ยน code ภายนอกนั้นไม่ได้ และการเขียน code ทั้งโปรเจกต์ของคุณใหม่เพื่อให้เข้ากับ code ภายนอกนั้นก็เป็นเรื่องไร้สาระ

Adapter คือชั้นแปลภาษาบาง ๆ ที่อยู่ตรงกลาง โดย implement interface ที่ client คาดหวัง และเบื้องหลังหน้ากากนั้นก็ส่งต่อการเรียกแต่ละครั้งไปยัง object ภายนอก พร้อมแปลงชื่อ argument และค่าที่ return ตามที่จำเป็น client ยังคงคุยกับ interface ที่รู้จักอยู่แล้ว ส่วน adapter รับเอาความไม่เข้ากันมาดูดซับ คุณสามารถสลับ service ภายนอกตัวหนึ่งไปเป็นอีกตัวได้ด้วยการเขียน adapter ใหม่ โดยไม่ต้องแตะต้องทุกอย่างที่อยู่ต้นน้ำเลย

classDiagram
  class Target {
    <<interface>>
    +fetch(city) Weather
  }
  class Client
  class Adapter {
    -service: LegacyWeatherAPI
    +fetch(city) Weather
  }
  class LegacyWeatherAPI {
    +getTempByZip(zip) number
  }
  Target <|.. Adapter
  Adapter --> LegacyWeatherAPI : delegates to
  Client --> Target : uses
adapter implement interface เป้าหมายและ delegate ไปยัง service ภายนอก
  • Target — interface ที่ code client ของคุณอิงอยู่และต้องการเรียกใช้
  • Adaptee (LegacyWeatherAPI) — object ที่มีอยู่แล้วซึ่งมีความสามารถที่มีประโยชน์แต่มี interface ที่เข้ากันไม่ได้
  • Adapter — implement interface เป้าหมายและแปลการเรียกแต่ละครั้งไปเป็นการเรียกหนึ่งครั้งหรือหลายครั้งบน adaptee
  • Client — ขึ้นต่อ interface เป้าหมายเท่านั้น โดยไม่รับรู้ถึง adaptee ที่อยู่เบื้องหลัง

แอปของคุณคาดหวัง WeatherProvider ที่รับชื่อเมืองและคืนค่าองศาเซลเซียส แต่ service ของบุคคลที่สามรับเฉพาะรหัสไปรษณีย์และคืนค่าองศาฟาเรนไฮต์ adapter จะเชื่อมช่องว่างนั้น

// Target interface our app is written against.
interface WeatherProvider {
currentCelsius(city: string): number;
}
// The foreign service we cannot change.
class LegacyWeatherApi {
tempFahrenheitByZip(zip: string): number {
return 68; // pretend network call
}
}
const cityToZip: Record<string, string> = { London: 'EC1A', Tokyo: '100' };
class LegacyWeatherAdapter implements WeatherProvider {
constructor(private readonly service: LegacyWeatherApi) {}
currentCelsius(city: string): number {
const zip = cityToZip[city] ?? '00000';
const f = this.service.tempFahrenheitByZip(zip);
return Math.round(((f - 32) * 5) / 9);
}
}
const provider: WeatherProvider = new LegacyWeatherAdapter(new LegacyWeatherApi());
console.log(provider.currentCelsius('London')); // 20
  • ข้อดี: ทำให้คุณนำ code ที่มีอยู่หรือ code ของบุคคลที่สามซึ่งคุณเปลี่ยน interface ไม่ได้กลับมาใช้ใหม่ได้
  • ข้อดี: แยกการแปลงไว้ที่จุดเดียว ดังนั้นการสลับ service จึงหมายถึงการเขียน adapter ใหม่เพียงตัวเดียว
  • ข้อดี: ทำให้ client ขึ้นต่อ interface เป้าหมายที่สะอาดเท่านั้น ช่วยให้ทดสอบได้ง่ายขึ้น
  • ข้อเสีย: เพิ่มชั้นของ indirection และ class พิเศษหนึ่งตัวสำหรับทุก adaptee ที่คุณห่อ
  • ข้อเสีย: การแปลงที่ซับซ้อนซ่อนงานจริงไว้ภายใน adapter ซึ่งอาจกลบความไม่เข้ากันด้านประสิทธิภาพหรือความหมาย
  • Bridge ดูคล้ายกันแต่ถูกออกแบบไว้ล่วงหน้าเพื่อให้ abstraction และ implementation เปลี่ยนแปลงได้ ขณะที่ Adapter เข้ามาปรับ interface สองตัวที่มีอยู่แล้วในภายหลัง
  • Decorator ก็ห่อ object เช่นกัน แต่คงไว้ซึ่ง interface เดิมและเพิ่มพฤติกรรม ขณะที่ Adapter เปลี่ยน interface
  • Facade ห่อ subsystem ทั้งก้อนไว้หลัง interface ที่เรียบง่ายตัวใหม่ ขณะที่ Adapter มักห่อ object ตัวเดียวเพื่อให้ตรงกับตัวที่มีอยู่
AdapterBridgeFacade
จุดประสงค์แก้ interface ที่ไม่ตรงกันแยก abstraction จาก implementationซ่อนความซับซ้อนของระบบ
เวลาที่ใช้หลังจากที่ทั้งสองฝั่งมีอยู่แล้วออกแบบตั้งแต่ต้นเมื่อ subsystem มีมากเกินไป
ฝั่งที่เปลี่ยนinterface ของ adapteeทั้ง abstraction และ implementationไม่เปลี่ยน เพียงห่อหุ้ม
ตัวอย่างArray.from(), JDBC driverDOM renderer vs paint engineaxios, SDK facade
สิ่งสำคัญที่ Adapter เปลี่ยนเกี่ยวกับ object คืออะไร?
ใน Adapter pattern อะไรคือ "adaptee"?
Adapter แตกต่างจาก Decorator อย่างไร?
ทำไม Adapter pattern จึงมีประโยชน์กับ code ของบุคคลที่สาม?