Design Pattern



  • So ganz verstehe ich deine Bedenken ehrlich gesagt nicht.

    Naja, wenn er das ganze pluginbasiert aufziehen will, aendern sich die möglichen Varianten zur Laufzeit. Bei nem Template muessen die zur compilezeit feststehen.

    Ergo, Template und Plugin beissen sich schon bissi in der Beziehung.
    Oder überzeug mich vom gegenteil und bau ne vector template implementierung, wo ich den Allokator über nen plugin zur verfuegung stellen kann, das geladen werden kann aber nicht muss, bzw der user beim start des Programms festlegen kann, welcher allokator in allen vectoren verwendet wird, und trotzdem der allokator ueber nen template parameter festgelegt werden soll 🙂

    Ciao ...



  • RHBaum schrieb:

    Ergo, Template und Plugin beissen sich schon bissi in der Beziehung.
    Oder überzeug mich vom gegenteil und bau ne vector template implementierung, wo ich den Allokator über nen plugin zur verfuegung stellen kann, das geladen werden kann aber nicht muss, bzw der user beim start des Programms festlegen kann, welcher allokator in allen vectoren verwendet wird, und trotzdem der allokator ueber nen template parameter festgelegt werden soll 🙂

    Easy. Du machst nen allocator der intern eben polymorph den echten allokator verwendet. Deshalb kannst du zB einen allocator beim ctor von vector angeben.

    bzw. ist das ganze deshalb illegal weil ein allocator keinen internen state haben darf - aber das ist ein design fehler der library und kein technisches hindernis.



  • Du hast zwei Baustellen. Zum einen wendest du ein Verhaltensmuster an, nämlich Strategie.

    class Crypter;
    class Client
    {
    public:
        void read(Crypter& c);  // strategy
    };
    

    Auf der anderen Seite steht die Frage nach der Erzeugung oder Verwaltung der Strategien. Hier ist ein Erzeugungsmuster gefragt. Deine Vorstellung geht denke ich in Richtung Fabrik.

    namespace SecurityLayer
    {
    
    class CrypterFactory
    {
    public:
        typedef Crypter* (*Creator)();
        void RegisterCrypter(const std::string& name, Creator f);
        Crypter* CreateCrypter(const std::string& name);
    };
    
    // oder Erzeugung mit Prototype Pattern
    class CrypterRepository
    {
    public:
        void RegisterCrypter(const std::string& name, Crypter* prototype);
        Crypter* CreateCrypter(const std::string& name);
    };
    
    }
    

    Alle Fabriken für Crypt, Hash usw. in einer Klasse SecurityLayer zu sammeln halte ich für sinnlos. Sieht nach Ordnungswahn aus, "toll, ich habe alles an einer Stelle". So eine Bündelung von Abhängigkeiten ist aber gerade zu vermeiden. Mach aus Klasse einen Namensraum um die Zusammengehörigkeit in einem Layer aufzuzeigen. Anders sieht es aus, wenn SecurityLayer eine abstrakte Fabrik darstellt, die austauschbar ist und jeweils zusammengehörende Strategien erzeugt.

    class Hasher;
    class AbstractSecurityFactory
    {
    public:
        virtual Crypter* CreateCrypter() = 0;
        virtual Hasher* CreateHasher() = 0;
    };
    
    class ABCSecurityFactory : public AbstractSecurityFactory
    {
    public:
        ABCCrypter* CreateCrypter();
        ABCHasher* CreateHasher();
    };
    class XYZSecurityFactory : public AbstractSecurityFactory
    {/*..*/};
    
    class Client
    {
    public:
        void ChangeSecurity(AbstractSecurityFactory&);
    };
    

    http://de.wikipedia.org/wiki/Abstrakte_Fabrik



  • Man könnte das auch als Decorator Pattern realisieren:

    class IDataSink
    {
       ...
       virtual void write( char* Data, unsigned int Size ) = 0;
       ...
    };
    
    class FileDataSink : public IDataSink
    {
       // Daten in Datei schreiben
       void write( const char* Data, unsigned int Size );
    };
    
    class MemoryDataSink : public IDataSink
    {
       // Daten in Puffer (Hauptspeicher) schreiben
       void write( const char* Data, unsigned int Size );
    };
    
    class HashDataSinkDecorator
    {
       IDataSink& DataSink_;
    
       HashDataSinkDecorator( IDataSink& Sink ) :
          DataSink_( Sink )
       {
       }
    
       void write( char* Data, unsigned int Size )
       {
          // aus Daten Hash berechnen und Hash schreiben
          DataSink_.write( Hash, HashSize );
       }
    };
    
    int main()
    {
       string s = "Hello World";
    
       // File Sink zum Schreiben in Datei
       FileSink FS( "c:/hash.dat" );
    
       // Hash Sink zum Erzeugen eines Hashes
       HashDataSink HS( FS );
       HS.write( s.c_str(), s.size() );
    }
    


  • Erst mal nochmal vielen dank fuer euer Antworten.

    @brotbernd:

    ich hab jetzt das hier geschrieben:

    namespace security
    {
    
    template <class T>
    class SecurityFactory
    {
    public:
    	typedef T* (*GetInstance) ();
    	typedef std::map<std::string, GetInstance>  AlgorithmMap;
    
    	SecurityFactory(void) {};
    	~SecurityFactory(void) {};
    
    	bool AddAlgorithm(const std::string& Name, GetInstance pfGetInstance)
    	{
    		if( AlgorithmExist(Name) )
    			return false;
    
    		m_AlgorithmMap[ Name ] = pfGetInstance;
    		return true;
    	}
    	void RemoveAlgorithm(const std::string& Name)
    	{
    		AlgorithmMap::iterator iter = m_AlgorithmMap.find( Name );
    
    		if( iter != m_AlgorithmMap.end() )
    			m_AlgorithmMap.erase( iter);
    	}
    	bool AlgorithmExist(std::string Name)
    	{
    		return (m_AlgorithmMap.find( Name) != m_AlgorithmMap.end() ) ? true : false;
    	}
    
    	T* Create(const std::string& Name)
    	{
    		if( AlgorithmExist(Name) )
    			return m_AlgorithmMap[Name]();
    		return NULL;
    	}
    
    private:
    
    	AlgorithmMap m_AlgorithmMap;
    };
    
    }
    

    Seh ich das richtig so?
    Und dann einfach drei verschiedene Factories erstellen:

    security::SecurityFactory<security::encode::Encoder> EncoderFactory;
    security::SecurityFactory<security::hash::Hash> HashFactory;
    security::SecurityFactory<security::crypt::Crypter> CrypterFactory;
    

    Greetz C0de4Fun



  • Ich würde statt einem Funktionszeiger einen Zeiger auf eine abstrakte Factory-Klasse registrieren.
    Also

    template <class T>
    class AbstractFactory
    {
    public:
        virtual T* CreateInstance() = 0;
    };
    
    class XxxFactoryDings
    {
    // ...
    
        void RegisterFactory(const std::string& Name, AbstractFactory<Foo>* fooFactory);
    
    // ...
    };
    

    Zwei andere Dinge die mit der Factory-Sache nix mehr zu tun haben und auch entschieden Ansichtssache sind: ich würde Smart-Pointer verwenden, und ich würde in den beiden Fällen "Factory mit dem Namen ist bereits registriert" und "kann Objekt nicht erzeugt werden weil Factory mit dem Namen nicht bekannt" Exceptions werfen antatt false bzw. nen NULL Pointer zurückzugeben.



  • hustbaer schrieb:

    Ich würde statt einem Funktionszeiger einen Zeiger auf eine abstrakte Factory-Klasse registrieren.
    Also

    template <class T>
    class AbstractFactory
    {
    public:
        virtual T* CreateInstance() = 0;
    };
    
    class XxxFactoryDings
    {
    // ...
    
        void RegisterFactory(const std::string& Name, AbstractFactory<Foo>* fooFactory);
    
    // ...
    };
    

    Meinste dann ca so:

    template<class T>
    class AbstractFactory
    {
    public:
    	virtual T* Create() = 0;
    
    };
    
    class StrFactory : public AbstractFactory<std::string>
    {
    public:
    	virtual std::string* Create() { return new std::string("test"); };
    };
    
    int main(int argc, char** argv)
    {
    	std::vector< AbstractFactory<std::string>* > Vector;
    	StrFactory StrFac;
    
    	Vector.push_back( &StrFac);
    
    	std::cout << (Vector[0]->Create())->c_str()  << std::endl;
    
        return 0;
    }
    

    Und wo ist da der Vorteil gegenueber einer Create Funktion? Seh ich leider ned ganz.

    hustbaer schrieb:

    Zwei andere Dinge die mit der Factory-Sache nix mehr zu tun haben und auch entschieden Ansichtssache sind: ich würde Smart-Pointer verwenden, und ich würde in den beiden Fällen "Factory mit dem Namen ist bereits registriert" und "kann Objekt nicht erzeugt werden weil Factory mit dem Namen nicht bekannt" Exceptions werfen antatt false bzw. nen NULL Pointer zurückzugeben.

    Also das mit den Smart Pointern werd ich mir halt jetzt doch mal anschauen. Hab bis jetzt immer versucht um boost drum rum zu kommen. Da ich denk des wird einige Zeit an einlesen kosten. Aber was solls. Kann ja eig nur Vorteile bringen :).

    Und zu den Exceptions: Ich muss sagen ich find des ned so gut. Sagen wir ich lad ein PlugIn das registriert dein MD5 Alogrithmus da es diesen braucht. Jetzt lad ich spaeter ein neues PlugIn dazu das wiederrum auch MD5 benoetigt und das halt auch registrieren will. Jetzt muss er ja immer vorher checken ob der schon vorhanden ist.
    Ist doch einfacher so.
    Beim "kann kein Objekt erzeugen" find ichs an sich au gut. Mal schauen.

    Greetz C0de4Fun



  • Ja, mein ich ca. so.

    Vorteil: die Factory kann "State" halten. Und das kann aus den verschiedensten Gründen vorteilhaft sein.
    Man braucht es in vielen Programmen vielleicht nicht oft (oder gar nicht), aber wenn man es mal bräuchte, und es geht nicht, dann ist es richtig kacke.



  • hustbaer schrieb:

    Ja, mein ich ca. so.

    Vorteil: die Factory kann "State" halten. Und das kann aus den verschiedensten Gründen vorteilhaft sein.
    Man braucht es in vielen Programmen vielleicht nicht oft (oder gar nicht), aber wenn man es mal bräuchte, und es geht nicht, dann ist es richtig kacke.

    Joa warum dann nicht gleich besser machen des stimmt schon.

    Ich hab jetzt mal beides umgesetzt und wollte fragen obste mal nen Blick drueber werfen koenntest:

    namespace security
    {
    
    template <class T>
    class SecurityFactory
    {
    public:
    	typedef AbstractFactory<T> AlgorithmFactory;
    	typedef std::map<std::string, AlgorithmFactory*>  AlgorithmMap;
    	typedef std::vector<std::string> AlgorithmNameVec;
    ....
    	bool AddAlgorithm(const std::string& Name, AlgorithmFactory* Factory)
    	{
    		if( AlgorithmExist(Name) )
    			return false;
    
    		m_AlgorithmMap[ Name ] = Factory;
    		return true;
    	}
    	....
    	boost::shared_ptr<T> Create(const std::string& Name)
    	{
    		if( AlgorithmExist(Name) )
    			return m_AlgorithmMap[Name]->Create();
    
    		throw "Algorithm not found";
    	}
    
    private:
    
    	AlgorithmMap m_AlgorithmMap;
    };
    

    und dann zB. so:

    class HashFac : public AbstractFactory<security::hash::Hash>
    {
    public:
    	boost::shared_ptr< security::hash::Hash> Create()
    	{
    		boost::shared_ptr< security::hash::Hash> Tmp( new security::hash::MD5 );
    		return Tmp; 
    	};
    };
    
    int main( int argc, char** argv)
    {
    	HashFac HashTmp;
    
    	boost::shared_ptr<security::hash::Hash> HashPtr;
    	security::SecurityFactory<security::hash::Hash> HashFactory;
    
    	std::string test("lol armin stock");
    
    	HashFactory.AddAlgorithm( "md5", &HashTmp);
    	HashPtr = HashFactory.Create("md5");
    
    	HashPtr->Update( (const BYTE*)test.c_str(), test.size() );
    	HashPtr->Finish( );
    
    	std::cout << HashPtr->Hex() ;
    
    	return 0;
    }
    


  • Nur so nebenbei: Es gibt neben Funktionszeigern und polymorphen Interfaces noch eine weitere Lösung, bei der all der Boilerplate-Code von Klassen wegfällt und die trotzdem State haben kann: std::tr1::function . Lohnt sich vor allem, wenn man mit bind() Funktionsobjekte zusammenbastelt.

    In C++0x gibts ausserdem noch Lambda-Ausdrücke.


Anmelden zum Antworten