Modelo de padrão de design padrão de método (herança) vs padrão de estratégia (delegação)

Padrão de método de modelo e padrão de estratégia são dois padrões de design comportamentais comumente usados. Eles usaram dois métodos de implementação diferentes de herança e delegação , respectivamente.Como o artigo anterior falou sobre diagramas UML, este artigo pode trazer os diagramas UML dos dois modos diferentes, a propósito.

Padrão de método de modelo

O padrão de método de modelo (Template Method Pattern) define o esqueleto do algoritmo e escrevemosos detalhes de implementação específicos em subclasses (subtipos) herdando a herança . Essa abordagem nos permite alterar partes do algoritmo por meio de subclasses sem modificar o método de modelo.

Subtipagem Comportamental

Antes de falar sobre herança, devemos primeiro mencionar um conceito Subtipagem comportamental, que é um princípio importante na programação orientada a objetos, que fornece uma maneira mais formal de determinar quando herança (herança) e extensão (extensão) devem ser usadas. Uma ideia central da subtipagem comportamental é que o código do cliente não deve ser afetado quando um subtipo é usado para substituir um supertipo. Ou seja, um subtipo deve ser capaz de satisfazer os requisitos de seu supertipo tanto sintática quanto comportamentalmente. Essa ideia está intimamente relacionada ao Princípio da Substituição de Liskov (LSP). Três condições importantes para a realização dessa visão são as seguintes:

  1. Invariantes iguais ou mais fortes que a superclasse: O subtipo deve ter invariantes iguais ou mais fortes que a superclasse. Uma invariante é uma condição que uma propriedade de uma classe precisa satisfazer durante todo o tempo de vida do objeto. Isso significa que o subtipo precisa manter as mesmas restrições de atributo do supertipo ou adicionar restrições mais rígidas. Isso ajuda a garantir que os objetos do subtipo sejam comportamentalmente compatíveis com o supertipo enquanto atendem a necessidades mais específicas. Isso é um pouco abstrato, vamos dar um exemplo: Por exemplo, existe uma classe pai chamada Bird, e uma de suas propriedades é chamada speed. BirdUma invariante da classe pode ser que a velocidade nunca é negativa, ou seja speed >= 0, . Agora, criamos uma subclasse Penguinpara estender Birda classe. Nesse caso, Penguina classe precisa satisfazer Birdas mesmas invariantes ou invariantes mais fortes que a classe. Isso significa que Penguina classe também precisa garantir que a velocidade nunca seja negativa (mesma invariante) ou pode adicionar restrições mais rígidas, como um limite superior na velocidade (invariante mais forte), por exemplo 0 <= speed <= 10.

  2. Pré-condições iguais ou mais fracas para todos os métodos na superclasse: Os métodos de um subtipo devem ter as mesmas pré-condições ou mais fracas que as da superclasse. Pré-condições são condições que precisam ser atendidas antes que um método seja chamado, geralmente envolvendo restrições nos parâmetros de entrada do método. Isso significa que os métodos de subtipos devem ter menos restrições ou as mesmas restrições nos parâmetros de entrada que os supertipos. Dessa forma, quando subtipos são usados ​​para substituir supertipos, a chamada original não falha devido a restrições de entrada.

  3. Pós-condições iguais ou mais fortes para todos os métodos na superclasse: Métodos de subtipos devem ter as mesmas ou mais fortes pós-condições da superclasse. Pós-condições são condições que devem ser satisfeitas após uma chamada de método, geralmente envolvendo restrições na saída do método. Isso significa que a saída do método de um subtipo deve atender aos requisitos do supertipo ou ter garantias mais fortes. Dessa forma, quando a subclasse substituir a classe pai, a exatidão dos resultados de saída do programa dependentes da classe pai não serão afetados.

class Car extends Vehicle {
    int fuel;
    boolean engineOn;
    //@ invariant fuel >= 0;
    //@ requires fuel > 0 && !engineOn;
    //@ ensures engineOn;
    void start() { … }
    void accelerate() { … }
    //@ requires speed != 0;
    //@ ensures speed < old(speed)
    void brake() { … }
}

class Hybrid extends Car {
    int charge;
    //@ invariant charge >= 0;
    //@ requires (charge > 0 || fuel > 0) 
    &&
    !engineOn;
    //@ ensures engineOn;
    void start() { … }
    void accelerate() { … }
    //@ requires speed != 0;
    //@ ensures speed < \old(speed)
    //@ ensures charge > \old(charge)
void brake() { … }

No exemplo acima, Hybrid é a subtipagem comportamental de Car. Neste exemplo, a classe Car possui uma invariante: //@ invariant fuel >= 0;, indicando que a quantidade de combustível do carro não pode ser negativa. A classe Hybrid, como uma subclasse da classe Car, também deve satisfazer esta invariante. Ao mesmo tempo, a classe Hybrid possui um invariante adicional: //@ invariant charge >= 0;, indicando que a quantidade elétrica do veículo híbrido não pode ser negativa. Assim, a classe Hybrid, como uma subclasse, satisfaz os invariantes da classe Car (restrições na quantidade de combustível), bem como seu próprio invariante adicional (restrições na quantidade de eletricidade). Esta é a primeira condição.

Para as outras duas condições, podemos facilmente saber que isso satisfaz que o início do método substituído tenha uma pré-condição mais fraca e o freio do método substituído tenha uma pós-condição mais forte. Essas duas condições nos permitem substituir a classe pai por uma subclasse em qualquer lugar no futuro sem causar erros de programa.

Geralmente, quando a subtipagem comportamental é satisfeita, podemos usar o padrão de método de modelo. O diagrama UML e o código de exemplo do padrão de método de modelo são os seguintes:

