Параллельное и распределенное программирование на С++ (Хьюз, Хьюз) - страница 281

«Полуширокие» интерфейсы

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

Существует по крайней мере два подхода к разработке общего интерфейса для семейства, или коллекции классов. Объектно-ориентированный подход начинается с общего и переходит к частностям посредством наследования. Другими словами, возьмем минимальный набор характеристик и атрибутов, которыми должен обладать каждый член рассматриваемого сехмейства классов, а затем посредством наследования будем конкретизировать характеристики для каждого класса. При таком подходе по мере «спуска» по иерархии классов интерфейс становится все более «узким». Второй подход часто используется в коллекциях шаблонов. Шаблонные методы начинаются c конкретного и переходят к более общему посредством «широких» интерфейсов. «Широкий» интерфейс включает обобщение всех характеристик и атрибутов (см. книгу Страуструпа « Язык программирования С++» , 1997). Если бы нам пришлось применить к библиотекам средств параллелизма «узкий» и «широкий» интерфейсы, то согласно метолу «узкого интерфейса» мы бы взяли от каждой библиотеки общие, или пересекающиеся, части (т.е. пересечение), обобщили их и поместили в базовый класс. И, наоборот, реализуя метод «широкого интерфейса», нужно было бы поместить в базовый класс все функциональные части каждой библиотеки (т.е. объединение), предварительно обобщив их. В результате пересечения мы получили бы меньший по объему да и менее полезный класс. А результат объединения, скорей всего, поразил бы каждого своей громоздкостью. Решение, которое интересует нас в данном случае, находится где-то посередине, т.е. нам нужны «полуширокие» интерфейсы. Начнем же мы с метода «узкого» интерфейса и обобщим его настолько, насколько это можно сделать в пределах иерархии одного класса. Затем используем этот «узкий» интерфейс в качестве основы для коллекции классов, которые связаны не наследованием, а функциями. «Узкий» интерфейс должен действовать в качестве стратегии сдерживания «ширины», до которой может разбухнуть «полуширокий» интерфейс. Другими словами, нам не нужно объединять буквально все характеристики и атрибуты; мы хотим получить объединение только тех частей, которые логически связаны с нашим «узким» интерфейсом. Проиллюстрируем эту мысль иа примере простого проекта интерфейсных классов для POSIX-семафора, Pthread-мьютекса и Pthread-переменной блокировки.