2015-03-13 640 views
5

Uwalniam się od najlepszego sposobu implementacji wzorca strategii w C++. Do tej pory, zawsze używany standardowy sposób, jeśli z kontekstu nie ma wskaźnik do klasy bazowej strategii następująco:Jak zaimplementować wzorzec strategii w C++ z std :: function

class AbstractStrategy{ 
public: 
    virtual void exec() = 0; 
} 
class ConcreteStrategyA{ 
public: 
    void exec(); 
} 
class ConcreteStrategyB{ 
public: 
    void exec(); 
} 

class Context{ 
public: 
    Context(AbstractStrategy* strategy):strategy_(strategy){} 
    ~Context(){ 
      delete strategy; 
     } 
     void run(){ 
      strategy->exec(); 
     } 
private: 
    AbstractStrategy* strategy_; 

Ponieważ posiadające wskaźniki do obiektów może spowodować złe zachowanie, szukałem dla bezpieczniejszego sposobu zaimplementowania tego wzoru i znalazłem this question, gdzie std::function są proponowane jako lepszy sposób obsługi tego wzorca.

Czy ktoś mógłby lepiej wyjaśnić, jak działa std::function, może z przykładem ze wzorem strategii?

Odpowiedz

7

Należy zauważyć, że obiekty pojedynczej metody są izomorficzne z funkcjami, a strategie są tylko obiektami o pojedynczej metodzie.

Więc w zasadzie, można pozbyć się wszystkich swoich zajęć, a po prostu użyć std::function<void()> zamiast:

class Context { 
public: 
    template<typename F> 
    explicit Context(F strategy) : strategy(std::move(strategy)) { } 

    void run() { strategy(); } 

private: 
    std::function<void()> strategy; 
}; 

Następnie można przejść każdy wpłacone do konstruktora Context:

Context ctx([] { std::cout << "Hello, world!\n"; }); 
ctx.run(); 
+0

W takim przypadku, jak zaimplementowałbyś F (strategię)? I dlaczego musisz używać std :: move? – gcswoosh

+2

@Gabrielecswoosh Strategia jest po prostu dowolnym wskaźnikiem funkcji lub obiektem z przeciążonym 'operator()'.Mój przykład podaje lambdę (która definiuje 'void operator()() const'). 'std :: move' ma zapobiegać kopiowaniu. – rightfold

0

coś takiego to?

#include <functional> 
#include <iostream> 


typedef std::function<int(int)> Strategy; 

void execute_strategy(Strategy strategy, int object) { 
    std::cout << strategy(object) << std::endl; 
}; 

int power2(int i) { 
    return i*i; 
}; 

int main() { 
    execute_strategy(power2, 3); 
} 

Mam na myśli, że wzorzec strategii jest rozwiązaniem problemu dotyczącego braku rzeczywistych lambd. Zostało to rozwiązane, więc możesz po prostu przekazać odpowiednią funkcję.

3

Jest trochę dyskusji na ten temat here i here. Myślę, że to zależy od konkretnego przypadku. Czy twoja strategia jest na przykład tylko prostym wywołaniem funkcji? Często mam strategie, w których moja strategia będzie wymagać wielu możliwości, co nie jest obsługiwane po prostu dzięki funkcji lub funktorowi. Ale jeśli potrzebujesz tylko funkcji lub funktora, to std::function jest poręcznym sposobem na zapewnienie maksymalnej elastyczności, przechowywania wskaźników funkcyjnych, lambdas lub funktorów. Mogą występować problemy z wydajnością, które zostały omówione here dla oryginalnej implementacji zwiększenia.

+0

Co zasugerowałbyś w przypadku, gdy strategia nie jest prostym wywołaniem funkcji? W takim przypadku wskaźnik do klasy abstrakcyjnej jest jedynym sposobem? – gcswoosh

+1

Tak. Możesz odpowiednio sterować zasobami za pomocą 'std :: unique_ptr' lub' std :: shared_ptr'. – sfjac

1

Praca nad odpowiedzią райтфолд

Zasadniczo, można pozbyć się wszystkich swoich zajęć, a po prostu użyć std :: funkcję zamiast.

uogólniona funkcja ta pozwala na przepuszczanie funkcje lambda funktory i funkcje składowe (używając std :: wiązania)

class Context { 
public: 
    explicit Context(std::function<void()> input) : strategy(input) { } 

void run() { strategy(); } 

private: 
    std::function<void()> strategy; 
}; 

Następnie można przejść każdy wpłacone do konstruktora kontekstu:

Context ctx([] { std::cout << "Hello, world!\n"; }); 
ctx.run(); 

lub

void sayHelloWorld(){ 
    std::cout << "Hello, world!\n"; 
} 


int main(){ 
    Context ctx(sayHelloWorld); 
    ctx.run(); 
} 

lub

class SayHelloWorld{ 
    operator()(){std::cout << "Hello, world!\n";} 
} 

int main(){ 
    SayHelloWorld hello_world; 
    Context ctx(hello_world); 
    ctx.run(); 
}