Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Связывание UI и функционала 
:(
    Опции темы
W4FhLF
Дата 13.7.2008, 14:05 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


found myself
****


Профиль
Группа: Участник Клуба
Сообщений: 2831
Регистрация: 2.12.2006

Репутация: 20
Всего: 121



Приветствую всех. 

Задумался над таким, интересным на мой взгляд, вопросом. Хотел бы услышать как его решают другие, наверняка есть какие-то более удобные и правильные универсальные схемы о которых я не знаю.

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


user posted image

У приложения есть GUI. Чтобы скрыть детали реализации между GUI и внутренней структурой существует некоторый промежуточный слой. Т.е. общая диаграмма выглядит так:

user posted image

Теперь собственно вопрос. Мне понадобилось отобразить данные "data" из класса "e" в окне подсистемы "GUI". Как лучше получить к ним доступ? Точнее вопрос даже стоит так: как определить ассоциативные связи IAbstract с остальными классами? 

Тут есть два пути(по-крайней мере я придумал только два):
1. Первый - это дать доступ IAbstract ко всем классам напрямую. Это будет выглядеть примерно так:

user posted image

2. IAbstract имеет доступ только к методам SuperClass, а последний через серию переходников получает доступ ко всем данным. Т.е. вот так:

user posted image

Что, на ваш взгляд, лучше? Есть ли другие варианты? 




Это сообщение отредактировал(а) W4FhLF - 13.7.2008, 14:15


--------------------
"Бог умер" © Ницше
"Ницше умер" © Бог
PM ICQ   Вверх
Mal Hack
Дата 13.7.2008, 14:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Мудрый...
****


Профиль
Группа: Участник Клуба
Сообщений: 9926
Регистрация: 15.2.2004

Репутация: 2
Всего: 261



Я, конечно, не большой гуру в сях и не так много сделал, но, что на сях, что на ПХП всегда делал так.
Есть гуй, который в зависимости от тех или иных ситуаций вызывает функции модуля, реализующего непосредственно решение задачи.
В Qt это у меня вроде бы неплохо получилось разнести, грубо говоря:
Class FormImpl {}; -> namespace modul {};
Модуль же уже обращается к еще как бы более низкому уровню аля интерфейи для работы с портами. В принципе в ряде случаев, гуй сам обращается к портам минуя модуль, например при подключении...
К примеру класс обработки исключений находится на той же ступеньки иерархии, что и модуль. 
Грубо говоря схема состоит из трех уровней: гуй, модуль, вспомогательные части модуля, которые могут использоваться и гуем, если уж так надо.
PM ICQ   Вверх
W4FhLF
Дата 13.7.2008, 14:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


found myself
****


Профиль
Группа: Участник Клуба
Сообщений: 2831
Регистрация: 2.12.2006

Репутация: 20
Всего: 121



Mal Hack, т.е. в общем случае ты описал способ №1? Этот способ кажется довольно неплохим. Если меняется любой из нижележащих классов, GUI об этом ничего не узнает, т.к. детали реализации скрывает IAbstract, но в тоже время насколько верно давать доступ IAbstract ко всем данным? Хотя, с другой стороны, способ номер №2 ничего не лучше, ибо чтобы получить некоторые данные из класса "e" класс IAbstract  в любом случае должен знать их тип и уметь работать с ними. К тому же в способе №2 добавляется множество переходников во все классы, т.е. такие конструкции:

Код

data d = SuperClass()->getA()->get_e()->get_data();


тоже не очень радуют smile 

В общем нужен разумный компромис, а может и совсем иное решение. 


--------------------
"Бог умер" © Ницше
"Ницше умер" © Бог
PM ICQ   Вверх
Torsten
Дата 13.7.2008, 14:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 174
Регистрация: 10.6.2008
Где: Pskov

Репутация: 3
Всего: 7



W4FhLF
Одно из ООП гласит - не делайте божественных классов, которые умеют все и вся. Каждый класс, метод должен решать только одну задачу.
Еще одно правило - всегда если возможно ослобляйте связи. В 1-ом случае у тебя их больше, чем во 2-ом, поэтому он хуже.
И конечно нужно идти от того, что за задачи выполняет интерфейс - можно ли его разделить на несколько независимых интерфейсов ?
У меня ни разу не было такого, чтобы интефрейс обьединял так много классов - 2, ну 3 максимум, но такое количество как у тебя на рисунках ни разу.

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

Цитата(W4FhLF @  13.7.2008,  14:35 Найти цитируемый пост)
data d = SuperClass()->getA()->get_e()->get_data();


А почему нельзя сделать метод у SuperClass который будет возращать get_data ?

Это сообщение отредактировал(а) Torsten - 13.7.2008, 14:50
--------------------
We have no begining, we have no end. We are infinite.
PM MAIL   Вверх
Mal Hack
Дата 13.7.2008, 14:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Мудрый...
****


Профиль
Группа: Участник Клуба
Сообщений: 9926
Регистрация: 15.2.2004

Репутация: 2
Всего: 261



Да, 1 способ.
Цитата(W4FhLF @  13.7.2008,  14:35 Найти цитируемый пост)
 К тому же в способе №2 добавляется множество переходников во все классы, т.е. такие конструкции:

Дело даже не в этом, а в оптимальности. Ну это что-то вроде:
Код

bool a = ns1::check();
....
bool ns1::check()
{return ns2::check();}
... и т.д.

Смысл устанавливать лишние программные связи - никакого. Плюс, первый подход обеспечивает некую гибкость приложения.
PM ICQ   Вверх
W4FhLF
Дата 13.7.2008, 15:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


found myself
****


Профиль
Группа: Участник Клуба
Сообщений: 2831
Регистрация: 2.12.2006

Репутация: 20
Всего: 121



Цитата(Torsten @  13.7.2008,  14:44 Найти цитируемый пост)
Еще одно правило - всегда если возможно ослобляйте связи. В 1-ом случае у тебя их больше, чем во 2-ом, поэтому он хуже.


С этой точки зрения хуже, да. Но вот, как уже Mal Hack упомянул, по части оптимизации как раз лучше. Если эти данные будут запрашиваться постоянно, то каждый раз придётся проходить цепочку вызовов от IAbstract до класса "e" в полном объёме, т.е. через все классы. Считаешь, это оптимально? Даже с т.з. зрения архитектуры, не говоря уже о реализации.

Цитата(Torsten @  13.7.2008,  14:44 Найти цитируемый пост)
И конечно нужно идти от того, что за задачи выполняет интерфейс - можно ли его разделить на несколько независимых интерфейсов ?


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


Цитата(Torsten @  13.7.2008,  14:44 Найти цитируемый пост)
А почему нельзя сделать метод у SuperClass который будет возращать get_data ?


Ну вот опять же мы увеличиваем кол-во связей. Поидее, классу SuperClass вообще пофиг до внутренней структуры класса "e" в котором хранятся нужные данные. Эти данные нужны только класу-владельцу и GUI для отображения. 



--------------------
"Бог умер" © Ницше
"Ницше умер" © Бог
PM ICQ   Вверх
Ulysses4j
Дата 13.7.2008, 15:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 304
Регистрация: 6.6.2007
Где: Ростов-на-Дону

Репутация: 4
Всего: 10



Цитата(W4FhLF @  13.7.2008,  15:05 Найти цитируемый пост)
С этой точки зрения хуже, да. Но вот, как уже Mal Hack упомянул, по части оптимизации как раз лучше. Если эти данные будут запрашиваться постоянно, то каждый раз придётся проходить цепочку вызовов от IAbstract до класса "e" в полном объёме, т.е. через все классы. Считаешь, это оптимально? Даже с т.з. зрения архитектуры, не говоря уже о реализации.

Геттеры скорее всего будут встраиваться и никакого ущерба для быстродействия не будет, а если и будет, то вы все равно занимаетесь преждевременной оптимизацей (Саттер, Александреску, “Стандарты кодирования на C++”, одно из первых правил). А вот на уровне архитектуры такая цепочка это плохо, нарушает закон Деметры (“Only talk to your immediate friends”). Хотя в реальной жизни используется часто ;)


