Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Ce que je ferais, c'est un map statique des modules,
chargement dynamiquement par le constructeur d'un objet
statique dans chaque module. Ton code
Application::Database() serait alors quelque chose du genre
:
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
La « configuration » se ferait lors de l'édition de liens,
où tu incorporeras ou non l'objet static d'initialisation de
tel ou tel module.
Alternativement, tu pourrais faire à peu près pareil avec
des objets dynamiques. Quand la fonction (par exemple
Application::Database) ne trouve pas l'entrée dans le map,
il essaie de charger l'objet dynamique correspondant (qui
s'insérera dans le map) avant de retourner NULL. Dans ce
cas-ci, la « configuration » ne se fait que lors du
packaging : tu inclus plus ou moins d'objets dynamiques dans
le package.
Dans ce cas-là, la fonction devient :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
if ( module == ourMap.end() ) {
tryToLoadModule( databaseId ) ;
module = ourMap.find( databaseId ) ;
}
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
C'est un peu vers quoi je m'oriente. J'explique un peu ça dans
ma réponse à Patrick:
"Je pars sur une idée d'enregistrement dynamique auprès
de l'Application, et de référencement via un nom sous forme de
chaine de caractères, et le dynamic_cast qui va bien.
class ModuleBase
{
public:
virtual std::string GetName() const = 0;
template<typename T>
T * Downcast()
{
return dynamic_cast<T*>( this );
}
};
class Application
{
public:
void RegisterModule( ModuleBase *M );
M * GetModule( std::string Name );
};
// *****
class ModuleX
{
void Toto()
{
// on utilise la base de données
DataBaseMgr *db = App()->GetModule( "DataBase"
)->Downcast<DataBaseMgr>();
}
}"Je compte migrer vers quelque chose comme celà: - pour
chaque module, une classe ModuleUser donne le droit
d'utiliser le module. Il faut dériver de cette classe pour
pouvoir utiliser la fonction membre donnant accès à
l'instance du module.
class DatabaseUser
{
protected:
DatabaseMgr* Database();
};
class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};
C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Ça me paraît un peu lourd, mais ça pourrait marcher aussi.
N'empèche que je chercherais à différer la décision au plus
tard possible, à l'édition de liens ou au packaging, et non
dans le code.
Oui, c'est ce qui me freine. D'un point de vue conceptuel, je
trouve ça pas mal de se déclarer utilisateur de tel ou tel
module. Je suis en train de voir si c'est pas trop lourd de
devoir hériter à chaque fois.
Dans ton exemple ci-dessus:static_cast< DatabaseMgr* >( module->second )
tu sembles trouver acceptable mon idée de classe Module de
base qu'on downcast selon le besoin. Ca rend ma classe
centrale Application indépendante de tout module. Car dans ton
exemple, il y a un problème:DatabaseMgr*
Application::Database()
Application::Database() ne peut pas être implémenté dans
Application, car:
- le module Database (comme tous les modules) utilise la classe
Application
- la classe Application utilise le module Database (et les autres)
donc le serpent se mord la queue. J'ai donc une classe
abstraite Application qui ne fait que des forward declaration:
class DatabaseMgr;
class Application
{
public:
virtual DatabaseMgr* Database() = 0;
};
et une classe ApplicationCore dans le module principal qui
implémente Application::Database() (et le reste).
C'est à peu près mon design actuel, mais ceci me gêne: si le
module Database n'est pas inclus, il n'est donc pas
utilisable, d'où la question:
- pourquoi proposer la fonction membre Database() ?
Car je ne veux pas renvoyer NULL. Je préfère le principe "si
c'est là tu peux l'utiliser" qu'au principe "ok c'est là mais
attention aucune garantie". Ca simplifie l'utilisation.
Je ne compte pas charger les modules à la demande, mais au
lancement de l'application initialiser tous les modules (pour
une base de données ça peut être long), et alors si tout est
ok, les modules sont présents et utilisables directement, sans
test de NULL.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Ce que je ferais, c'est un map statique des modules,
chargement dynamiquement par le constructeur d'un objet
statique dans chaque module. Ton code
Application::Database() serait alors quelque chose du genre
:
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
La « configuration » se ferait lors de l'édition de liens,
où tu incorporeras ou non l'objet static d'initialisation de
tel ou tel module.
Alternativement, tu pourrais faire à peu près pareil avec
des objets dynamiques. Quand la fonction (par exemple
Application::Database) ne trouve pas l'entrée dans le map,
il essaie de charger l'objet dynamique correspondant (qui
s'insérera dans le map) avant de retourner NULL. Dans ce
cas-ci, la « configuration » ne se fait que lors du
packaging : tu inclus plus ou moins d'objets dynamiques dans
le package.
Dans ce cas-là, la fonction devient :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
if ( module == ourMap.end() ) {
tryToLoadModule( databaseId ) ;
module = ourMap.find( databaseId ) ;
}
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
C'est un peu vers quoi je m'oriente. J'explique un peu ça dans
ma réponse à Patrick:
"Je pars sur une idée d'enregistrement dynamique auprès
de l'Application, et de référencement via un nom sous forme de
chaine de caractères, et le dynamic_cast qui va bien.
class ModuleBase
{
public:
virtual std::string GetName() const = 0;
template<typename T>
T * Downcast()
{
return dynamic_cast<T*>( this );
}
};
class Application
{
public:
void RegisterModule( ModuleBase *M );
M * GetModule( std::string Name );
};
// *****
class ModuleX
{
void Toto()
{
// on utilise la base de données
DataBaseMgr *db = App()->GetModule( "DataBase"
)->Downcast<DataBaseMgr>();
}
}"
Je compte migrer vers quelque chose comme celà: - pour
chaque module, une classe ModuleUser donne le droit
d'utiliser le module. Il faut dériver de cette classe pour
pouvoir utiliser la fonction membre donnant accès à
l'instance du module.
class DatabaseUser
{
protected:
DatabaseMgr* Database();
};
class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};
C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Ça me paraît un peu lourd, mais ça pourrait marcher aussi.
N'empèche que je chercherais à différer la décision au plus
tard possible, à l'édition de liens ou au packaging, et non
dans le code.
Oui, c'est ce qui me freine. D'un point de vue conceptuel, je
trouve ça pas mal de se déclarer utilisateur de tel ou tel
module. Je suis en train de voir si c'est pas trop lourd de
devoir hériter à chaque fois.
Dans ton exemple ci-dessus:
static_cast< DatabaseMgr* >( module->second )
tu sembles trouver acceptable mon idée de classe Module de
base qu'on downcast selon le besoin. Ca rend ma classe
centrale Application indépendante de tout module. Car dans ton
exemple, il y a un problème:
DatabaseMgr*
Application::Database()
Application::Database() ne peut pas être implémenté dans
Application, car:
- le module Database (comme tous les modules) utilise la classe
Application
- la classe Application utilise le module Database (et les autres)
donc le serpent se mord la queue. J'ai donc une classe
abstraite Application qui ne fait que des forward declaration:
class DatabaseMgr;
class Application
{
public:
virtual DatabaseMgr* Database() = 0;
};
et une classe ApplicationCore dans le module principal qui
implémente Application::Database() (et le reste).
C'est à peu près mon design actuel, mais ceci me gêne: si le
module Database n'est pas inclus, il n'est donc pas
utilisable, d'où la question:
- pourquoi proposer la fonction membre Database() ?
Car je ne veux pas renvoyer NULL. Je préfère le principe "si
c'est là tu peux l'utiliser" qu'au principe "ok c'est là mais
attention aucune garantie". Ca simplifie l'utilisation.
Je ne compte pas charger les modules à la demande, mais au
lancement de l'application initialiser tous les modules (pour
une base de données ça peut être long), et alors si tout est
ok, les modules sont présents et utilisables directement, sans
test de NULL.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Ce que je ferais, c'est un map statique des modules,
chargement dynamiquement par le constructeur d'un objet
statique dans chaque module. Ton code
Application::Database() serait alors quelque chose du genre
:
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
La « configuration » se ferait lors de l'édition de liens,
où tu incorporeras ou non l'objet static d'initialisation de
tel ou tel module.
Alternativement, tu pourrais faire à peu près pareil avec
des objets dynamiques. Quand la fonction (par exemple
Application::Database) ne trouve pas l'entrée dans le map,
il essaie de charger l'objet dynamique correspondant (qui
s'insérera dans le map) avant de retourner NULL. Dans ce
cas-ci, la « configuration » ne se fait que lors du
packaging : tu inclus plus ou moins d'objets dynamiques dans
le package.
Dans ce cas-là, la fonction devient :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
if ( module == ourMap.end() ) {
tryToLoadModule( databaseId ) ;
module = ourMap.find( databaseId ) ;
}
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
C'est un peu vers quoi je m'oriente. J'explique un peu ça dans
ma réponse à Patrick:
"Je pars sur une idée d'enregistrement dynamique auprès
de l'Application, et de référencement via un nom sous forme de
chaine de caractères, et le dynamic_cast qui va bien.
class ModuleBase
{
public:
virtual std::string GetName() const = 0;
template<typename T>
T * Downcast()
{
return dynamic_cast<T*>( this );
}
};
class Application
{
public:
void RegisterModule( ModuleBase *M );
M * GetModule( std::string Name );
};
// *****
class ModuleX
{
void Toto()
{
// on utilise la base de données
DataBaseMgr *db = App()->GetModule( "DataBase"
)->Downcast<DataBaseMgr>();
}
}"Je compte migrer vers quelque chose comme celà: - pour
chaque module, une classe ModuleUser donne le droit
d'utiliser le module. Il faut dériver de cette classe pour
pouvoir utiliser la fonction membre donnant accès à
l'instance du module.
class DatabaseUser
{
protected:
DatabaseMgr* Database();
};
class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};
C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Ça me paraît un peu lourd, mais ça pourrait marcher aussi.
N'empèche que je chercherais à différer la décision au plus
tard possible, à l'édition de liens ou au packaging, et non
dans le code.
Oui, c'est ce qui me freine. D'un point de vue conceptuel, je
trouve ça pas mal de se déclarer utilisateur de tel ou tel
module. Je suis en train de voir si c'est pas trop lourd de
devoir hériter à chaque fois.
Dans ton exemple ci-dessus:static_cast< DatabaseMgr* >( module->second )
tu sembles trouver acceptable mon idée de classe Module de
base qu'on downcast selon le besoin. Ca rend ma classe
centrale Application indépendante de tout module. Car dans ton
exemple, il y a un problème:DatabaseMgr*
Application::Database()
Application::Database() ne peut pas être implémenté dans
Application, car:
- le module Database (comme tous les modules) utilise la classe
Application
- la classe Application utilise le module Database (et les autres)
donc le serpent se mord la queue. J'ai donc une classe
abstraite Application qui ne fait que des forward declaration:
class DatabaseMgr;
class Application
{
public:
virtual DatabaseMgr* Database() = 0;
};
et une classe ApplicationCore dans le module principal qui
implémente Application::Database() (et le reste).
C'est à peu près mon design actuel, mais ceci me gêne: si le
module Database n'est pas inclus, il n'est donc pas
utilisable, d'où la question:
- pourquoi proposer la fonction membre Database() ?
Car je ne veux pas renvoyer NULL. Je préfère le principe "si
c'est là tu peux l'utiliser" qu'au principe "ok c'est là mais
attention aucune garantie". Ca simplifie l'utilisation.
Je ne compte pas charger les modules à la demande, mais au
lancement de l'application initialiser tous les modules (pour
une base de données ça peut être long), et alors si tout est
ok, les modules sont présents et utilisables directement, sans
test de NULL.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Mais justement, il faut compiler tout, et seulement linker une
partie.
Dans ton exemple ci-dessus:static_cast< DatabaseMgr* >( module->second )tu sembles trouver acceptable mon idée de classe Module de
base qu'on downcast selon le besoin. Ca rend ma classe
centrale Application indépendante de tout module. Car dans ton
exemple, il y a un problème:DatabaseMgr*
Application::Database()Application::Database() ne peut pas être implémenté dans
Application, car:
- le module Database (comme tous les modules) utilise la classe
Application
Ça, ce n'est pas bien. Il faudrait séparer les concernes : une
classe qui donne accès aux modules, et une autre classe (ou une
module) qui est toujours présente, et qui contient tout ce qui
est commun et qui sert dans les modules.
C'est à peu près mon design actuel, mais ceci me gêne: si le
module Database n'est pas inclus, il n'est donc pas
utilisable, d'où la question:
- pourquoi proposer la fonction membre Database() ?Car je ne veux pas renvoyer NULL. Je préfère le principe "si
c'est là tu peux l'utiliser" qu'au principe "ok c'est là mais
attention aucune garantie". Ca simplifie l'utilisation.
S'il ne s'agit que de la classe Application... on pourrait assez
facilement le générer automatiquement à partir des paramètres
externes.
Je ne compte pas charger les modules à la demande, mais au
lancement de l'application initialiser tous les modules (pour
une base de données ça peut être long), et alors si tout est
ok, les modules sont présents et utilisables directement, sans
test de NULL.
Je ne connais pas assez l'application pour donner les détails,
mais je soupçonne que ton gestion des NULL serait toujours ce
qu'il y a de plus simple. Les #ifdef à droit et à gauche,
surtout dans le code des fonctions (ou la définition des
classes) mènent très vite à quelque chose d'immaintenable.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Mais justement, il faut compiler tout, et seulement linker une
partie.
Dans ton exemple ci-dessus:
static_cast< DatabaseMgr* >( module->second )
tu sembles trouver acceptable mon idée de classe Module de
base qu'on downcast selon le besoin. Ca rend ma classe
centrale Application indépendante de tout module. Car dans ton
exemple, il y a un problème:
DatabaseMgr*
Application::Database()
Application::Database() ne peut pas être implémenté dans
Application, car:
- le module Database (comme tous les modules) utilise la classe
Application
Ça, ce n'est pas bien. Il faudrait séparer les concernes : une
classe qui donne accès aux modules, et une autre classe (ou une
module) qui est toujours présente, et qui contient tout ce qui
est commun et qui sert dans les modules.
C'est à peu près mon design actuel, mais ceci me gêne: si le
module Database n'est pas inclus, il n'est donc pas
utilisable, d'où la question:
- pourquoi proposer la fonction membre Database() ?
Car je ne veux pas renvoyer NULL. Je préfère le principe "si
c'est là tu peux l'utiliser" qu'au principe "ok c'est là mais
attention aucune garantie". Ca simplifie l'utilisation.
S'il ne s'agit que de la classe Application... on pourrait assez
facilement le générer automatiquement à partir des paramètres
externes.
Je ne compte pas charger les modules à la demande, mais au
lancement de l'application initialiser tous les modules (pour
une base de données ça peut être long), et alors si tout est
ok, les modules sont présents et utilisables directement, sans
test de NULL.
Je ne connais pas assez l'application pour donner les détails,
mais je soupçonne que ton gestion des NULL serait toujours ce
qu'il y a de plus simple. Les #ifdef à droit et à gauche,
surtout dans le code des fonctions (ou la définition des
classes) mènent très vite à quelque chose d'immaintenable.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Mais justement, il faut compiler tout, et seulement linker une
partie.
Dans ton exemple ci-dessus:static_cast< DatabaseMgr* >( module->second )tu sembles trouver acceptable mon idée de classe Module de
base qu'on downcast selon le besoin. Ca rend ma classe
centrale Application indépendante de tout module. Car dans ton
exemple, il y a un problème:DatabaseMgr*
Application::Database()Application::Database() ne peut pas être implémenté dans
Application, car:
- le module Database (comme tous les modules) utilise la classe
Application
Ça, ce n'est pas bien. Il faudrait séparer les concernes : une
classe qui donne accès aux modules, et une autre classe (ou une
module) qui est toujours présente, et qui contient tout ce qui
est commun et qui sert dans les modules.
C'est à peu près mon design actuel, mais ceci me gêne: si le
module Database n'est pas inclus, il n'est donc pas
utilisable, d'où la question:
- pourquoi proposer la fonction membre Database() ?Car je ne veux pas renvoyer NULL. Je préfère le principe "si
c'est là tu peux l'utiliser" qu'au principe "ok c'est là mais
attention aucune garantie". Ca simplifie l'utilisation.
S'il ne s'agit que de la classe Application... on pourrait assez
facilement le générer automatiquement à partir des paramètres
externes.
Je ne compte pas charger les modules à la demande, mais au
lancement de l'application initialiser tous les modules (pour
une base de données ça peut être long), et alors si tout est
ok, les modules sont présents et utilisables directement, sans
test de NULL.
Je ne connais pas assez l'application pour donner les détails,
mais je soupçonne que ton gestion des NULL serait toujours ce
qu'il y a de plus simple. Les #ifdef à droit et à gauche,
surtout dans le code des fonctions (ou la définition des
classes) mènent très vite à quelque chose d'immaintenable.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Mais justement, il faut compiler tout, et seulement linker
une partie.
J'essaye de m'orienter vers celà.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Mais justement, il faut compiler tout, et seulement linker
une partie.
J'essaye de m'orienter vers celà.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Mais justement, il faut compiler tout, et seulement linker
une partie.
J'essaye de m'orienter vers celà.
Toute reflexion faite, dans ton cas, je ferais plutôt quelque
chose du genre :
-- On définit une interface pour chaque module, c-à-d une
classe abstraite dont toutes les fonctions sont virtuelle
pûre. (Sauf le destructeur, qui est virtuelle, mais inline.)
L'importance, c'est que n'importe qui peut utiliser cette
classe sans générer des références externes dans son fichier
objet. Quelque chose du genre :
class DataBase
{
public:
virtual ~DataBase() {}
virtual void doSomethingWithDataBase() = 0 ;
virtual int doSomethingElse() = 0 ;
// ...
} ;
-- La définition de la classe Application est toujours
complète, avec la déclaration des fonctions usine pour
toutes les modules, qu'ils soient présents ou non. Ça veut
dire que ces utilisateurs doivent pouvoir faire face à une
fonction usine qui renvoie NULL, mais je crois que c'est
toujours plus simple que les alternatifs.
Dans la définition de la classe Application, on doit pouvoir
se servir des déclarations préalables des interfaces des
modules, de façon à ce que l'inclusion de Application.hh
n'impose pas l'inclusion de toutes les ModuleX.hh.
-- La définition de chaque fonction usine dans Application
appartient au module -- il n'y a pas de fichier
d'implémentation de Application. Donc, quelque part dans la
bibliothèque de DataBase, on a :
DataBase*
Application::getDataBase() const
{
return new ConcreteDataBase ;
}
C'est très important : cette fonction se trouve dans la
bibliothèque du module, et NON dans l'implémentation de
Application (où on s'attendrait à le trouver d'habitude).
-- Il existe une bibliothèque « dummy », avec une
implémentation vide de toutes les fonctions usine, du
genre :
DataBase*
Application::getDataBase() const
{
return NULL ;
}
C'est très important : chaque fonction se trouve dans un
fichier source séparé, de façon à ce que l'éditeur de liens
puisse prendre getDataBase de cette bibliothèque, mais
getModuleX d'une autre bibliothèque.
Au fond, on doit pouvoir générer cette bibliothèque
automatiquement, à partir de la définition de Application.
Si ça vaut la peine ou non dépend de combien de modules tu
as.
-- Enfin, quand tu fais l'édition des liens, tu précises les
bibliothèques pour les modules que tu veux avoir ; de cette
façon, les fonctions usines pour ces modules sont
incorporées de leurs bibliothèques. Et en fin de la liste
des bibliothèques, tu précises la bibliothèque « dummy »,
pour résoudre les références à des fonctions usine qui n'ont
pas été résolues par les bibliothèques des modules.
Si tu te sers de GNU make sous Unix, le fichier make
pourrait contenir quelque chose du genre :
define build
$(CC) -o $@ ... application.o $(addprefix -l,$^) -lDummy
endef
complete : DataBase ModuleX
$(build)
simple : ModuleX
$(build)
complete simple : application.o libDummy.a
Pour Windows, évidemment, les séquences de commande et les
suffixes des noms de fichiers seront différents. En
revanche, je conseillerais quand même l'utilisation de GNU
make.
Toute reflexion faite, dans ton cas, je ferais plutôt quelque
chose du genre :
-- On définit une interface pour chaque module, c-à-d une
classe abstraite dont toutes les fonctions sont virtuelle
pûre. (Sauf le destructeur, qui est virtuelle, mais inline.)
L'importance, c'est que n'importe qui peut utiliser cette
classe sans générer des références externes dans son fichier
objet. Quelque chose du genre :
class DataBase
{
public:
virtual ~DataBase() {}
virtual void doSomethingWithDataBase() = 0 ;
virtual int doSomethingElse() = 0 ;
// ...
} ;
-- La définition de la classe Application est toujours
complète, avec la déclaration des fonctions usine pour
toutes les modules, qu'ils soient présents ou non. Ça veut
dire que ces utilisateurs doivent pouvoir faire face à une
fonction usine qui renvoie NULL, mais je crois que c'est
toujours plus simple que les alternatifs.
Dans la définition de la classe Application, on doit pouvoir
se servir des déclarations préalables des interfaces des
modules, de façon à ce que l'inclusion de Application.hh
n'impose pas l'inclusion de toutes les ModuleX.hh.
-- La définition de chaque fonction usine dans Application
appartient au module -- il n'y a pas de fichier
d'implémentation de Application. Donc, quelque part dans la
bibliothèque de DataBase, on a :
DataBase*
Application::getDataBase() const
{
return new ConcreteDataBase ;
}
C'est très important : cette fonction se trouve dans la
bibliothèque du module, et NON dans l'implémentation de
Application (où on s'attendrait à le trouver d'habitude).
-- Il existe une bibliothèque « dummy », avec une
implémentation vide de toutes les fonctions usine, du
genre :
DataBase*
Application::getDataBase() const
{
return NULL ;
}
C'est très important : chaque fonction se trouve dans un
fichier source séparé, de façon à ce que l'éditeur de liens
puisse prendre getDataBase de cette bibliothèque, mais
getModuleX d'une autre bibliothèque.
Au fond, on doit pouvoir générer cette bibliothèque
automatiquement, à partir de la définition de Application.
Si ça vaut la peine ou non dépend de combien de modules tu
as.
-- Enfin, quand tu fais l'édition des liens, tu précises les
bibliothèques pour les modules que tu veux avoir ; de cette
façon, les fonctions usines pour ces modules sont
incorporées de leurs bibliothèques. Et en fin de la liste
des bibliothèques, tu précises la bibliothèque « dummy »,
pour résoudre les références à des fonctions usine qui n'ont
pas été résolues par les bibliothèques des modules.
Si tu te sers de GNU make sous Unix, le fichier make
pourrait contenir quelque chose du genre :
define build
$(CC) -o $@ ... application.o $(addprefix -l,$^) -lDummy
endef
complete : DataBase ModuleX
$(build)
simple : ModuleX
$(build)
complete simple : application.o libDummy.a
Pour Windows, évidemment, les séquences de commande et les
suffixes des noms de fichiers seront différents. En
revanche, je conseillerais quand même l'utilisation de GNU
make.
Toute reflexion faite, dans ton cas, je ferais plutôt quelque
chose du genre :
-- On définit une interface pour chaque module, c-à-d une
classe abstraite dont toutes les fonctions sont virtuelle
pûre. (Sauf le destructeur, qui est virtuelle, mais inline.)
L'importance, c'est que n'importe qui peut utiliser cette
classe sans générer des références externes dans son fichier
objet. Quelque chose du genre :
class DataBase
{
public:
virtual ~DataBase() {}
virtual void doSomethingWithDataBase() = 0 ;
virtual int doSomethingElse() = 0 ;
// ...
} ;
-- La définition de la classe Application est toujours
complète, avec la déclaration des fonctions usine pour
toutes les modules, qu'ils soient présents ou non. Ça veut
dire que ces utilisateurs doivent pouvoir faire face à une
fonction usine qui renvoie NULL, mais je crois que c'est
toujours plus simple que les alternatifs.
Dans la définition de la classe Application, on doit pouvoir
se servir des déclarations préalables des interfaces des
modules, de façon à ce que l'inclusion de Application.hh
n'impose pas l'inclusion de toutes les ModuleX.hh.
-- La définition de chaque fonction usine dans Application
appartient au module -- il n'y a pas de fichier
d'implémentation de Application. Donc, quelque part dans la
bibliothèque de DataBase, on a :
DataBase*
Application::getDataBase() const
{
return new ConcreteDataBase ;
}
C'est très important : cette fonction se trouve dans la
bibliothèque du module, et NON dans l'implémentation de
Application (où on s'attendrait à le trouver d'habitude).
-- Il existe une bibliothèque « dummy », avec une
implémentation vide de toutes les fonctions usine, du
genre :
DataBase*
Application::getDataBase() const
{
return NULL ;
}
C'est très important : chaque fonction se trouve dans un
fichier source séparé, de façon à ce que l'éditeur de liens
puisse prendre getDataBase de cette bibliothèque, mais
getModuleX d'une autre bibliothèque.
Au fond, on doit pouvoir générer cette bibliothèque
automatiquement, à partir de la définition de Application.
Si ça vaut la peine ou non dépend de combien de modules tu
as.
-- Enfin, quand tu fais l'édition des liens, tu précises les
bibliothèques pour les modules que tu veux avoir ; de cette
façon, les fonctions usines pour ces modules sont
incorporées de leurs bibliothèques. Et en fin de la liste
des bibliothèques, tu précises la bibliothèque « dummy »,
pour résoudre les références à des fonctions usine qui n'ont
pas été résolues par les bibliothèques des modules.
Si tu te sers de GNU make sous Unix, le fichier make
pourrait contenir quelque chose du genre :
define build
$(CC) -o $@ ... application.o $(addprefix -l,$^) -lDummy
endef
complete : DataBase ModuleX
$(build)
simple : ModuleX
$(build)
complete simple : application.o libDummy.a
Pour Windows, évidemment, les séquences de commande et les
suffixes des noms de fichiers seront différents. En
revanche, je conseillerais quand même l'utilisation de GNU
make.
-- Enfin, quand tu fais l'édition des liens, tu précises les
bibliothèques pour les modules que tu veux avoir ; de cette
façon, les fonctions usines pour ces modules sont
incorporées de leurs bibliothèques. Et en fin de la liste
des bibliothèques, tu précises la bibliothèque « dummy »,
pour résoudre les références à des fonctions usine qui n'ont
pas été résolues par les bibliothèques des modules.
Si tu te sers de GNU make sous Unix, le fichier make
pourrait contenir quelque chose du genre :
define build
$(CC) -o $@ ... application.o $(addprefix -l,$^) -lDummy
endef
complete : DataBase ModuleX
$(build)
simple : ModuleX
$(build)
complete simple : application.o libDummy.a
Pour Windows, évidemment, les séquences de commande et
les suffixes des noms de fichiers seront différents. En
revanche, je conseillerais quand même l'utilisation de
GNU make.
J'utilise un IDE (VC++), et je sais que le jour où on va
commencer à compiler/linker le programme en multiples versions
à la demande des clients ça va pas être top.
J'ai exploré avec succès la voie de cmake (un méta-make en
quelque sorte), mais j'attends le prochain VC++ dont le format
des fichiers projets de l'IDE est le même que celui de leur
nouveau make (msbuild) avant de choisir quoi utiliser.
Je pense avoir tous les éléments pour faire quelque chose
d'assez élégant. Je m'étonne quand même de ne pas trouver de
ressources là dessus. N'y-a-til pas un design pattern ou
quelque chose de ce style qui existe ? Personne n'a publié sur
ce sujet qui me semble courant ?
-- Enfin, quand tu fais l'édition des liens, tu précises les
bibliothèques pour les modules que tu veux avoir ; de cette
façon, les fonctions usines pour ces modules sont
incorporées de leurs bibliothèques. Et en fin de la liste
des bibliothèques, tu précises la bibliothèque « dummy »,
pour résoudre les références à des fonctions usine qui n'ont
pas été résolues par les bibliothèques des modules.
Si tu te sers de GNU make sous Unix, le fichier make
pourrait contenir quelque chose du genre :
define build
$(CC) -o $@ ... application.o $(addprefix -l,$^) -lDummy
endef
complete : DataBase ModuleX
$(build)
simple : ModuleX
$(build)
complete simple : application.o libDummy.a
Pour Windows, évidemment, les séquences de commande et
les suffixes des noms de fichiers seront différents. En
revanche, je conseillerais quand même l'utilisation de
GNU make.
J'utilise un IDE (VC++), et je sais que le jour où on va
commencer à compiler/linker le programme en multiples versions
à la demande des clients ça va pas être top.
J'ai exploré avec succès la voie de cmake (un méta-make en
quelque sorte), mais j'attends le prochain VC++ dont le format
des fichiers projets de l'IDE est le même que celui de leur
nouveau make (msbuild) avant de choisir quoi utiliser.
Je pense avoir tous les éléments pour faire quelque chose
d'assez élégant. Je m'étonne quand même de ne pas trouver de
ressources là dessus. N'y-a-til pas un design pattern ou
quelque chose de ce style qui existe ? Personne n'a publié sur
ce sujet qui me semble courant ?
-- Enfin, quand tu fais l'édition des liens, tu précises les
bibliothèques pour les modules que tu veux avoir ; de cette
façon, les fonctions usines pour ces modules sont
incorporées de leurs bibliothèques. Et en fin de la liste
des bibliothèques, tu précises la bibliothèque « dummy »,
pour résoudre les références à des fonctions usine qui n'ont
pas été résolues par les bibliothèques des modules.
Si tu te sers de GNU make sous Unix, le fichier make
pourrait contenir quelque chose du genre :
define build
$(CC) -o $@ ... application.o $(addprefix -l,$^) -lDummy
endef
complete : DataBase ModuleX
$(build)
simple : ModuleX
$(build)
complete simple : application.o libDummy.a
Pour Windows, évidemment, les séquences de commande et
les suffixes des noms de fichiers seront différents. En
revanche, je conseillerais quand même l'utilisation de
GNU make.
J'utilise un IDE (VC++), et je sais que le jour où on va
commencer à compiler/linker le programme en multiples versions
à la demande des clients ça va pas être top.
J'ai exploré avec succès la voie de cmake (un méta-make en
quelque sorte), mais j'attends le prochain VC++ dont le format
des fichiers projets de l'IDE est le même que celui de leur
nouveau make (msbuild) avant de choisir quoi utiliser.
Je pense avoir tous les éléments pour faire quelque chose
d'assez élégant. Je m'étonne quand même de ne pas trouver de
ressources là dessus. N'y-a-til pas un design pattern ou
quelque chose de ce style qui existe ? Personne n'a publié sur
ce sujet qui me semble courant ?
J'utilise un IDE (VC++), et je sais que le jour où on va
commencer à compiler/linker le programme en multiples versions
à la demande des clients ça va pas être top.
A priori, tu dois pouvoir créer de nouveaux cibles dans ton
fichier de make. Strictement parlant, c'est tout ce qu'il te
faut. L'avantage de GNU make, ici, c'est dans la possibilité de
définir des séquences prédéfinies, avec des fonctions
($(addprefix...)) pour générer de texte à partir des dépendances
($^). Avec un autre make, il y a beaucoup plus à tapper à la
main. Mais en principe, ça doit être faisable quand même.
Une autre possibilité serait d'utiliser le IDE pour le
développement et la génération de la verion « complète », avec
tous les modules, et d'écrire un petit script qui génère le
makefile voulu en fonction de la ligne de commande, puis
l'exécute. Il serait même assez simple d'écrire un petit
programme GUI qui présente les modules avec des checkbox, puis
génère le makefile voulu et l'exécute.
Je pense avoir tous les éléments pour faire quelque chose
d'assez élégant. Je m'étonne quand même de ne pas trouver de
ressources là dessus. N'y-a-til pas un design pattern ou
quelque chose de ce style qui existe ? Personne n'a publié sur
ce sujet qui me semble courant ?
Pas à ce que je sache. À vrai dire, je ne sais pas si c'est
aussi courant que ça, comme problème. Mais de toute façon, je ne
l'ai jamais vu abordé dans la littérature.
J'utilise un IDE (VC++), et je sais que le jour où on va
commencer à compiler/linker le programme en multiples versions
à la demande des clients ça va pas être top.
A priori, tu dois pouvoir créer de nouveaux cibles dans ton
fichier de make. Strictement parlant, c'est tout ce qu'il te
faut. L'avantage de GNU make, ici, c'est dans la possibilité de
définir des séquences prédéfinies, avec des fonctions
($(addprefix...)) pour générer de texte à partir des dépendances
($^). Avec un autre make, il y a beaucoup plus à tapper à la
main. Mais en principe, ça doit être faisable quand même.
Une autre possibilité serait d'utiliser le IDE pour le
développement et la génération de la verion « complète », avec
tous les modules, et d'écrire un petit script qui génère le
makefile voulu en fonction de la ligne de commande, puis
l'exécute. Il serait même assez simple d'écrire un petit
programme GUI qui présente les modules avec des checkbox, puis
génère le makefile voulu et l'exécute.
Je pense avoir tous les éléments pour faire quelque chose
d'assez élégant. Je m'étonne quand même de ne pas trouver de
ressources là dessus. N'y-a-til pas un design pattern ou
quelque chose de ce style qui existe ? Personne n'a publié sur
ce sujet qui me semble courant ?
Pas à ce que je sache. À vrai dire, je ne sais pas si c'est
aussi courant que ça, comme problème. Mais de toute façon, je ne
l'ai jamais vu abordé dans la littérature.
J'utilise un IDE (VC++), et je sais que le jour où on va
commencer à compiler/linker le programme en multiples versions
à la demande des clients ça va pas être top.
A priori, tu dois pouvoir créer de nouveaux cibles dans ton
fichier de make. Strictement parlant, c'est tout ce qu'il te
faut. L'avantage de GNU make, ici, c'est dans la possibilité de
définir des séquences prédéfinies, avec des fonctions
($(addprefix...)) pour générer de texte à partir des dépendances
($^). Avec un autre make, il y a beaucoup plus à tapper à la
main. Mais en principe, ça doit être faisable quand même.
Une autre possibilité serait d'utiliser le IDE pour le
développement et la génération de la verion « complète », avec
tous les modules, et d'écrire un petit script qui génère le
makefile voulu en fonction de la ligne de commande, puis
l'exécute. Il serait même assez simple d'écrire un petit
programme GUI qui présente les modules avec des checkbox, puis
génère le makefile voulu et l'exécute.
Je pense avoir tous les éléments pour faire quelque chose
d'assez élégant. Je m'étonne quand même de ne pas trouver de
ressources là dessus. N'y-a-til pas un design pattern ou
quelque chose de ce style qui existe ? Personne n'a publié sur
ce sujet qui me semble courant ?
Pas à ce que je sache. À vrai dire, je ne sais pas si c'est
aussi courant que ça, comme problème. Mais de toute façon, je ne
l'ai jamais vu abordé dans la littérature.