![translation](https://cdn.durumis.com/common/trans.png)
Esta é uma postagem traduzida por IA.
Selecionar idioma
Texto resumido pela IA durumis
- Aborda como implementar um sistema de reserva de ingressos para filmes usando programação orientada a objetos, explicando conceitos como objetos, classes, colaboração e interfaces.
- Em particular, apresenta em detalhes como usar herança, polimorfismo e abstração para implementar políticas e condições de desconto, mostrando exemplos de como implementar as funções principais do sistema de reserva de ingressos para filmes.
- Analisa e compara as diferenças entre herança e composição, destacando os benefícios da composição para o reaproveitamento de código e a importância da abstração para um design flexível.
Sistema de reserva de bilhetes de cinema
Analisando os requisitos
- Pretende-se implementar um sistema online de reserva de bilhetes de cinema.
- O filme representa as informações básicas sobre o filme.
- Título, duração, informações sobre preços, etc.
- A exibição representa o evento real em que o público assiste ao filme.
- Data, hora e número de exibição
- As pessoas reservam bilhetes de cinema, mas na verdade, deveriam reservar exibiçõesde um filme.
- Condições de desconto
- Se o preço é descontado
- Condição de ordem: use o número de exibição para determinar se há desconto.
- Condição de período: use a hora de início da exibição do filme para determinar se há desconto.
- Política de desconto
- Determina a taxa de desconto.
- Política de desconto de valor: desconto de um valor fixo da tarifa de reserva.
- Política de desconto percentual: desconto de uma porcentagem do preço total.
- Um filme pode ter uma política de desconto ou nenhuma, e as condições de desconto podem ser combinadas.
- Se uma política de desconto estiver em vigor, mas as condições de desconto não forem satisfeitas, ou se não houver política de desconto em vigor, a tarifa não será descontada.
Em direção à programação orientada a objetos
Cooperação, objetos, classes
- A orientação a objetos é orientada a objetos.
- Depois de determinar a classe, não devemos pensar sobre quais atributos e métodos a classe precisa.
- Devemos decidir quais objetos têm quais estados e comportamentos.
- Devemos ver os objetos não como entidades independentes, mas como membros de uma comunidade colaborativa.
Estrutura do programa seguindo a estrutura do domínio
- O domínio refere-se à área em que os usuários usam o programa para resolver um problema.
- Em geral, o nome da classe deve ser o mesmo ou pelo menos semelhante ao nome do conceito de domínio correspondente.
- O relacionamento entre as classes também deve ser o mais semelhante possível ao relacionamento estabelecido entre os conceitos de domínio, para tornar a estrutura do programa mais fácil de entender e prever.
Implementando classes
- Uma classe é dividida em interno e externo, e para projetar uma classe excelente, devemos decidir quais partes tornar públicas e quais partes ocultar.
- Os atributos de um objeto são privados e os métodos necessários para alterar o estado interno são públicos.
- A separação entre o interior e o exterior de uma classe garante a autonomia do objeto, fornecendo aos programadores a liberdade de implementação.
Objeto autônomo
- Um objeto deve ser um objeto autônomo com estado e comportamento.
- Agrupar dados e funções dentro de um objeto é chamado de encapsulamento.
- O controle de acesso deve reduzir a interferência externa para que o objeto possa determinar suas próprias ações.
- O princípio de separação de interface e implementação é um princípio essencial para a programação orientada a objetos.
- Interface pública: a parte acessível de fora.
- Implementação: a parte acessível apenas internamente.
Liberdade do programador
- O papel do programador é dividido em programador de classe e programador cliente.
- O programador de classe adiciona um novo tipo de dados.
- O programador cliente usa o tipo de dados adicionado pelo programador de classe.
- Ocultar implementação
- O programador de classe pode ocultar a implementação interna fornecendo apenas as partes necessárias ao programador cliente.
- O programador cliente precisa apenas conhecer a interface, o que reduz a quantidade de conhecimento necessária.
Uma comunidade de objetos colaborativos
- Ao expressar dinheiro, é melhor embrulhá-lo como um objeto, como Money, em vez de simplesmente declarar uma variável do tipo Long. O uso de objetos torna a comunicação mais clara e permite que operações redundantes sejam executadas em um único local.
- A interação que ocorre entre os objetos para implementar uma determinada função do sistema é chamada de colaboração.
Uma breve história sobre colaboração
- A única maneira de um objeto interagir com outro é enviar ou receber mensagens.
- Ele tem seus próprios métodos para lidar com mensagens recebidas.
- É importante diferenciar mensagens e métodos. O conceito de polimorfismo se origina aqui.
Obtendo a tarifa de desconto
Iniciando a colaboração para calcular a tarifa de desconto
- A classe Movie não contém a lógica detalhada sobre a política de desconto e a delegou para a interface DiscountPolicy. Herança, polimorfismo e abstração são realmente importantes.
Política de desconto e condições de desconto
Herança e polimorfismo
Dependência de tempo de compilação e dependência de tempo de execução
- A dependência do código e a dependência do tempo de execução podem ser diferentes.
- Quanto maior a diferença entre os dois, mais difícil será entender o código, mas mais flexível e expansível será o código.
Programação por diferença
- A herança permite adicionar novas classes com base em classes existentes de forma rápida e fácil, e pode reutilizar a implementação da classe pai.
- O método de criar uma nova classe adicionando apenas as partes diferentes da classe pai é chamado de programação por diferença.
Herança e interface
- A herança faz com que a classe filha herde todas as interfaces fornecidas pela classe pai.
- A interface define uma lista de mensagens que um objeto pode entender.
public class Movie {
public Money calculateMovieFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
}
- Movie está enviando uma mensagem para DiscountPolicy para calcular o valor do desconto. Do ponto de vista do Movie, não importa qual instância de classe responde, contanto que ela responda com sucesso.
- Portanto, tanto AmountDiscountPolicy quanto PercentDiscountPolicy podem colaborar com Movie em nome de DiscountPolicy.
- Essa capacidade da classe filha substituir a classe pai é chamada de conversão para cima. Isso ocorre porque parece que a classe filha é automaticamente convertida para o tipo de classe pai.
Polimorfismo
- O polimorfismo é a capacidade de responder de forma diferente a uma mesma mensagem, dependendo do tipo do objeto.
- Movie envia a mesma mensagem, mas qual método será realmente executado depende da classe do objeto que recebe a mensagem.
- O polimorfismo se baseia no fato de que a dependência de tempo de compilação e a dependência de tempo de execução do programa podem ser diferentes.
- Como o polimorfismo determina o método a ser executado em tempo de execução, ele é chamado de ligação tardia ou ligação dinâmica.
Interface e polimorfismo
- Se você não precisar compartilhar a implementação e apenas quiser compartilhar a interface, pode usar uma interface em vez de uma classe abstrata.
Abstração e flexibilidade
O poder da abstração
- Vantagens da abstração
- Podemos descrever as políticas de requisitos em alto nível observando apenas a hierarquia de abstração.
- Torna o design mais flexível.
Design flexível
- A abstração impede que o design seja acoplado a situações específicas, permitindo um design flexível.
Compensação de classes abstratas e interfaces
public abstract class DiscountPolicy {
private List conditions;
public DiscountPolicy(DiscountCondition... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for (DiscountCondition condition : conditions) {
if (condition.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountAmount(Screening screening);
}
public class NoneDiscountPolicy extends DiscountPolicy {
@Override
protected Money getDiscountAmount(Screening screening) {
return Money.ZERO;
}
- No momento, NoneDiscountPolicy na verdade não é necessário porque o método calculateDiscountAmount() da DiscountPolicy retorna 0 se não houver nenhuma condição de desconto, então não há problema. No entanto, foi adicionado porque os usuários devem inserir uma política None sem desconto como um argumento para Movie.
- Portanto, para melhor compreensão, é melhor corrigir como abaixo.
public interface DiscountPolicy {
Money calculdateDiscountAmount(Screening screening);
}
public abstract class DefaultDiscountPolicy implements DiscountPolicy {
private List conditions;
public DefaultDiscountPolicy(DiscountCondition... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for (DiscountCondition condition : conditions) {
if (condition.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountAmount(Screening screening);
}
public class NoneDiscountPolicy implements DiscountPolicy {
@Override
public Money calculateDiscountAmount(Screening screening) {
return Money.ZERO;
}
}
public class PercentDiscountPolicy extends DefaultDiscountPolicy {
private double percent;
public PercentDiscountPolicy(double percent, DiscountCondition... conditions) {
super(conditions);
this.percent = percent;
}
@Override
protected Money getDiscountAmount(Screening screening) {
return screening.getMovieFee().times(percent);
}
}
public class AmountDiscountPolicy extends DefaultDiscountPolicy {
private Money discountAmount;
public AmountDiscountPolicy(Money discountAmount, DiscountCondition... conditions) {
super(conditions);
this.discountAmount = discountAmount;
}
@Override
protected Money getDiscountAmount(Screening screening) {
return discountAmount;
}
- Isso faz com que DiscountPolicy seja uma interface abstrata e a divide em sua implementação, NoneDiscountPolicy, e DefaultDiscountPolicy, que representa uma política de desconto geral.
- Você pode pensar que adicionar uma interface apenas para NoneDiscountPolicy é ineficiente em relação ao aumento da complexidade.
- Devemos reconhecer que tudo relacionado à implementação é uma compensação. Ou seja, cada código que você escreve deve ter um bom motivo.
Reutilização de código
- A herança pode ser usada para reutilizar código.
- No entanto, é melhor usar composição em vez de herança para reutilizar código.
- A maneira atual pela qual Movie está reutilizando o código de DiscountPolicy é composição.
- Se Movie for a classe pai e for dividida em AmountDiscountMovie e PercentDiscountMovie, isso seria considerado um uso de herança.
Herança
- Desvantagens da herança
- Viola o encapsulamento.
- Você deve conhecer a estrutura interna da classe pai.
- Há uma alta probabilidade de a classe filha precisar ser alterada quando a classe pai for alterada.
- Torna o design menos flexível.
- O relacionamento entre a classe pai e a classe filha é determinado em tempo de compilação.
- É impossível alterar o tipo de objeto em tempo de execução.
- No entanto, com composição, você pode substituir instâncias em tempo de execução usando um método como changeDiscountPolicy().
- Viola o encapsulamento.
Composição
- O método de reutilizar código apenas por meio de mensagens definidas na interface é chamado de composição.
- Permite a implementação de encapsulamento e a manutenção de um acoplamento fraco.
- Quando você usa uma interface para polimorfismo, você deve combinar herança e composição.
- Por exemplo, a interface DiscountPolicy deve ser usada com herança para implementar suas subimplementações.
Fonte
- Objetos