--------------------
Communication is critical to the job of a programmer.
C. Jazdzewski. Fatherly Advice To New Programmers
PM MAIL WWW   Вверх
W4FhLF
Дата 13.7.2008, 16:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


found myself
****


Профиль
Группа: Участник Клуба
Сообщений: 2831
Регистрация: 2.12.2006

Репутация: 20
Всего: 121



Ulysses4j, за ссылки спасибо. Но я всё-таки не понял, вы за какой подход?smile


--------------------
"Бог умер" © Ницше
"Ницше умер" © Бог
PM ICQ   Вверх
Ulysses4j
Дата 13.7.2008, 17:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 304
Регистрация: 6.6.2007
Где: Ростов-на-Дону

Репутация: 4
Всего: 10



Я не знаю, это слишком общая постановка вопроса, но то, о чем вы говорите, похоже на Façade pattern. Соответственно, второй подход.


--------------------
Communication is critical to the job of a programmer.
C. Jazdzewski. Fatherly Advice To New Programmers
PM MAIL WWW   Вверх
The_Thing
Дата 14.7.2008, 10:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 6
Регистрация: 24.9.2007

Репутация: 1
Всего: 1



Что бы таких ситуаций не было делается это так:
Делай интерфейс на каждый из классов, не только на "супер класс", но  и на A, B, C, D, e, f, g (IA, IB, IC, ID, Ie, If, Ig соответственно).
После этого, реализуй следующую связь  "IСупер класс" предоставляет доступ к интерфейсу IA, IB, IC, ID, пусть даже если они не предоставляют никаких данных (вся реализация скрыта, пока скрыта! ), если тебе понадобится открыть данные ты всегда сможешь добавить в интерфейс нужные данные. Интерфейс IA, IB должен предоставлять доступ к интерфейсу к Ie и If, а IC и ID к Ig.
Доступ к Ie осуществляется таким образом: Получаешь интерфейс ISuperClass, от него интерфейс IA, от него Ie  у него get_data.

