Вчера, просматривая очередное DnrTV! видео, я наткнулся на интересный пример реализации принципа Dependency Inversion. Я уже подробно описывал этот принцип вот здесь, но для того чтобы вы поняли, что меня заинтересовало, еще раз кратенько повторю основные моменты.
Итак. Для того чтобы убрать из класса Manager жесткую ссылку на класс Worker, мы создали интерфейс IWorker, где определили весь функционал, который требуется Manager’у от Worker’а. Затем мы заменили в Manager’е ссылку на конкретный класс Worker ссылкой на интерфейс IWorker, а в классе Worker реализовали этот интерфейс. Полученные зависимости выглядели как на рисунке ниже.
В этой реализации есть один нюанс. Любой класс, который мы захотим использовать вместо Worker должен реализовывать интерфейс IWorker.
Давайте рассмотрим другой пример. Допустим, у нас есть два класса: Radio и TV.
public class Radio { public int Volume { get; private set; } } public class TV { public int TvVolume { get; private set; } }
И мы хотим создать некий класс Remote, который бы управлял громкостью радио и телевизоров. Вооружившись знанием DIP, не составляет никакой проблемы определить вот такой интерфейс IDevice:
public interface IDevice { int Volume { get; set; } }
Реализовать этот интерфейс в классах Radio и TV. И затем определить класс Remote следующим образом:
public class Remote { private IDevice _device; public Remote(IDevice device) { _device = device; } public void IncreaseVolume() { _device.Volume +=1; } }
А что делать, если мы не хотим чтобы все классы, которыми можно управлять из Remote, обязательно реализовывали IDevice? Причины могут быть разные. Например, мы не хотим расширять API наших девайсов, добавляя реализацию IDevice. Или, как в случае с классом TV, мы не хотим изменять его API. Там уже есть свойство TvVolume, и нет смысла его переименовывать в Volume или добавлять свойство Volume рядом.
Мы пойдем другим путем. Вместо выделения общего для всех устройств интерфейса IDevice мы создадим интерфейс IRemote, в который включим всю функциональность нужную для управления громкостью устройства.
public interface IRemote { void IncreaseVolume(); }
Теперь класс Radio можно переписать так:
public class Radio { public int Volume { get; private set; } public IRemote GetRemote { return new Remote(this); } private ctass Remote : IRemote { private Radio _radio; public Remote(Radio radio) { _radio = radio; } public void IncreaseVolume() { _radio.Volume +=1; } } }
Переписав аналогичным образом класс TV, мы получим возможность написать нечто подобное:
Radio radio = new Radio(); Console.Write(radio.Volume); IRemote radioRemote = radio.GetRemote(); radioRemote.IncreaseVolume(); Console.Write(radio.Volume); TV tv = new TV(); Console.Write(tv.TvVolume); IRemote tvRemote = tv.GetRemote(); tvRemote.IncreaseVolume(); Console.Write(tv.TvVolume);
Не будем останавливаться и определим еще один интерфейс:
public interface IRemotable { IRemote GetRemote(); }
Реализовав его в классах Radio и TV, мы сможем переписать клиентский код вот так:
IRemotable radio = new Radio(); IRemote radioRemote = radio.GetRemote(); radioRemote.IncreaseVolume(); IRemotable tv = new TV(); IRemote tvRemote = tv.GetRemote(); tvRemote.IncreaseVolume();
Как видите, мы успешно развернули зависимости. Теперь клиентский код может управлять любым устройством реализующим IRemotable, вообще не зная конкретный тип этого устройства.
Ну как? Ничего не напоминает? Наши IRemote и IRemotable по смыслу – это один в один IEnumerator и IEnumerable из .Net BCL, про интенсивность использования которых, я думаю, не стоит даже говорить.
Вот такой вот нашелся интересный пример реализации DIP сразу же с отличным примером практического использования. Кому интересно, рекомендую посмотреть оригинал.
No comments:
Post a Comment