С++: рекомендуемый шаблон проектирования для подмножеств функциональности класса?

Я ищу совета по шаблону проектирования, который хорошо подходит для моих нужд:

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

Если бы я программировал на C, я бы, вероятно, решил реализовать это как структуру указателей на функции с NULL для любой отсутствующей функциональности. Но это не очень удовлетворяет C++.

Единственная идея, о которой я могу думать, - это класс, в котором каждая функция-член является «защищенной» и соответствующий набор указателей на «общедоступные» функции-члены. Конструкторы будут нести ответственность за инициализацию MFP либо значением NULL, либо адресом соответствующей функции-члена, в зависимости от того, какую функциональность предоставляет этот класс.

Но на самом деле это лишь немногим более похоже на C++, чем C-struct-of-function-pointers, о которых я впервые упоминал выше. И, возможно, это достаточно хорошо. Но мне интересно, может ли кто-нибудь предложить более удовлетворительный, проницательный шаблон проектирования для этого сценария.

Я открыт для любой общепринятой практики. СТЛ в порядке.

ОБНОВЛЕНИЕ. Причина, по которой подход с использованием МФУ не очень удовлетворителен, заключается в том, что мне придется внедрить заглушки для тех, которые неприменимы, из-за чисто- виртуальный базовый класс — хотя я бы установил для соответствующих МФУ значение NULL. Если поразмыслить, то это обновление оказалось полностью фальшивым. (Они не будут бесполезными заглушками, это будут полезные функции в тех случаях, когда NULL не используется для МФУ. Думаю, я устал.)

ОБНОВЛЕНИЕ 2. Аналогия: мой проект поддерживает аппаратные модули, которые можно заменять. Все они в основном относятся к одной и той же категории функциональных возможностей, но различаются по функциям и возможностям. При запуске я должен определить, какой аппаратный модуль фактически подключен, и создать экземпляр соответствующего класса. Но я не хочу, чтобы код, использующий этот класс, обладал специальными знаниями о каждом из классов; Я хочу, чтобы класс рекламировал, какие функции он предоставляет. (Иногда два аппаратных модуля идентифицируются как идентификаторы одного и того же типа, но при проверке возможностей один из них указывает функциональные возможности, которых нет у другого.)


person Ryan V. Bissell    schedule 11.12.2013    source источник
comment
(Я не уверен, что этот вопрос подходит для S.O., но я не знаю, где еще спросить.)   -  person Ryan V. Bissell    schedule 11.12.2013
comment
Скорее всего, этот дизайн начинается не с той ноги. Но были случаи, когда что-то подобное было уместно. Одним из решений является разложение функциональности на чистые интерфейсы, которые каждый конкретный класс может наследовать и реализовывать по своему усмотрению. Затем клиентский код может использовать, например. dynamic_cast чтобы узнать во время выполнения, какие функции есть. Или в коде шаблона он может использовать std::is_base_derived (который, насколько я помню, имеет свои аргументы в неправильном порядке, но в любом случае). Но подумайте, служит ли это усложнение — а оно и есть — полезной цели. Имеет ли это?   -  person Cheers and hth. - Alf    schedule 11.12.2013
comment
Я обновил свой вопрос аналогией, иллюстрируя причины, по которым я чувствую, что мне нужен такой подход. В частности, эта часть: иногда два аппаратных модуля идентифицируются как идентификатор одного и того же типа, но при проверке возможностей один из них указывает функциональные возможности, которых нет у другого.   -  person Ryan V. Bissell    schedule 11.12.2013
comment
Что касается меня, то структура указателей на функции звучит довольно C++'ish.   -  person c-smile    schedule 11.12.2013
comment
Звучит так, как будто здесь может быть полезен один из фабричных шаблонов проектирования.   -  person Ray Tayek    schedule 12.12.2013


Ответы (2)


Ваше требование к дизайну нарушает очень важный принцип ООП. Если бы класс или функция зависели от этого «надмножества» интерфейса, то компилятор никогда не смог бы обеспечить безопасность типов — вы, по сути, боролись бы с этим, и за что?

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

Обновление Теперь я прочитал ваше обновление и считаю, что вам нужно средство для продвижения объектов. Есть 2 вида продвижения:

  • Продвижение реализации:
    Здесь вы фактически заменяете реализацию объекта. Это можно сделать либо с помощью шаблона состояния, либо путем нового размещения объекта в объект с разные VTABLE.
  • Продвижение функциональных возможностей:
    здесь вы добавляете функциональные возможности, например, дополнительные функции. Вы можете сделать это, имея карту от имени аппаратного модуля до указателя его варианта. Когда вы переводите объект в другой класс, вы просто заменяете вариант указателя. Например, если ваше первое сопоставление было от «COM1» -> GenericSerial*, то теперь вы устанавливаете «COM1» -> SpecializedSerial*. Вы можете работать с библиотекой вариантов, такой как boost's.
person Yam Marcovic    schedule 11.12.2013

Почему бы не сделать что-то вроде этого:

interface All {
    void f1();
    void f2();
    void f3();
}
abstract class AllABC implements All {
    public void f1() {
        throw new RuntimeException("not implemented");
    }
    public void f2() {
        throw new RuntimeException("not implemented");
    }
    public void f3() {
        throw new RuntimeException("not implemented");
    }
}
class F12 extends AllABC {
    public void f1() {
        System.out.println("f1");
    }
    public void f2() {
        System.out.println("f2");
    }
}
class F13 extends AllABC {
    public void f1() {
        System.out.println("f1");
    }
    public void f3() {
        System.out.println("f3");
    }
}
public class So20511733 {
    public static void main(String[] arguments) {
        All f12=new F12();
        f12.f1();
        f12.f2();
        All f13=new F13();
        f13.f1();
        f13.f3();
    }
}
person Ray Tayek    schedule 24.02.2014
comment
Это разумно, и, по сути, это подход, который я использовал (2 месяца назад), несмотря на то, что принял ответ Яма. Первоначально я хотел решение, в котором клиентский код запрашивал какую-то конкретную поддержку, прежде чем пытаться это сделать, но в конечном итоге просто согласился на исключения, когда они не поддерживаются. - person Ryan V. Bissell; 24.02.2014