الگوی Adapter

الگوی Adapter، در مورد incompatible interfaces یا interfaceهای ناسازگار و متفاوت صحبت می کند. موضوع interface، در اینجا به معنای رابط است و صرفا در مورد interfaceهایی که در زبان های برنامه نویسی استفاده می شود نیست. adapter امکان کار کردن کلاس هایی که به خاطر interfaceهای متفاوت نمی توانند با هم کار کنند را فراهم می کند.الگوی Adapterممکن است در برنامه خود، یک انتزاع داشته باشیم که از یک library یا یک کلاس موجود در برنامه استفاده می کند. اما می خواهیم از interface خود در برنامه استفاده کنیم که ممکن است با آن library interface متفاوت باشد و یا نمی خواهیم به آن وابستگی داشته باشیم. بنابراین، رفتارهایی که می خواهیم را تعریف می کنیم و اگر interfaceهای متفاوتی وجود دارد، adapterهای مختلفی را نیز می نویسیم.در مثال لاگ، فرض کنید ابزار Log4Net، از Log.Write برای نوشتن استفاده می کند و ابزار Serilog از Logger.Information استفاده می کند. ما interface خود را می شناسیم و از طریق نوشتن adapter، ابزارهای مختلف را به interface خود تبدیل می کنم.public interface ILogger { void WriteDebug(string message); void WriteInfo(string message); void WriteWarning(string message); } public class SerilogAdapter : ILogger { private readonly Serilog.ILogger _logger; public SerilogAdapter(Serilog.ILogger logger) { _logger = logger; } public void WriteDebug(string message) { _logger.Debug(message); } public void WriteInfo(string message) { _logger.Information(message); } public void WriteWarning(string message) { _logger.Warning(message); } }در تصویر بالا بخش Target، اینترفیسی است که client آن را می شناسد و از آن استفاده می کند. در مثال ما ILogger نقش target را دارد.بخش Adaptee قسمتی است که باید با interface ما سازگار شود که در مثال ما serilog می باشد. و بخش Adapter که در این مثال SerilogAdapter است که target را پیاده سازی می کند و در این روش، از adaptee یک composition دارد. از مثال های پرکاربرد دیگر می تونیم به repository و acl اشاره کنیم که آن ها نیز نوعی adapter هستند و ما از api دلخواه خود استفاده می کنیم.در پیاده سازی الگوی adapter دو نوع پیاده سازی داریم که نسبت به هم trade-off دارند. در مثالی که زده شد از نوع Object Adapter استفاده شد که از composition استفاده می کند. به این صورت که به adaptee بصورت یک object دسترسی دارد. در روش دیگر از Inheritance برای این موضوع استفاده می شود. به این صورت که با عملیات ارث بری از adaptee می تونیم به اجزای base class دسترسی داشته باشیم.در مواردی که ترجیح می دهیم از حالت object adapter استفاده کنیم، می تواند بخاطر multi inheritance نبودن برخی زبان ها مانند سی شارپ و زمانی که کلاس adaptee بصورت sealed تعریف شده باشد و قابل ارث بری نباشد.تنها نکته مثبتی که در مدل inheritance دیده می شود، این است که ما ممکن است بخواهیم یکسری از رفتارهای adaptee را override کنیم. اما از آنجایی که ما وابستگی مشخصی به یک concrete class داریم، بنابراین adapter را برای آن third-party مشخص ایجاد می کنیم. اما در object adapter در مثال بالا، وجود یک object از نوع آن interface، به این معناست که در مورد adaptee و concrete بودن آن اطلاعی نداریم و می تواند از هر نوعی باشد.بنابراین object adapter می تواند انتخاب بهتری باشد مگر در مواردی که رفتاری در adaptee باشد که نیازمند override باشد. گاهی adapter به عنوان یک ابزار، برای مواردی مورد استفاده قرار می گیرد که ممکن است در آینده اتفاق بیافتد. ما به عنوان یک طراح، ممکن است به سراغ یک interface برویم که احتمال می دهیم آن interface در آینده تغییر کند یا interfaceهای دیگر جایگزین آن شوند. در اینگونه مواقع برای ایجاد extensibility point، می تونیم از adapter استفاده کنیم. به این معنی که نقاطی را در طراحی ایجاد کنیم که بعدا بتونیم چیزی را به آن اضافه کنیم.به طور خلاصه، در این الگو اینترفیسی که آن را می شناسیم و رفتاری که از آن انتظار داریم، commonality می باشد و incompatible interfaceها نیز variability می باشند.