ارث بری از یک کلاس، یک مکانیسم قدرتمندی است. اما قدرت واقعی وراثت از یک اینترفیس نشات میگرد. یک اینترفیس، اعضایی را ارائه می دهد که یک کلاس که از یک اینترفیس ارث بری میکند باید آنها را پیاده سازی کند. ما میتوانیم به اینترفیس به عنوان یک قرارداد نگاه کنیم که بیانگر این است که یک کلاس که یک اینترفیس را پیاده سازی میکند باید کل اعضای درون آن اینترفیس را پیاده سازی کند.
این مقاله، سرفصلی از دوره آموزشی زیر میباشد:
- کلاسها و سازنده ها
- ویژگیها
- constant ،Static member ها و Extension Method ها
- Anonymous Type ها و Nullabel Type ها
- ساختارها
- شمارنده ها
- وراثت
- Interface ها
- کلاسهای Abstract
- Generic ها
- Queue, Stack, Hashtable
- Generic List و Dictionary
- Delegate ها
اگر می خواهید محتویات کامل این دوره را ببینید ، می توانید بر روی لینک سطح متوسطه #C کلیک کنید.
برای دانلود سورس، رو لینک سورس اینترفیسها در #C کلیک کنید.
ما این مقاله را به قسمتهای زیر تقسیم بندی میکنیم:
- تعریف یک اینترفیس
- پیاده سازی یک اینترفیس
- ارجاع دادن کلاسها از طریق اینترفیسها
- استفاده از یک اینترفیس برای جدا کردن کلاسها
- کار کردن با چند اینترفیس
- پیاده سازی صریح اینترفیس
- نتیجه گیری
تعریف یک اینترفیس
برای تعریف یک اینترفیس، ما باید از کلمه interface استفاده کنیم. تعریف آن کاملا شبیه تعریف کلاس است با این تفاوت که از کلمه دیگری برای تعریف اینترفیس استفاده میکنیم. داخل اینترفیس، ما اعضا را بدون access modifier و بدنه پیاده سازی، مشخص میکنیم. بنابراین ما فقط اعضای درون اینترفیس را تعریف میکنیم. پیاده سازی آن، درون کلاسی که آن اینترفیس را پیاده سازی میکند انجام میشود:
1 2 3 4 |
interface InterfaceName { returnType methodName(paramType paramName...); } |
پیاده سازی یک اینترفیس
برای پیاده سازی یک اینترفیس، ما یک کلاس یا ساختار را تعریف میکنیم که از آن اینترفیس ارث بری کند و تمام اعضای درون آن را پیاده سازی کند:
1 2 3 4 |
class ClassName: InterfaceName { //members implementation } |
حالا اجازه بدید تا تمام این موارد را از طریق مثال زیر ببینیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public interface IWriter { void WriteFile(); } public class XmlWritter: IWriter { public void WriteFile() { Console.WriteLine("Writing file in the XmlWriter class."); } } public class JsonWriter: IWriter { public void WriteFile() { Console.WriteLine("Writing file in the JsonWritter class."); } } |
همانطور که در مثال میبینیم، بعد از ارث بری کردن کلاسهایمان از یک اینترفیس، آنها موظف هستند که عضو ()WriteFile را پیاده سازی کنند در غیر اینصورت با خطای کامپایلر مواجه میشویم.
وقتیکه ما یک اینترفیس را پیاده سازی میکنیم، باید با رعایت قوانین زیر، از پیاده سازی متد اطمینان حاصل کنیم:
- نام متدها و نوع های برگشتی آنها باید با نام و نوع برگشتی متد در اینترفیس دقیقا مطابقت داشته باشد.
- تمام پارامترها باید دقیقا مطابقت داشته باشند.
- تمام متدها باید در حین پیاده سازی، public باشند. این فقط در مورد پیاده سازی صریح اینترفیس صدق نمی کند (جلوتر در مورد آن صحبت خواهیم کرد).
یک کلاس همزمان میتواند هم از یک کلاس ارث بری کرده و هم یک اینترفیس را نیز پیاده سازی کند. اما در همچین حالتی، ما ابتدا باید کلاس پایه مورد نظر را مشخص کنیم و سپس با قرار دادن یک کاما، نام اینترفیس را بعد از آن تعیین کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public interface IWriter { void WriteFile(); } public class FileBase { public virtual void SetName() { Console.WriteLine("Setting name in the base Writer class."); } } public class XmlWritter: FileBase, IWriter { public void WriteFile() { Console.WriteLine("Writing file in the XmlWriter class."); } public override void SetName() { Console.WriteLine("Setting name in the XmlWriter class."); } } public class JsonWriter: FileBase, IWriter { public void WriteFile() { Console.WriteLine("Writing file in the JsonWritter class."); } public override void SetName() { Console.WriteLine("Setting name in the JsonWriter class."); } } |
ارجاع دادن کلاسها از طریق اینترفیسها
همانطور که ما میتوانیم یک آبجکت را با استفاده از یک متغیر کلاس رفرنس دهیم، میتوانیم یک آبجکت را با استفاده از یک متغیر اینترفیس نیز تعریف کنیم:
1 2 3 |
XmlWriter writer = new XmlWriter(); writer.SetName(); //overridden method from a base class writer.WriteFile(); //method from an interface |
همانطور که میبینیم، تمام متدها از طریق آبجکت writer مورد دسترس هستند. اما حالا میخواهیم از یک آبجکت اینترفیس برای رفرنس دادن استفاده کنیم:
1 2 3 |
IWriter writer = new XmlWriter(); writer.WriteFile(); //method from an interface writer.SetName(); //error the SetName method is not part of the IWriter interface |
اگر ما از یک اینترفیس برای ایجاد یک آبجکت استفاده کنیم، در اینصورت فقط میتوانیم به اعضای تعریف شده درون آن اینترفیس دسترسی داشته باشیم.
همانطور که در بالا اشاره کردیم، اینترفیس یک قرارداد را برای کلاسی که از آن ارث بری میکند ارائه میدهد و این یک مزیت عالی از استفاده از اینترفیس ها به حساب می آید. از این جهت که ما همیشه میتوانیم مطمئن باشیم که یک کلاس که از یک اینترفیس ارث بری میکند حتما تمام عضوهای آن را پیاده سازی خواهد کرد.
اما پیاده سازی اینترفیس، مزیتهای بیشتر از این هم نیز دارد. یکی از آنها، جدا کردن آبجکت (object decoupling) است.
استفاده از یک اینترفیس برای جداسازی کلاسها
زمانیکه یک کلاس به یک کلاس دیگر وابسته است اصطلاحا آن کلاسها به یکدیگر وابستگی (coupled) دارند. این چیزی است که ما میخواهیم از آن اجتناب کنیم. زیرا اگر تغییری در کلاس A صورت گیرد و کلاس B به شدت به کلاس A وابسته باشد، آنگاه احتمال زیادی وجود دارد که ما مجبور به تغییر کلاس B نیز باشیم. یا حداقل، ما نمیتوانیم همچنان مطمئن باشیم که کلاس B هنوز به درستی کار میکند یا خیر. در نتیجه، ما میخواهیم کلاسهایمان کمتر به هم وابسته باشند (loosely coupled) یا جدا از هم باشند (decoupled).
اجازه دهید تا ببینیم که چه اتفاقی می افتد اگر کلاسهای ما به شدت به هم وابسته باشند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class XmlFileWriter { private XmlWriter _xmlWriter; public XmlFileWriter(XmlWriter xmlWriter) { _xmlWriter = xmlWriter; } public void Write() { _xmlWriter.WriteFile(); } } |
XmlFileWriter یک کلاسی است که هدفش نوشتن درون یک فایل xml است. حالا ما میتوانیم کلاس XmlWriter
را نمونه سازی کنیم و آبجکت را از طریق سازنده XmlFileWriter ارسال کنیم و متد Write را فراخوانی کنیم:
1 2 3 4 5 6 7 8 9 |
class Program { static void Main(string[] args) { XmlWriter xmlWriter = new XmlWriter(); XmlFileWriter fileWriter = new XmlFileWriter(xmlWriter); fileWriter.Write(); } } |
اوکی تا اینجا همه چیزی عالی کار میکند.
اما ما اینجا چند مشکل داریم. کلاس XmlFileWriter ما به شدت به کلاس XmlWriter وابستگی دارد. اگر ما متد WriteFile را داخل کلاس XmlWriter تغییر دهیم سپس آن را باید در کلاس XmlFileWriter نیز تغییر دهیم. بنابراین تغییر در یک کلاس، منجربه تغییر در دیگری نیز میشود. کاری به این نداریم که میخواهیم کد ما چطور کار کند.
مسئله چیز دیگری است. ما قطعا میخواهیم همچین رفتاری را برای کلاس JsonWriter نیز داشته باشیم. ما نمیتوانیم از کلاس XmlFileWriter استفاده کنیم (زیرا آن فقط آبجکت XmlWriter را میپذیرد)، ما باید یک کلاس دیگر ایجاد کنیم و تمام اقدامات را برای این کلاس جهت استفاده از JsonWriter نیز تکرار کنیم که این خیلی میتواند بد باشد.
در آخر ما میتوانیم از خودمان بپرسیم که آیا ما واقعا از دو کلاس برای یک کار مشابه نیاز داریم. چرا ما نتوانیم فقط از یک کلاس استفاده کنیم؟ خب، اینجا جایی است که اینترفیس به کمک ما می آید.
حال اجازه دهید کلاس XmlFileWriter را تغییر دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class FileWriter { private readonly IWriter _writer; public FileWriter(IWriter writer) { _writer = writer; } public void Write() { _writer.WriteFile(); } } |
عالی. این یکی خیلی بهتر است.
حالا اینجا نام کلاس گویای این است که این کلاس فقط فایلهای xml نمینویسد. علاوه بر این، ما سازنده را فقط به پذیرش کلاس XmlWiter محدود نکرده ایم، بلکه به همه کلاسهایی که از اینترفیس IWriter ارث بری میکنند محدود کرده ایم. نام متد WriteFile نمیتواند تغییر کند چرا که اینترفیس IWritter بیان میکند که تمام کلاسها باید یک متد با یک نام یکسان را پیاده سازی کنند. حالا ما میتوانیم ببینیم که کلاس FileWriter مستقل از کلاس XmlWriter
یا JsonWriter است و اینکه ما میتوانیم آبجکتهای هر دو کلاس را به کلاس FileWriter بفرستیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Program { static void Main(string[] args) { XmlWriter xmlWriter = new XmlWriter(); JsonWriter jsonWriter = new JsonWriter(); FileWriter fileWriter = new FileWriter(xmlWriter); fileWriter.Write(); fileWriter = new FileWriter(jsonWriter); fileWriter.Write(); Console.ReadKey(); } } |
آیا این خیلی بهتر نیست؟
حالا ما یک کلاس داریم که کارش را برای هر کلاسی که از اینترفیس IWriter ارث بری میکند انجام میدهد.
این ویژگی به عنوان تزریق وابستگی (Dependency Injection) شناخته میشود.
کار کردن با چند اینترفیس
یک کلاس فقط میتواند از یک کلاس ارث بری کند، اما میتواند چند اینترفیس را پیاده سازی کند. آن کلاس باید تمام متدهای تعریف شده در آن اینترفیسها را پیاده سازی کند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public interface IFormatter { void FormatFile(); } public class XmlWriter: FileBase, IWriter, IFormatter { public void WriteFile() { Console.WriteLine("Writing file in the XmlWriter class."); } public override void SetName() { Console.WriteLine("Setting name in the XmlWriter class."); } public void FormatFile() { Console.WriteLine("Formatting file in XmlWriter class."); } } |
پیاده سازی صریح اینترفیس
همانطور که گفتیم، یک کلاس میتواند بیشتر از یک اینترفیس را پیاده سازی کند. غیرمعمول نیست که دو اینترفیس، یک متد هم نام داشته باشند، اما ما باید آنها را در کلاسمان پیاده سازی کنیم. از این جهت، ما نباید آن متدها را به شیوه قبل پیاده سازی کنیم، بلکه باید نام اینترفیس و سپس نام متد مورد نظر را به همراه پارامترهای آن بیان کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public interface Interface1 { void MethodExample(); } public interface Interface2 { void MethodExample(); } public class ExampleClass: Interface1, Interface2 { void Interface1.MethodExample() { Console.WriteLine(""); } void Interface2.MethodExample() { Console.WriteLine(""); } } |
همانطور که میبینیم، ما در پیاده سازی متد، از access modifier استفاده نمیکنیم.
نتیجه گیری
در مقاله بعدی، در مورد کلاسهای Abstract در #C صحبت میکنیم.