Почему программист хочет отделить реализацию от интерфейса?

18

Шаблон проектирования моста отделяет реализацию от интерфейса программы.

Почему это выгодно?

Дэвид Фокс
источник
Для предотвращения негерметичной абстракции
SK-logic
1
Кажется, это уже все поняли, но для новых зрителей Bridge Design - это не реализация и интерфейс «программы», а части, возможно, даже небольшие части программы. Целая программа будет представлять собой набор интерфейсов и реализаций (причем каждый интерфейс имеет одну или несколько реализаций). Возможно, первое предложение должно быть следующим: «Шаблон проектирования моста отделяет интерфейсы от их реализаций по всему исходному коду».
RalphChapin

Ответы:

35

Это позволяет изменять реализацию независимо от интерфейса. Это помогает справиться с изменяющимися требованиями.

Классический пример - замена реализации хранилища в интерфейсе чем-то большим, лучшим, быстрым, меньшим или другим, без необходимости изменения остальной части системы.

Даниэль Питтман
источник
23

В дополнение к ответу Даниэля, отделение интерфейса от реализации с помощью таких понятий, как полиморфизм, позволяет создавать несколько реализаций одного и того же интерфейса, которые по-разному выполняют сходные вещи.

Например, многие языки имеют понятие потока где-то в стандартной библиотеке. Поток - это то, что содержит данные для последовательного доступа. Он имеет две основные операции: чтение (загрузка следующего X-го числа байтов из потока) и запись (добавление X-го числа байтов данных в поток), а иногда и третье, поиск (сброс «текущей позиции» потока на новое место).

Это достаточно простая концепция, но подумайте обо всех вещах, которые вы могли бы с ней сделать. Наиболее очевидным является взаимодействие с файлами на диске. Файловый поток позволит вам читать данные из файла или записывать в него. Но что, если вы хотите вместо этого отправлять данные по сетевому соединению?

Если вы полагались на реализации напрямую, вам пришлось бы выписать две совершенно разные подпрограммы, чтобы сохранить одни и те же данные в файл или отправить их по сети. Но если у вас есть потоковый интерфейс, вы можете создать две разные его реализации ( FileStreamи NetworkStream), которые инкапсулируют конкретные детали отправки данных в нужное место, а затем вам нужно всего лишь написать код, который имеет дело с сохранением файла один раз. , Внезапно ваши SaveToFileи SendOverNetworkподпрограммы становятся намного проще: они просто устанавливают поток соответствующего типа и передают его SaveDataподпрограмме, которая принимает интерфейс потока - ему не нужно заботиться о том, какой тип, если он может выполнять Операция записи - и сохраняет данные в поток.

Это также означает, что если ваш формат данных изменяется, вам не нужно менять его в разных местах. Если вы централизуете свой код сохранения данных в одной подпрограмме, которая принимает поток, то это единственное место, где его нужно обновить, поэтому вы не можете случайно внести ошибку, изменив только одно место, когда вам нужно изменить оба. Таким образом, отделение интерфейсов от реализаций и использование полиморфизма делает код более простым для чтения и понимания и с меньшей вероятностью возникновения ошибок.

Мейсон Уилер
источник
1
Это одна из сильных сторон ООП. Я просто обожаю это ...
Раду Мурзеа
1
Как эта другая форма использует простые интерфейсы?
Vainolo
@ Vainolo: это помогает с повторным использованием кода. Даже если у вас есть несколько типов потоков, они все будут делать то же самое, что и другие. Если вы начинаете с IStreamинтерфейса, вам придется заново изобретать весь набор функций для каждого потока. Но если вы начнете с базового абстрактного класса потока, он может содержать все общее состояние и функциональность, а затем позволить потомкам реализовать различные функции.
Мейсон Уилер
2
@RaduMurzea это не относится к ООП. Классы типов позволяют вам делать то же самое совершенно не ООП.
Уэс
@ Точно, просто хотел сказать то же самое, а потом я прочитал твой комментарий.
Джегедус
4

У вас действительно есть два совершенно разных вопроса, хотя они связаны между собой.

Более общий вопрос в заголовке: зачем отделять интерфейс от реализации в целом. Второй вопрос, почему шаблон моста полезен. Они связаны, потому что паттерн моста - это особый способ отделения интерфейса от реализации, что также имеет некоторые специфические другие последствия.

Общий вопрос - это то, что каждый программист должен понять. Это то, что предотвращает распространение изменений в программе повсеместно. Я не могу представить, чтобы люди могли программировать без этого.

Когда вы пишете простой оператор сложения на языке программирования, это уже абстракция (даже если он не использует перегрузку операторов для добавления матриц или что-то в этом роде), которая проходит через довольно много другого кода, прежде чем он, наконец, будет выполнен на схеме в вашем компьютере. Если бы не было отделения интерфейса (скажем, «3 + 5») от реализации (связка машинного кода), то вам пришлось бы менять код каждый раз, когда менялась реализация (как вы хотели запустить на новый процессор).

Даже в простом приложении CRUD каждая сигнатура метода в широком смысле является интерфейсом для его реализации.

Все эти виды абстракций имеют одну и ту же основную цель - сделать так, чтобы вызывающий код выражал свое намерение самым абстрактным из возможных способов, что дает разработчику столько информации, сколько необходимо. Это обеспечивает минимально возможную связь между ними и ограничивает волновой эффект, когда необходимо максимально изменить код.

Это звучит просто, но на практике это становится сложным.

Шаблон моста - это особый способ разделения определенных битов реализации на интерфейсы. Диаграмма классов шаблона более информативна, чем описание. Это больше похоже на способ иметь подключаемые модули, чем на мост, но они назвали его мостом, потому что он часто используется там, где модули были созданы до интерфейса. Таким образом, создание общего интерфейса для аналогичных существующих реализаций как бы «наводит мост» на разницу и позволяет вашему коду работать с любой из реализаций.

Допустим, вы хотите написать дополнение к текстовому процессору, но хотите, чтобы оно работало на нескольких текстовых процессорах. Вы можете создать интерфейс, который абстрагирует необходимую вам функциональность на основе текстового процессора (и которая должна быть реализована каждым текстовым процессором, поскольку вы не можете их изменить), и одну реализацию этого интерфейса для каждого текстового процессора, который вы хотите поддерживать. Тогда ваше приложение может вызывать этот интерфейс и не беспокоиться о деталях каждого текстового процессора.

Это на самом деле немного более подробно, чем это, потому что каждый класс может действительно быть иерархией классов (так что, это может быть не просто абстрактный текстовый процессор, но абстрактный документ, абстрактный TextSelection и т. Д., С конкретными реализациями для каждого), но это та же идея.

Это немного похоже на фасад, за исключением того, что в этом случае уровень абстракции ориентирован на предоставление одного и того же интерфейса нескольким базовым системам.

Это связано с инверсией управления, поскольку конкретный реализатор будет передан методам или конструкторам и определит фактическую вызванную реализацию.

PSR
источник