 Aqui, a seta oca sólida em UML representa o relacionamento de herança.Implementamos a classe abstrata e realizamos as funções definidas na classe abstrata por meio das classes específicas class1 e class2.

abstract class AbstractOrder {
    public abstract boolean lessThan(int i, int j);
}

class AscendingOrder extends AbstractOrder {
    public boolean lessThan(int i, int j) {
        return i < j;
    }
}

class DescendingOrder extends AbstractOrder {
    public boolean lessThan(int i, int j) {
        return i > j;
    }
}

// ...

static void sort(int[] list, AbstractOrder order) {
    // ...
    boolean mustSwap = order.lessThan(list[j], list[i]);
    // ...
}

O exemplo acima é um exemplo de código de um método de classificação estendido implementado com o padrão de método de modelo. Usamos uma classe base abstrata AbstractOrder. AscendingOrdere DescendingOrderherdam da AbstractOrderclasse, respectivamente, e substituem lessThano método nela. Por herança, a classe AscendingOrdere DescendingOrderobtém implicitamente AbstractOrdertodos os métodos e propriedades da classe. Aqui, nosso foco principal está nas hierarquias de tipos e na reutilização de código.

Padrão de estratégia

Strategy Pattern (Padrão de estratégia) é um padrão de design comportamental que permite alternar entre diferentes algoritmos ou estratégias em tempo de execução, conforme necessário. O modo de estratégia é implementado por delegação , que separa a implementação do algoritmo do objeto que usa o algoritmo, melhorando assim a flexibilidade e escalabilidade do código. De um modo geral, o que pode ser alcançado com o padrão de método de modelo, podemos usar o padrão de estratégia para alcançar e, geralmente, obter mais benefícios.

No padrão de estratégia, vamos abstrair a estratégia em uma interface e, em seguida, usar estratégias diferentes para implementar a interface de estratégia. Aqui, a seta vazada e a linha pontilhada indicam o relacionamento de implementação entre as classes, e aqui precisamos herdar do linha sólida acima. relacionamento para distinguir. No padrão de estratégia, qual estratégia precisamos usar, colocamos diretamente o objeto específico da estratégia no Navegador para uso. Usamos a classe routeStrategy para definir essa estratégia. Através do despacho dinâmico do Java, podemos apontar livremente o ponteiro para diferentes estratégia específica.

 A figura acima é o diagrama de interação do padrão de estratégia.O sistema chama métodos em diferentes objetos específicos de estratégia com base em diferentes estratégias. A vantagem disso é que as políticas podem ser facilmente introduzidas no sistema sem modificar o código do cliente. O padrão Strategy permite que a implementação de algoritmos seja separada do código cliente que os utiliza, melhorando a manutenibilidade e a flexibilidade do código.

Um exemplo de código de um padrão de estratégia:

interface Order {
boolean lessThan(int i, int j);
}
class AscendingOrder implements Order {
public boolean lessThan(int i, int j) { return i < j; }
}
class DescendingOrder implements Order {
public boolean lessThan(int i, int j) { return i > j; }
}
…
static void sort(int[] list, Order order) {
…
boolean mustSwap =
order.lessThan(list[j], list[i]);
…
}

Na verdade, parece que esse código é muito parecido com o anterior, mas na forma de escrever o delegado, usamos uma interface Order. AscendingOrdere DescendingOrderimplementar Ordera interface, respectivamente. Então passamos diretamente a estratégia a ser utilizada ( AscendingOrder或DescendingOrder ) para a variável do tipo Order em sort, de forma que realizamos a troca livre de estratégias de forma bastante flexível e de baixo acoplamento. É equivalente a "delegar" o algoritmo específico de classificação para esse objeto de pedido específico e chamar diretamente outros para usá-lo, independentemente de seus detalhes de implementação específicos. Na delegação, estamos preocupados em definir um conjunto de comportamentos (via Orderinterfaces) e implementá-los individualmente. A delegação enfatiza o comportamento e a composição, não a hierarquia de tipos.

Resumo sobre esses dois métodos:

Herança (Herança) e composição + delegação (Composição + Delegação) são conceitos-chave no projeto orientado a objetos. A herança pode conseguir muita reutilização de código em um relacionamento de acoplamento forte, mas deve ser usada com cuidado. Bons projetos geralmente são mais inclinados a usar composição e delegação, porque eles suportam a reutilização e o encapsulamento de interfaces de programação, ajudam na ocultação de informações e produzem código mais fácil de testar (ao usar herança porque você precisa reescrever o código da classe pai , Muitas vezes é necessário conhecer as informações de implementação específicas da classe pai, o que não é propício para ocultar informações). Embora a herança seja mais conveniente em alguns casos, a delegação deve receber prioridade no design.

Acho que você gosta

Origin blog.csdn.net/weixin_44492824/article/details/130518595
Recomendado
Clasificación