Таким образом каждый класс отвечает за свою задачу, и если тебе прийдется что-то поменять, ты не затрагиваешь всю структуру приложения.. так как каждый работает только с наследуемым. Я делаю так, очень удобно, проблем не возникает.
PM MAIL   Вверх
mes
Дата 16.7.2008, 11:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


любитель
****


Профиль
Группа: Участник Клуба
Сообщений: 7954
Регистрация: 14.1.2006

Репутация: 144
Всего: 250



Цитата(W4FhLF @  13.7.2008,  14:05 Найти цитируемый пост)

Что, на ваш взгляд, лучше? Есть ли другие варианты? 


3й вариант:  совместить оба подхода:

1.  определить структуры данных
2.  раделить ядро  на компоненты
     каждый компонент состоит из одного или множества объектов контролирующих данные
     к внутренним объектам нет доступа извне 
     компонент предоставляет доступ к любым данным своих объектов 
3. Разделить Гуи на компоненты 
    несколько виджетов реализующих общую смысловую функциональность представлены одним компонентом (точнее  делегируют функции одного компонента)
4. реализовать логику взаимодействия между гуи- и ядро- компонентами 


    





--------------------
PM MAIL WWW   Вверх
Lazin
Дата 16.7.2008, 12:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3820
Регистрация: 11.12.2006
Где: paranoid oil empi re

Репутация: 41
Всего: 154



Цитата(The_Thing @  14.7.2008,  10:21 Найти цитируемый пост)
Делай интерфейс на каждый из классов, не только на "супер класс", но  и на A, B, C, D, e, f, g (IA, IB, IC, ID, Ie, If, Ig соответственно).

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

Допустим у нас есть некоторый объект, который что-то реализует, и имеет набор свойств и функций - сервис. В общем случае нужно ввести дополнительную сущность(объект посредник), через которую клиент(GUI) сможет влиять на объект сервис...

