IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Réduire la visibilité d'une fonction membre


Sujet :

Langage C++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 483
    Points : 13 681
    Points
    13 681
    Billets dans le blog
    1
    Par défaut Réduire la visibilité d'une fonction membre
    Bonjour,

    Voici un code me permettant de réduire la visibilité d'une fonction membre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include <iostream>
     
    class Base
    {
    public:
        virtual void f()
        {
            std::cout << "Base::f()" << std::endl;
        }
    };
     
    class Derived : public Base
    {
    private:
       // Visibilité réduite
        void f() override
        {
            Base::f();
        }
    };
     
    int main(void)
    {
        Derived d;
     
        d.f();
    }
    Y a t-il un moyen plus élégant de faire ceci ?

    Merci d'avance !

  2. #2
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 483
    Points : 13 681
    Points
    13 681
    Billets dans le blog
    1
    Par défaut
    En cherchant comment faire l'inverse (rendre public quelque chose de privé), je suis tombé sur la solution à mon problème orignal. Voici la solution plus élégante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Derived : public Base
    {
    private:
        using Base::f;
    };

    Dans le même temps, je me demande vraiment s'il y a un intérêt à faire ça... mais c'est un autre sujet

  3. #3
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 170
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 170
    Points : 12 291
    Points
    12 291
    Par défaut
    Pour moi, c'est un refus d'héritage, donc une erreur de conception.
    Le concepteur devrait être en droit de maitriser les appel à "f" et c'est pas le cas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include <iostream>
     
    class Base
    {
    public:
        virtual void f()
        {
            std::cout << "Base::f()" << std::endl;
        }
    };
     
    class Derived : public Base
    {
    private:
        using Base::f;
    };
     
    int main(void)
    {
        Base d = Derived();
     
        d.f();
    }

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 128
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 128
    Points : 33 053
    Points
    33 053
    Billets dans le blog
    4
    Par défaut
    L'intérêt me semble moindre puisque ta nouvelle portée n'est utilisée que dans le cas où Derived est typée statiquement. Si tu utilises le polymorphisme et travailles sur des Base*, ce qui est normalement le plus souvent le cas dans une relation d'héritage, c'est toujours public.

  5. #5
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 170
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 170
    Points : 12 291
    Points
    12 291
    Par défaut
    C'est bien là le problème.
    Le mainteneur de la classe "Derived" fait l'assertion qu'il est le seul à pouvoir utiliser cette méthode, même pas les classes dérivée de "Derived".
    Il pourrait donc la modifier s'en avoir à concevoir tout l'attirail qu'entraine des méthodes publiques (vérification des arguments, les pré-conditions, les post-conditions, etc...).

  6. #6
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 486
    Points : 6 169
    Points
    6 169
    Par défaut
    Bonjour,

    Citation Envoyé par Bktero Voir le message
    Voici un code me permettant de réduire la visibilité d'une fonction membre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include <iostream>
     
    class Base
    {
    public:
        virtual void f()
        {
            std::cout << "Base::f()" << std::endl;
        }
    };
     
    class Derived : public Base
    {
    private:
       // Visibilité réduite
        void f() override
        {
            Base::f();
        }
    };
     
    int main(void)
    {
        Derived d;
     
        d.f();
    }
    Ce code ne réduit pas la visibilité de Base::f. En effet, si tu remplaces d.f(); par d.Base::f();, tu constateras que cela compile.
    Si Derived dérive publiquement de Base, alors ça veut dire que tu veux que Base soit publique, donc que ses fonctions publiques soient accessibles pour l'utilisateur.
    Si tu veux de l'héritage, mais que Base soit privée, alors il faut faire de l'héritage privé.

    Voici un exemple concret qui utilise l'héritage privé :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    #include <cassert>
    #include <iosfwd>
    #include <stdexcept>
     
    ////////////////////////////////////////////////
    // External code which the user cannot modify //
    ////////////////////////////////////////////////
     
    class WorkerConstVisitor;
     
    class Worker {
    public:
    	virtual void accept(WorkerConstVisitor& visitor) const = 0;
    	// ...
    };
     
    class Consultant;
    class Trainee;
     
    class WorkerConstVisitor {
    public:
    	virtual void visit(const Consultant& visited) = 0;
    	virtual void visit(const Trainee&    visited) = 0;
    	// ...
    };
     
    class Consultant final : public Worker {
    public:
    	void accept(WorkerConstVisitor& visitor) const final {visitor.visit(*this);}
    	// ...
    };
     
    class Trainee final : public Worker {
    public:
    	void accept(WorkerConstVisitor& visitor) const final {visitor.visit(*this);}
    	// ...
    };
     
    ///////////////
    // User code //
    ///////////////
     
    class Config final {
    	// ...
    };
     
    class PrintWorkerDescriptionImpl final : private WorkerConstVisitor {
    public:
    	PrintWorkerDescriptionImpl(std::ostream& os, const Worker& worker, const Config& config) :
    		m_os{os}, m_config{config}, m_printDone{false}
    	{
    		worker.accept(*this);
    		assert(m_printDone);
    		if(!m_printDone)
    			throw std::logic_error("The visit function did not do the work.");
    				// It can happen if a NON PURE virtual function "visit" is added to
    				// WorkerConstVisitor, but such an addition would be a development mistake.
    	}
    private:
    	void visit(const Consultant& visited) final
    	{
    		// ...
    		m_printDone = true;
    	}
    	void visit(const Trainee& visited) final
    	{
    		// ...
    		m_printDone = true;
    	}
    	std::ostream& m_os;
    	const Config& m_config;
    	bool          m_printDone;
    };
     
    void printWorkerDescription(std::ostream& os, const Worker& worker, const Config& config)
    {
    	PrintWorkerDescriptionImpl{os, worker, config};
    }
    Edit 2017-08-24-22h45 : Renommage de printDescription en printWorkerDescription.

  7. #7
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 483
    Points : 13 681
    Points
    13 681
    Billets dans le blog
    1
    Par défaut
    Je vais décrire la situation plus globalement

    Je développe une interface graphique pour l'embarqué. Le framework m'offre les classes suivantes:
    - Drawable qui est abstraite (avec quelques méthodes virtuelles pures)
    - Container qui est n'est pas abstraite et qui hérite publiquement de Drawable. C'est une sorte de composite / layout. Elle ajoute notamment une méthode add(Drawable)

    Je crée des layouts personnalisés, donc des classes héritant de Container. J'ai notamment ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Layout_1 : public Container
    {
    public:
        void setTitle(Drawable& top);
    }
    Comme Layout_1 dérive de Container, je peux faire ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Label label("title");
    Layout_1 layout;
    layout.add(label);
    Sauf que add() ne faire rien visuellement, c'est un peu un méthode interne... Je me demande d'ailleurs pourquoi Container n'est pas abstraite et add() protected mais passons... J'aurais aimé la masquer pour que l'API de mon Layout_1 soit simple à utiliser correctement et difficile à utiliser incorrectement (http://programmer.97things.oreilly.c...se_Incorrectly). Cette histoire de réduire la visibilité n'est pas une bonne solution.

    Mon idée suivante a donc été celle-ci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Layout_1 : private Container,  public Drawable
    {
    };
    Sauf que bien sûr je me retrouve avec des tas d'erreurs :
    "C:\Program Files\JetBrains\CLion 2017.2\bin\cmake\bin\cmake.exe" --build C:\Users\X-pigradot\svn\0373_AI_Island_Delight_trunk\cmake-build-debug --target aivg-delight-simulation -- -j 2
    Scanning dependencies of target aivg-delight-simulation
    [  1%] Building CXX object CMakeFiles/aivg-delight-simulation.dir/simulator/source/main.cpp.obj
    In file included from C:\Users\X-pigradot\svn\0373_AI_Island_Delight_trunk\simulator\source\main.cpp:10:0:
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/TestPage.hpp:60:7: warning: direct base 'touchgfx::Drawable' inaccessible in 'Layout_1' due to ambiguity
     class Layout_1 : private Container,  public Drawable
           ^
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/TestPage.hpp:144:14: error: cannot declare field 'TestPage::layout_m' to be of abstract type 'Layout_1'
         Layout_1 layout_m;
                  ^
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/TestPage.hpp:60:7: note:   because the following virtual functions are pure within 'Layout_1':
     class Layout_1 : private Container,  public Drawable
           ^
    In file included from C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/touchgfx/framework/include/touchgfx/containers/Container.hpp:42:0,
                     from C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/touchgfx/framework/include/touchgfx/Screen.hpp:46,
                     from C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/Page.hpp:4,
                     from C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/TestPage.hpp:4,
                     from C:\Users\X-pigradot\svn\0373_AI_Island_Delight_trunk\simulator\source\main.cpp:10:
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/touchgfx/framework/include/touchgfx/Drawable.hpp:113:18: note: 	virtual void touchgfx::Drawable::draw(const touchgfx::Rect&) const
         virtual void draw(const Rect& invalidatedArea) const = 0;
                      ^
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/touchgfx/framework/include/touchgfx/Drawable.hpp:129:18: note: 	virtual touchgfx::Rect touchgfx::Drawable::getSolidRect() const
         virtual Rect getSolidRect() const = 0;
                      ^
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/touchgfx/framework/include/touchgfx/Drawable.hpp:202:18: note: 	virtual void touchgfx::Drawable::getLastChild(int16_t, int16_t, touchgfx::Drawable**)
         virtual void getLastChild(int16_t x, int16_t y, Drawable** last) = 0;
                      ^
    In file included from C:\Users\X-pigradot\svn\0373_AI_Island_Delight_trunk\simulator\source\main.cpp:10:0:
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/TestPage.hpp: In member function 'virtual void TestPage::setupScreen()':
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/TestPage.hpp:126:44: error: 'touchgfx::Drawable' is an ambiguous base of 'Layout_1'
             layout_m.setPosition(0, 0, 320, 240);
                                                ^
    C:/Users/X-pigradot/svn/0373_AI_Island_Delight_trunk/Software/application/Common/delight/page/TestPage.hpp:126: confused by earlier errors, bailing out
    
    J'ai essayé de rajouter ceci dans ma classe, mais ça n'a pas eu l'effet désiré...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public:
        using Container::draw;
        using Container::getSolidRect;
        using Container::setPosition;
        using Container::getLastChild;

  8. #8
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 128
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 128
    Points : 33 053
    Points
    33 053
    Billets dans le blog
    4
    Par défaut
    Le problème de class Layout_1 : private Container, public Drawable c'est que tu te retrouves avec un héritage en diamant, et c'est assez rarement une bonne chose.
    Pourquoi vouloir hériter de Container et ne pas simplement en faire une composition ?
    As-tu besoin à quelconque moment de traiter tes Layout_1 comme Container ou Drawable ?
    Si oui, de toute façon c'est la visibilité de la classe mère qui l'emporte si tu manipules un Base* (Drawable*), donc ce que tu as fait ne changes rien du tout.

  9. #9
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 195
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 195
    Points : 17 163
    Points
    17 163
    Par défaut
    J'aurai vu le "layout" comme une sorte de "LayoutManager" de Swing (en Java), c'est à dire une stratégie/politique à chaud.
    Tu aurais un "LayoutableContainer", qui hérite de Container, et applique le layout au add (et au resize)

  10. #10
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 483
    Points : 13 681
    Points
    13 681
    Billets dans le blog
    1
    Par défaut
    Mais oui ! Préférer la composition à l'héritage !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class Layout : public Drawable
    {
    public:
        void draw(const Rect& rect) const override
        {
            container_m.draw(rect);
        }
     
        // Idem pour les autres fonctions membres abstraites
     
    protected:
        Container container_m;
     
        void add(Drawable& drawable)
        {
            container_m.add(drawable);
        }
     
        void remove(Drawable& drawable)
        {
            container_m.remove(drawable);
        }
    };
     
    class Layout_1 : public Layout
    {
        ...
    };
    Du coup, Layout_1 n'expose plus publiquement ce add() piégeux mais uniquement des fonctions intéressantes. Il reste disponible dans Layout_1 qui en a besoin pour ajouter ses composants à la hiérarchie des drawables. Je crois que c'est la bonne solution à mon problème

  11. #11
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 195
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 195
    Points : 17 163
    Points
    17 163
    Par défaut
    Je ne vois toujours pas pourquoi ton layout est drawable…

  12. #12
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 483
    Points : 13 681
    Points
    13 681
    Billets dans le blog
    1
    Par défaut
    J'ai envie de te dire....parce que le framework est fait comme ça. Il utilise un genre de pattern Composite où un Container est un Drawable qui contient plusieurs Drawables. Dans certains cas, c'est vraiment un aggrégat de widgets simples pour faire un widget plus compliqué (comme AnalogClock) mais des fois ça se rapproche vraiment d'un layout dans lequel tu ajoutes tes widgets et qui se charge de faire la mise en forme (le layout ) (comme ListLayout)

    C'est un peu surprenant par rapport à Qt par exemple (je ne maitrise pas Swing mais vue ta réponse ça doit être pareil) où les layouts ne servent qu'à faire de la mise en forme.

    Je devrais réussir à faire des layouts qui ne sont pas des Drawables, mais je ne souhaite pas trop m'éloigner de la philosophie du framework pour que les autres développeurs s'y retrouvent. Et faire des layouts qui ne sont pas de containers mais en composent un, c'est déjà un bon éloignement

  13. #13
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 128
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 128
    Points : 33 053
    Points
    33 053
    Billets dans le blog
    4
    Par défaut
    Si tu as a un moment besoin d'enregistrer ton layout dans un truc en tant que Drawable* qui gérera son affichage pour toi - ce qui est souvent le cas avec un framework de ce genre -, alors oui tu n'as pas le choix.
    Si tu appelles la méthode draw toi-même pour l'afficher, l'héritage est inutile.

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Salut,

    Il y a quelques années de cela, nous avons eu une discussion à peu près identique sur le fond, qui a dégénéré en débat sur le respect du LSP (ca rappelle des souvenirs, non ). En gros, je soutenais (et je soutiens d'ailleurs toujours) que le fait de vouloir réduire l'accessibilité d'une fonction (et, de manière plus générale, de n'importe quelle propriété issue de la classe de base) par rapport à l'accessibilité de l'élément au niveau de la classe de base est en contradiction avec le principe de substitution de Liskov.

    Or, (c'était surtout sur ce point que le bât blessait), le LSP est un principe de conception, c'est à dire un "GO/ NO GO" qui te permet de décider si tu peux effectivement envisager de faire en sorte que ta classe dérivée puisse être substituée à ta classe de base partout où un élément du type de la classe de base est attendu.

    Certains me disaient à l'époque (et je me doute que d'autres utilseront un argument similaire) que "cela ne peut pas contrevenir au LSP, vu que le langage l'autorise."; ce à quoi je répliquais que le langage n'est absolument pas armé remettre en cause les décisions que nous avons prises : qu'elles soient bonnes ou mauvaises, pour autant que la syntaxe et la grammaire soient respectées, le langage n'a simplement pas d'autre choix que de s'aligner (selon les règles qui lui sont imposées) sur nos décisions.

    Même si mon avis sur certains points évoqués dans cette discussion a beaucoup évolué (entre autres en ce qui concerne la définition du terme "propriété" ou le lien qui lie LSP à la programmation par contrat), mon avis général sur la question qui est "le changement de visibilité dans une classe dérivée viole-t-il le LSP n'a absolument pas changé

    Mais le plus gros de l'évolution de mon point de vue est que je suis devenu beaucoup plus intransigeant sur le respect des principes SOLID (et de la loi de Déméter, pour ne citer qu'elle): autant je pouvais concevoir, à l'époque, que nous puissions nous trouver dans de (très) rares cas, confronté à des situations dans lesquelles il était "raisonnable" de déroger au différents principes, autant je suis de plus en plus persuadé que le simple fait d'y déroger ne fait rien d'autre que nous tendre un piège dans lequel nous tomberons forcément plus tard et qui nous obligera à y déroger d'avantage.

    Ainsi, je me suis rendu compte que, les cinq principes SOLID sont intimement liés les uns aux autres, même s'il est fréquent de les dissocier au cours des discussions que l'on peut avoir. En effet, après avoir lourdement insisté sur le fait que réduire la visibilité d'une fonction dans la classe dérivée est une lourde erreur de conception (car elle viole le LSP), je ne peux m'empêcher d'aller plus loin et de me dire que, si tu te trouve dans cette situation, c'est parce que tu en viens à vouloir apposer "un emplâtre sur une jambe de bois" dans l'espoir de "lisser" une autre erreur de conception.

    Le truc, c'est que si tu continues dans cette voie, tu ne fera jamais qu'empiler des couches sur cette erreur de conception, ce qui n'aura jamais qu'un seul effet (re)connu: la rendre de plus en plus grosse, au point que tôt ou tard, elle finisse par se transformer en véritable montagne impossible à surmonter.

    De manière générale, je dirais donc que, si tu es confronté à cette situation, c'est parce que tu n'as déjà pas correctement respecté le SRP et l'ISP, et que cela t'a amené indument à envisager le fait que Container puisse hériter de Drawable.

    Mais, revenons à ce qui est à la base de tout ton problème : à mon sens, tu n'as "simplement" pas accordé assez d'importance à la sémantique que tu veux donner à ta classe Container et aux services que tu estimes qu'elle devra te rendre.

    Bien sur, il est "logique" que tu souhaites pouvoir disposer d'une fonction draw, mais pas parce que ce serait une classe qui EST-UN élément Drawable. Si tu as besoin de cette fonction, c'est uniquement pour qu'elle puisse, effectivement, "transmettre" cette instruction aux élément Drawable qu'elle contient.

    En effet, si je pars du principe que le terme de Container a été judicieusement choisi (ce dont je n'ai aucune raison de douter, jusqu'à preuve du contraire), la vraie responsabilité de ta classe Container est... de garder en mémoire "un ensemble d'éléments Drawable. Autrement dit, de servir de "collection" d'éléments Drawable. Or, il n'y a rien à faire : il est sémantiquement incorrect d'envisager le fait qu'une collection puisse ... être un élément qu'il faudra tracer.

    Il faut donc se poser la question des services dont tu souhaites disposer. Je dirais personnellement que la liste de ces services doit ressembler à quelque chose comme:
    • ajouter un nouvel élément (après qu'il ait été créé "par ailleurs"), potentiellement à différents endroits (==> fonction add + insert )
    • retirer un (ou plusieurs) élément(s successifs) devenu(s) "obsolète(s)"
    • savoir combien d'élément sont contenus à un instant T dans la collection
    • (éventuellement) parcourir l'ensemble des éléments contenus (potentiellement selon différents critères de tri)
    • (éventuellement) accéder à certains éléments en fonction d'un rectangle donné (le cas échéant, selon différents critères)
    • Et c'est tout (modulo quelques services auxquels je n'aurais pas pensé)


    Accessoirement, une fonction libre proche de void draw(Container const &, Rect const &r); pourrait -- le cas échéant -- être ajouté à "l'interface étendue" de cette classe (car, depuis la discussion dont je parlais en introduction, je me suis rendu compte que, bien qu'une fonction libre manipulant un type d'objet particulier ne fasse pas à proprement parler partie des propriétés de ce type particulier, elle fait -- très clairement -- partie de l'interface de ce type d'objet). Cette fonction ferait appel aux fonction membres de la classe Container qui permettent de récupérer l'élément traçable sur base d'un rectangle afin de faire appel à la fonction draw de ce dernier.

    En gros, cette erreur de sémantique t'a tout simplement mené à ne pas respecter le SRP, car ta classe Container se retrouve (telle qu'elle est présentée pour l'instant) avec deux responsabilité (maintenir les élément et... permettre le tracé des éléments contenus), ce qui t'a amené à faire un choix inoportun en terme d'héritage (et donc, en violation du LSP), qui a eu pour effet de rendre l'interface publique de ta classe plus complexe qu'elle n'aurait pu l'être (en violant au passage l'ISP). Et je ne serais même pas étonné outre mesure si tu te rendais compte -- si tu persistes dans cette voie -- avec le temps que l'OCP était lui-aussi mis à mal par la conception telle qu'elle se présente.

    Comme je te le disais plus haut : tous les principes SOLID sont intimement liés les uns aux autres, car mon analyse démontre clairement qu'il ne manque plus que la preuve que le DIP est lui aussi mis à mal pour compléter la liste (et je suis convaincu qu'on pourrait arriver à le prouver avec une meilleure connaissance du projet )

    De la même manière, je ne suis absolument pas persuadé qu'il soit opportun de faire hériter Layout de Drawable et ce, exactement pour la même raison: le but "originel" du layout est bien plus d'apporter des informations sur la manière dont les éléments Drawable seront représentés à l'écran( comprend : horizontalement Vs verticalement, voir, sous forme "tabulaire") que d'être lui-même tracé à l'écran.

    Au final, je dirais que tu n'as absolument aucun héritage à mettre ici: Drawable servirait de classe de base à tes "widgets" (tes boutons, tes zones de texte, tes bouton radio, ...) et contiendraient (par agrégation) un élement de type Layout (orientation et organisation à préciser) qui contiendrait lui-même (toujours par agrégation, donc) un élément de type Container (pluralité à préciser le cas échéant). Mais tant Layout que Container pourraient le cas échéant servir de classe de base à des hiérarchie de classes spécifiques, ne serait-ce que --justement -- pour permettre d'introduire les "points de variations" concernant l'orientation et / ou la pluralité des éléments

    Enfin, et cela cloturera mon (roman) intervention présente, je me demande depuis pas mal de temps déjà si on ne fait pas fausse route en voulant absolument utiliser le paradigme orienté objet, avec sa (ses) hiérarchie de classes abominable pour les bibliothèques graphiques / d'IHM. Je me demande en effet si une approche orientée ECS ne serait pas intéressante

  15. #15
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 483
    Points : 13 681
    Points
    13 681
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par koala01 Voir le message
    De manière générale, je dirais donc que, si tu es confronté à cette situation, c'est parce que tu n'as déjà pas correctement respecté le SRP et l'ISP, et que cela t'a amené indument à envisager le fait que Container puisse hériter de Drawable.
    Beau pavé mais qui est basé sur une erreur : ce n'est pas mon choix de faire hériter Container de Drawable ! J'utilise une bibliothèque qui a fait ce choix, je ne fais que le subir !

    En gros, cette erreur de sémantique t'a tout simplement mené à ne pas respecter le SRP, car ta classe Container se retrouve (telle qu'elle est présentée pour l'instant) avec deux responsabilité (maintenir les élément et... permettre le tracé des éléments contenus), ce qui t'a amené à faire un choix inoportun en terme d'héritage (et donc, en violation du LSP)
    Mais n'est pas le principe même du design pattern Composite ?

    Citation Envoyé par koala01 Voir le message
    qui a eu pour effet de rendre l'interface publique de ta classe plus complexe qu'elle n'aurait pu l'être (en violant au passage l'ISP)
    C'est bien mon problème à moi. Quand je fais des dérivés de Container, je me retrouve avec des tas de méthodes publiques qui me servent pas.

    Au final, j'ai aujourd'hui rencontré de nombreux effets indésirables en faisant une classe Layout qui hérite de Drawable et qui compose un Container, au lieu de faire directement des dérivés de Container. Des effets visuels bizarres, difficilement compréhensibles. Il faudra que je prenne toutes les méthodes de Drawable qui sont redéfinies dans Container et que ma classe Layout les redéfinissent également en redirigeant vers mon container composé. Au final, je crois que je vais rester dans la situation d'origine pour des raisons pratiques, même si la solution à base de composition était quand même vraiment tentante

  16. #16
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 195
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 195
    Points : 17 163
    Points
    17 163
    Par défaut
    Ce que je ne comprends pas, c'est pourquoi tu n'aurais pas un LayoutableContainer, qui utilise les algorithmes fournit par le layout pour choisir comment placer ses sous composants.
    Le Layout n'a pas à dessiner, il gère plus ou moins une collection de rectangles avec leurs contraintes associées (tailles minimales, maximales, souhaitées) et celles du conteneur.

    C'est un pattern strategy. Inséré dans le pattern composite que constitue Container vis-à-vis de Drawable.

  17. #17
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Beau pavé mais qui est basé sur une erreur : ce n'est pas mon choix de faire hériter Container de Drawable ! J'utilise une bibliothèque qui a fait ce choix, je ne fais que le subir !
    Qu'elle soit de ton fait ou du fait d'un illustre inconnu, l'erreur l'erreur est là et bien là.

    Et c'est cette erreur même qui t'oblige à trouver le moyen de la "contourner" au risque d'en commettre une pire encore.
    Citation Envoyé par Bktero Voir le message
    Mais n'est pas le principe même du design pattern Composite ?
    Effectivement, c'est le principe même du patron Composite.

    Mais ce patron permet de mettre en place les notions de noeuds (node) et de feuilles (leaf), et c'est une notion qui n'a rien à voir avec notion de traçable.

    Au mieux, le choix du terme Container pour représenter la notion de noeud est inopportun, au pire, l'erreur de conception vient du fait d'avoir fait hériter la notion de noeud de la classe de base Drawable.

    Citation Envoyé par Bktero Voir le message
    C'est bien mon problème à moi. Quand je fais des dérivés de Container, je me retrouve avec des tas de méthodes publiques qui me servent pas.
    Ce qui est bien la preuve que SRP, LSP et ISP ne sont pas respectée au niveau de la classe Container...

    Du coup, la solutions la moins sale serait sans doute soit de redéfinir les fonctions dont tu n'as pas besoin de manière à ce qu'elle ne fassent purement et simplement rien ou, mieux encore, de les redéfinir de telles sortes qu'elle fassent planter le programme en débug sur une assertion non vérifiée, par exemple avec un code aussi simple que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void TaClasse::laFonctionNonSouhaitée(){
        assert(false && "You should never call this function");
    }

  18. #18
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 195
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 195
    Points : 17 163
    Points
    17 163
    Par défaut
    Si mes souvenirs sont bons, Composite<Bidule> se traduit par "est un bidule, contient une collection de Bidule, délègue chaque méthode de Bidule à tous ses éléments"

    Sauf que dans les bibliothèques de GUI, en réalité, on a la moitié des méthodes qui sont déléguées, et l'autre qui est remplacée.
    Par exemple, onclickevent est propagée à tous les composants (ou seulement à ceux susceptibles d'être sous le clic), tandis que le resize ne l'est pas.

    Le Container peut être une classe de base pour une popup, un bloc de composant (= un panel?) une fenetre, etc.
    Mais il a souvent des propriétés propres.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 14
    Dernier message: 16/05/2006, 11h26
  2. Réponses: 3
    Dernier message: 29/04/2006, 13h02
  3. Réponses: 4
    Dernier message: 01/12/2005, 12h33
  4. Réponses: 3
    Dernier message: 28/11/2005, 12h15
  5. Thread avec une fonction membre d'une classe
    Par SteelBox dans le forum Windows
    Réponses: 6
    Dernier message: 01/03/2004, 01h15

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo