Pergunta

Eu olhei esta explicação na Wikipedia , especificamente amostra do C ++, e não conseguem reconhecer a diferença entre apenas definir 3 classes, criar instâncias e chamando-os, e que exemplo. O que vi foi apenas colocar duas outras classes no processo e não pode ver onde haveria um benefício. Agora eu tenho certeza que estou faltando alguma coisa óbvia (madeira para as árvores?) - Alguém poderia explicar-lo usando um exemplo do mundo real definitiva


O que posso fazer a partir das respostas até agora, parece-me ser apenas uma forma mais complexa de fazer isso:

have an abstract class: MoveAlong with a virtual method: DoIt()
have class Car inherit from MoveAlong, 
     implementing DoIt() { ..start-car-and-drive..}
have class HorseCart inherit from MoveAlong, 
     implementing DoIt() { ..hit-horse..}
have class Bicycle inherit from MoveAlong, 
     implementing DoIt() { ..pedal..}
now I can call any function taking MoveAlong as parm 
passing any of the three classes and call DoIt
Isn't this what Strategy intents? (just simpler?)

[Edit-update] A função Refiro-me acima é substituída por outra classe em que MoveAlong seria atributo que é definido de acordo com a necessidade baseado no algoritmo implementado nesta nova classe. (Semelhante ao que é demonstrado na resposta aceita.)


[Edit-update] Conclusão

O padrão de estratégia tem a sua utilidade, mas eu sou um crente forte no KISS, e tenderia a técnicas mais simples e menos ofuscante. Principalmente desde que eu quero passar o código de fácil manutenção (e porque eu provavelmente vai ser o único que tem que fazer as mudanças!).

Foi útil?

Solução

O ponto é separar algoritmos em classes que podem ser conectados em tempo de execução. Por exemplo, digamos que você tem um aplicativo que inclui um relógio. Há muitas maneiras diferentes que você pode desenhar um relógio, mas a maior parte da funcionalidade subjacente é a mesma. Assim você pode criar uma interface de exibição do relógio:

class IClockDisplay
{
    public:
       virtual void Display( int hour, int minute, int second ) = 0;
};

Depois de ter sua classe relógio que está ligada a um temporizador e atualiza o visor do relógio uma vez por segundo. Então você teria algo como:

class Clock
{
   protected:
      IClockDisplay* mDisplay;
      int mHour;
      int mMinute;
      int mSecond;

   public:
      Clock( IClockDisplay* display )
      {
          mDisplay = display;
      }

      void Start(); // initiate the timer

      void OnTimer()
      {
         mDisplay->Display( mHour, mMinute, mSecond );
      }

      void ChangeDisplay( IClockDisplay* display )
      {
          mDisplay = display;
      }
};

Em seguida, em tempo de execução que você instancia seu relógio com a classe exibição correta. ou seja, você poderia ter ClockDisplayDigital, ClockDisplayAnalog, ClockDisplayMartian todos implementando a interface IClockDisplay.

Assim, você pode mais tarde adicionar qualquer tipo de novo visor do relógio através da criação de uma nova classe sem ter que mexer com a sua classe Relógio, e sem ter que substituir métodos que podem ser confuso para manter e depurar.

Outras dicas

Em Java você usar um fluxo de entrada cifra para descriptografar assim:

String path = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), ???);

Mas a cifra de fluxo não tem conhecimento do que algoritmo de criptografia que você pretende usar ou o tamanho do bloco, a estratégia estofamento etc ... Novos algoritmos serão adicionados o tempo todo para codificar-los não é prático. Em vez disso, passar em um objeto Cipher estratégia para dizer-lhe como realizar a descriptografia ...

String path = ... ;
Cipher strategy = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), strategy);

Em geral, você usar o padrão de estratégia sempre que houver qualquer objeto que sabe o que precisa fazer, mas não como para fazê-lo. Outro bom exemplo é gerenciadores de layout em Swing, embora, nesse caso, aquilo não funcionar muito bem, consulte Totalmente GridBag para uma ilustração divertida.

NB: Existem dois padrões no trabalho aqui, como o envolvimento de fluxos nos córregos é um exemplo de Decorator .

Há uma diferença entre estratégia e decisão / escolha. Na maioria das vezes um seríamos manipulação decisões / escolhas em nosso código, e realizá-los usando if () / interruptor (construções). padrão de estratégia é útil quando há uma necessidade de dissociar a lógica / algoritmo de uso.

Como exemplo, pense em um mecanismo de pesquisa, onde diferentes usuários iria verificar se há recursos / atualizações. Agora a gente pode querer alguns dos usuários priveliged para ser notificado com um tempo de resposta mais rápido ou com mais detalhes. Essentailly a lógica sendo usado alterações baseadas em funções de usuário. Estratégia faz sentido do ponto de vista de design / arquitetura, em níveis mais baixos de granularidade que deve sempre ser questionada.

O padrão de estratégia permite-lhe explorar polimorfismo sem estender sua classe principal. Em essência, você está colocando todas as partes variáveis ??na interface estratégia e implementações e os principais delegados de classe para eles. Se os seus principais usos objeto apenas uma estratégia, é quase o mesmo que ter um método abstrato (puro virtual) e implementações diferentes em cada subclasse.

A abordagem estratégia oferece alguns benefícios:

  • você pode mudar de estratégia em tempo de execução - comparar este a mudar o tipo de classe em tempo de execução, o que é muito mais difícil, específico compilador e impossível para métodos não-virtuais
  • uma classe principal pode usar mais de um estratégias que lhe permite recombinar-los de várias maneiras. Considere uma classe que anda uma árvore e avalia uma função com base em cada nó e o resultado atual. Você pode ter uma estratégia curta (em profundidade ou em largura) e estratégia de cálculo (alguns functor - ou seja, 'contagem de números positivos' ou 'soma'). Se você não usar estratégias, você precisará implementar subclasse para cada combinação de caminhada / cálculo.
  • código é mais fácil de manter como modificar ou estratégia entendimento não requer que você entenda todo o objeto principal

A desvantagem é que, em muitos casos, o padrão de estratégia é um exagero - o operador switch / case está lá por uma razão. Considere começar com demonstrações de fluxo de controle simples (switch / case ou se), em seguida, apenas se for necessário mover a hierarquia de classes e se você tiver mais de um dimensões de variabilidade, estratégias extrato de fora. ponteiros de função cair em algum lugar no meio deste continuum.

Leitura recomendada:

Uma maneira de olhar para isto é quando você tem uma variedade de ações que deseja executar e essas ações são determinadas em tempo de execução. Se você criar uma tabela hash ou dicionário de estratégias, você pode recuperar essas estratégias que correspondem aos valores de comando ou parâmetros. Uma vez que seu subconjunto é selecionado, você pode simplesmente iterar a lista de estratégias e executar em sucessão.

Um exemplo concreto seria cálculo do total de uma ordem. Seus parâmetros ou comandos seria preço base, impostos locais, imposto municipal, imposto estadual, o transporte à terra e cupom de desconto. A flexibilidade entram em jogo quando você lida com a variação de encomendas - alguns estados não terão imposto sobre vendas, enquanto outras ordens precisará aplicar um cupom. Você pode atribuir dinamicamente a ordem dos cálculos. Enquanto você ter sido responsável por todos os seus cálculos, você pode acomodar todas as combinações sem re-compilação.

Este padrão de design permite encapsular algoritmos em classes.

A classe que usa a estratégia, a classe do cliente, está dissociada da implementação do algoritmo. Você pode alterar a execução de algoritmos, ou adicionar novo algoritmo sem ter que modificar o cliente. Isso também pode ser feito de forma dinâmica: o cliente pode escolher o algoritmo que vai usar.

Por exemplo, imagine um aplicativo que precisa para salvar uma imagem em um arquivo; a imagem podem ser salvos em diferentes formatos (PNG, JPG ...). Os algoritmos de codificação serão todos implementados em classes diferentes que partilham a mesma interface. A classe cliente irá escolher um, dependendo da preferência do usuário.

No exemplo Wikipedia, essas instâncias podem ser passadas para uma função que não tem que se importar qual classe essas instâncias pertencem. A função apenas chama execute sobre o objeto passado, e sei que a coisa certa vai acontecer.

Um exemplo típico do padrão de estratégia é como arquivos de trabalhar em Unix. Dado um descritor de arquivo, você pode ler a partir dele, escrever para ele, poll-lo, buscar nele, ioctls envia-lhes, etc., sem ter que saber se você está lidando com um arquivo, diretório, tubulação, socket, dispositivo , etc. (É claro que algumas operações, como procurar, não funcionam em tubulações e soquetes. Mas leituras e gravações vai funcionar muito bem nesses casos.)

Isso significa que você pode escrever código genérico para lidar com todos esses diferentes tipos de "arquivos", sem ter que escrever código separado para lidar com arquivos contra diretórios, etc. O Unix do kernel cuida de delegar as chamadas para o código certo.

Agora, este é padrão de estratégia como usado no código do kernel, mas você não especificou que tinha que ser o código do usuário, apenas um exemplo do mundo real. : -)

Estratégia padrão funciona na simples ideia ou seja, "Composição Favor sobre herança" de modo que a estratégia / algoritmo pode ser alterado em tempo de execução. Para ilustrar, vamos dar um exemplo onde, em que precisamos para criptografar mensagens diferentes com base em seu tipo, por exemplo, MailMessage, ChatMessage etc.

class CEncryptor
{
    virtual void encrypt () = 0;
    virtual void decrypt () = 0;
};
class CMessage
{
private:
    shared_ptr<CEncryptor> m_pcEncryptor;
public:
    virtual void send() = 0;

    virtual void receive() = 0;

    void setEncryptor(cost shared_ptr<Encryptor>& arg_pcEncryptor)
    {
        m_pcEncryptor =  arg_pcEncryptor;
    }

    void performEncryption()
    {
        m_pcEncryptor->encrypt();
    }
};

Agora, em tempo de execução você pode instanciar diferentes mensagens herdados de CMessage (como CMailMessage: CMessage público) com diferentes encryptors (como CDESEncryptor: CEncryptor público)

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top