Например в интерфейс базового класса можно добавить вирт. функцию, которая бы возвращала такой proxy(посредник) объект. В качестве посредника, может выступать, к примеру xml документ, содержащий описание состояния (точнее той его части, которую может изменять клиент) и мета-информацию (типы переменных, различные описания), получив proxy объект, клиент может создать форму с виджетами для его редактирования динамически, когда пользователь изменит свойство, клиент(GUI класс) может отправить измененный xml документ( а еще лучше - его изменившуюся часть) сервису, который обработает его и изменит свое состояние... то-есть, в итоге, произойдет вызов метода, но не напрямую.
Конечно xml это не очень эффективно, но его я привел только в качестве примера (который может вполне может быть использован). Можно придумать какую угодно реализацию proxy объекта, который, в общем случае должен содержать мета-информацию (описывать тип каждого свойства, название для пользователя и т.д.) и состояние (значение свойства).
Можно придумать, к примеру структуру содержащую список указателей на методы объекта...
Код


template<
    typename I, // класс - сервис
    X (I::*Get)(), // метод класса сервиса, для получения значения
    void (I::*Set)(const X&),// метод для изменения свойства
    typename X // тип свойства
    >
class proxy_t
{
        I& srv_;
        std::string name_;
    public:
        proxy_t(I& serv, const std::string& str) : srv_(srv), name_(str)
        {
        }
        proxy_t operator = (const X& value)
        {
            (srv_->*Set)(value);
        }
        operator X ()
        {
            return (srv_->*Get)();
        }
        std::string get_name()
        {
            return name_;
        }
};

template<
    template<
        class I, 
        X (I::*Get)(),
        void (I::*Set)(const X&),
        typename X
    >   class Property
>
void show_gui(CForm* form, Property& prop)
{
    //добавляем к форме виджет для свойства произвольного типа
}



PM MAIL Skype GTalk   Вверх
Lazin
Дата 16.7.2008, 13:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3820
Регистрация: 11.12.2006
Где: paranoid oil empi re

Репутация: 41
Всего: 154



Написал пример того, что я имел ввиду, только вместо создания диалога, класс Client использует для ввода значений консоль...
Код

#include <exception>
#include <iostream>
#include <string>

//объект посредник
template<
    typename I, // класс - сервис
    typename X, // тип свойства
    X (I::*Get)(), // метод класса сервиса, для получения значения
    void (I::*Set)(const X&)// метод для изменения свойства
>
class proxy_t
{
        I& srv_;//значение параметра
        std::string name_;//имя параметра
    public:
        proxy_t(I& srv, const std::string& str) : srv_(srv), name_(str)
        {
        }
        proxy_t& operator = (const X& value)
        {
            (srv_.*Set)(value);
            return *this;
        }
        operator X ()
        {
            return (srv_.*Get)();
        }
        std::string get_name()
        {
            return name_;
        }
};

//враппер для значения с диапазоном допустимых значений
template<class T>
struct value_wrapper
{
    T& value_;
    std::pair<T,T> range_;
    value_wrapper(T& v, T min, T max) : value_(v), range_(min, max) {}
    //value_wrapper
    value_wrapper& operator = (const value_wrapper& v) {value_ = v.value_; range_ = v.range_;}
    value_wrapper& operator = (const double& v) {value_ = v;}
    operator T() const {return value_;}
    bool check_range(T& v)
    {
        if (v < range_.first)
            return false;
        if (v > range_.second)
            return false;
        return true;
    }
};

//класс отвечает за диалог с пользователем, ничего не знает о том объекте, чьи свойства он показывает
class Client//GUI
{
public:
    //метод получает объект посредник и отображает диалог изменения свойства
    template<
        class Proxy
    >
    void apply(Proxy& p);

    //специализация для посредника любого типа
    template<
            class I, 
            class X,
            X (I::*Get)(),
            void (I::*Set)(const X&)
    >
    void apply (proxy_t<I, X, Get, Set>& p)
    {
        //значение произвольного типа
        X tmp = static_cast<X>(p);
        std::cout << p.get_name() << " = " << tmp << std::endl;
        std::cout << "let " << p.get_name() << " = " << std::ends;
        std::cin >> tmp;
        p = tmp;
    }

    //специализация для строковых значений
    template<
            class I, 
            std::string (I::*Get)(),
            void (I::*Set)(const std::string&)
    >
    void apply (proxy_t<I, std::string, Get, Set>& p)
    {
        //значение типа std::string
        std::cout << "str: " << p.get_name() << " = " << static_cast<std::string>(p) << std::endl;
        std::cout << "let " << p.get_name() << " = " << std::ends;
        std::cin >> static_cast<std::string>(p);
    }


    //специализация для числовых значений с проверкой диапазона
    template<
            class I,
            class X,
            value_wrapper<X> (I::*Get)(),
            void (I::*Set)(const value_wrapper<X>&)
    >
    void apply (proxy_t<I, value_wrapper<X>, Get, Set>& p)
    {
        std::cout << p.get_name() << " = " << static_cast< value_wrapper<X> >(p) << std::endl;
        std::cout << "let " << p.get_name() << " = " << std::ends;
        X tmp;
        std::cin >> tmp;
        if (! static_cast< value_wrapper<X> >(p).check_range(tmp) )
            throw std::runtime_error("input error");
        p = value_wrapper<X>(tmp,tmp,tmp);
    }

};



//класс - сервис, реализует какую-то бизнес логику, не работает с GUI сам
class Service
{
    int int_value;
    double dbl_value;
    std::string str_value;
public:
    Service(int i, std::string s, double d) : int_value(i), dbl_value(d), str_value(s)
    {
    }
    //метод создает proxy объекты для своих полей и передает их клиенту
    void apply(Client& client)
    {
        proxy_t<Service, int, &Service::get_int_value, &Service::set_int_value> int_proxy(*this, "int_value");
        proxy_t<Service, std::string, &Service::get_str_value, &Service::set_str_value> str_proxy(*this, "str_value");
        proxy_t<Service, value_wrapper<double>,    &Service::get_dbl_value, &Service::set_dbl_value> dbl_proxy(*this, "dbl_value");
        client.apply(int_proxy);
        client.apply(str_proxy);
        client.apply(dbl_proxy);
    }

    //методы для доступа к полям объекта
    void set_int_value(const int& v)
    {
        int_value = v;
    }
    void set_dbl_value(const value_wrapper<double>& v)
    {
        dbl_value = (double)v;
    }
    void set_str_value(const std::string& s)
    {
        str_value = s;
    }
    int get_int_value()
    {
        return int_value;
    }
    value_wrapper<double> get_dbl_value()
    {
        return value_wrapper<double>(dbl_value, 0, 10);
    }
    std::string get_str_value()
    {
        return str_value;
    }
};



int main()
{
    Service srv(25, "Foo", 3.14159);
    Client gui;
    srv.apply(gui);
    std::cout << "result: " << srv.get_int_value() << ", " << srv.get_str_value() << ", " << (double)srv.get_dbl_value() << std::endl;
    system("pause");
}

PM MAIL Skype GTalk   Вверх
W4FhLF
Дата 16.7.2008, 14:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


found myself
****


Профиль
Группа: Участник Клуба
Сообщений: 2831
Регистрация: 2.12.2006

Репутация: 20
Всего: 121



mes, мне кажется ты представил разновидность варианта №1. В данном случае IAbstract - компонент для всех составных объектов классов SuperClass, A,B,C,D... Единственное, если базовых классов будет несколько, каждый из которых решает свою собственную задачу, то и IAbstract должно быть несколько, каждый из которых реализует интерфейс взаимодействия с внутренними данными составных классов. 

Lazin, ты взорвал мой мозг smile Спасибо, что потратил время на код. На первый взгляд решение действительно неплохое, но сегодня я уже порядком подустал, поэтому отложим его разбор на завтра.


--------------------
"Бог умер" © Ницше
"Ницше умер" © Бог
PM ICQ   Вверх
Partizan
Дата 16.7.2008, 17:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Let's do some .NET
****


Профиль
Группа: Модератор
Сообщений: 2828
Регистрация: 19.12.2005
Где: Санкт-Петербург

Репутация: 4
Всего: 67



W4FhLF, задача слишком абстрактна для того чтобы выбирать какой из вариантов лучше. Тем не менее я склоняюсь ко 2 варианту...


А такие конструкцуии явно указывают на ошибки с проектировании...
Код

data d = SuperClass()->getA()->get_e()->get_data();


В общем надо плясать от конкретной задачи и уже там решать что лучше.


--------------------
СУВ,
       Partizan.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1220 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.