[O]pen/Closed Principle (Encapsulamento, classes fechadas para modificação mas abertas para extensão)
[L]iskov Substitution Principle (Herança vs Composição, todas as ações devem ter pré-condições e pós condições)
[I]nterface Segregation Principle (A interface deve prover para as filhas, somente com coisas que elas precisam, exemplo: criar interfaces distintas para que seja possível extende-las separadamente)
[D]ependency Inversion Principle (Depender sempre da classe mais estável(Abstração), Abstrações devem depender de abstrações, e implementação deve depender de abstração, exemplo interfaces).
Coesão
Refere-se ao princípio da responsabilidade única, cada classe deve ter a sua responsabilidade(Single Responsibility Principle(SRP))
Vantagem: Facilidade na manutenção.
Acoplamento
Refere-se a dependência e significa o quanto uma classe depende da outra para funcionar.
Como resolver o problema de acoplamento?
Utilizando interfaces(Polimorfismo) que respeitem o SRP, ajuda a desacoplar as funcionalidades diminiundo a complexidade ciclomática.
Encapsulamento
Encapsular é esconder os detalhes da implementação dentro da classe.
Princípio "Tell, Don't Ask'", refere-se a não pedir para acessar determinado atributo e pedir para ele fazer determinada ação, exemplo getListaXpto, e sim criar métodos para fazerem determinadas ações, daí o chamador realiza a ação através destes métodos e não diretamente no atributo da classe.
The Law Demeter
Heurística referente ao encapsulamento dos dados de uma classe, onde não deve-se passar informações para tais atributos utilizando o getter e muito menos o setter evitando a cadeia a.getB().getC().getD().acao(). É necessario criar um método para realizar tal ação.
Como descobrir se um código está encapsulado ou não? respondendo as seguinte pergunta.
Consigo saber COMO a classe está implementando uma regra de negócio? a resposta deve ser não(a implementação deve ficar escondida).
Herança: quando uma classe estende de uma outra e herda o comportamento desta classe.
em cada método que a classe filha herdou e sobrescreveu, lembrar que as pré-condições não podem ser apertadas, e as pós-condições não podem serem afrouxadas(a sobrescrita pela filha não deve divergir da classe pai).
Como resolver um problema quando se está usando herança e esta não atende a necessidade? com a composição.
Composição: O uso de composição é para reaproveitar comportamentos, quando utilizado um componente(classe) como sendo atributo dentro de outra classe ao invés de usar a herança.
Exemplo, quando utilizado um List não devemos expor os métodos get e set, devemos criar determinados métodos dentro da própria classe que acessam o atributo para realizar o que se deseja.
Injeção de dependência
o principal problema é que não se tem a rastreabilidade, ficam escondidas(grafo profundo).
Como resolver o problema de Coesão e Acoplamento
Inversão de Controle e utilizar o padrão de injeção de dependência, para diminuir o acoplamento. Basicamente seria criar interface para as classes e assim o chamador apontaria para as interfaces e não para as classes concretas.
Com Abstrações e Polimorfismo. Através deles é possível ter classes coesas e juntá-las de forma com que o acoplamento não seja um problema.
GOF - Design Patterns
Identificação: qual o problema? solução possível(Design Pattern)?
Creational Design patterns:
São padrões de design que lidam com mecanismos de criação de objetos, tentando criar objetos de maneira adequada à situação. A forma básica de criação de objeto pode resultar em problemas de design ou adicionar complexidade ao design. Os padrões de design criacional resolvem esse problema controlando a criação desse objeto.
Definem a melhor maneira possível de criar um objeto, considerando a reutilização e a mutabilidade. Isso descreve a melhor maneira de lidar com a instanciação. A codificação embutida da instanciação real é uma armadilha e deve ser evitada se a reutilização e a troca forem desejadas.
Abstract Factory
Builder
Factory Method
Prototype
Singleton
Structural Design patterns:
São úteis para definir o relacionamento entre os objetos usando herança ou composição para estruturas maiores de um aplicativo.
Mostram como colar diferentes partes de um sistema de maneira flexível e extensível.
Ajudam a garantir que, quando uma das partes é alterada, toda a estrutura não precisa ser alterada também.
Adapter
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
Behavior Design patterns:
Se preocupam com o comportamento em tempo de execução do programa, como padrões de modelo dinâmico.
Definem as funções que os objetos assumem e a maneira como esses objetos interagem entre si.
O objetivo é garantir o baixo acoplamento.
Caracterizam as maneiras pelas quais classes ou objetos interagem e distribuem responsabilidades.
Strategy
Chain Of Responsibility
Template Method
Command
Interpreter
Iterator
Mediator
Memento
Observer
State
Visitor
Abstract Factory
- colocar aqui a descrição
Builder
Separa a construção complexa do objeto da sua representação para que o mesmo processo de construção possa criar diferentes representações.
Quando usar o Builder?
Sempre que tivermos um objeto complexo de ser criado, que possui diversos atributos, ou que possui uma lógica de criação complicada, podemos esconder tudo isso em um Builder.
Além de centralizar o código de criação e facilitar a manutenção, ainda facilitamos a vida das classes que precisam criar essa classe complexa
Factory
Factory é usado quando temos que isolar o processo de criação de um objeto em um único lugar. Essa factory pode descobrir como criar o objeto dentro dela própria, mas geralmente ela não precisa de muitas informações para criar o objeto.
Quando usar Factory?
*
Prototype
- colocar aqui a descrição
Adapter e Bridge
Adapter: Faça com que uma classe pareça suportar uma interface familiar que ela realmente não suporta. Dessa forma, o código existente pode alavancar novas classes desconhecidas como se fossem classes existentes e familiares, eliminando a necessidade de refatorar o código existente para acomodar as novas classes.
Quando usar Adapter:
Quando usamos varios métodos estáticos por exemplo do próprio java, Calendar.algumacoisa(), criamos uma interface e uma classe concreta para implementar a lógica.
Bridge: Para desacoplar subsistemas de forma que qualquer um deles possa mudar radicalmente sem afetar nenhum código do outro, coloque um conjunto de interfaces entre dois subsistemas e código para essas interfaces.
Quando usar Bridge:
Quando queremos chamar um outro sistema, fazemos uma ponte entre eles através de uma interface e uma classe concreta que implementa a logica da chamada.
Detalhe: no exemplo Mapa m = new GoogleMaps(); Podemos fazer uso de uma Factory, por exemplo, que nos devolve a Bridge esperada naquele momento.
Observação:
A diferença é semântica. A ideia da Bridge é justamente ser uma ponte em dois mundos/sistemas.
A ideia do Adapter é esconder alguma "sujeira", ou adaptar algo que é diferente e não bate com o sistema atual.
Composite
- colocar aqui a descrição
Decorator
Serve para anexar responsabildiades adicionais e modificar a funcionalidade dinamicamente.
Quando usar o Decorator?
Quando queremos adicionar responsabilidade dinamicamente em determinado Objeto sem afetar o Objeto original.
Quando temos comportamentos que podem ser compostos por comportamentos de outras classes envolvidas em uma mesma hierarquia.
Exemplo: Imagine que estamos implementando uma sequência de filtros. Esses filtros precisam eliminar diversas faturas de uma lista, de acordo com algumas regras de negócio: faturas menores que 2000 devem ser eliminadas, faturas maiores do que 8000 devem ser eliminadas, faturas entre 3000 e 4500 que foram emitidas no estado de São Paulo devem ser eliminadas, e assim por diante. Uma implementação procedural produziria uma sequência de ifs enorme para verificar todas essas condições.
Flyweight
Este padrão é usado para reduzir o uso de memória minimizando a quantidade instâncias de objetos e reutilizando-as.
Factory vs Flyweight:
Uma Factory instancia uma classe que é importante/complexa, e seu processo de criação deve ser isolado.
Um Flyweight serve para quando temos muitas instâncias do mesmo objeto andando pelo sistema, e precisamos economizar. Para tal, o Flyweight faz uso de uma fábrica modificada, que guarda essas instâncias.
Sigleton vs Flyweight:
A ideia de ambos é garantir que existam apenas uma única referência para o objeto ao longo do programa.
A diferença é que o Flyweight garante que existam apenas uma única instância de vários elementos. É um "singleton maior".
A própria JVM faz uso de um Flyweight internamente. Quando você declara um "int", e repete o mesmo valor de "int" em vários lugares, ela sempre devolve a mesma instância desse número. É um bom exemplo de implementação do padrão.
Quando usar o Flyweight?
Quando precisa ter varias instâncias de uma mesma classe.
Exemplo:
Proxy
- colocar aqui a descrição
Facade
Fornece uma única interface por meio da qual todas as classes em um subsistema complexo são manipuladas. O Facade permite que você trate um subsistema complexo como se fosse um único objeto granulado com uma interface simples e fácil de usar.
Strategy
É um padrão que permite mudar o comportamento do algorítimo em tempo de execução.
Define uma estratégia para executar algum algoritmo. Uma família de classes intercambiáveis, uma para cada algoritmo, implementa o entrelaçamento.
Quando usar Strategy? quando temos um conjunto de algoritmos similares, e precisamos alternar entre eles em diferentes pedaços da aplicação.
Motivação: segregar diversos algoritmos para uma ação, resultando na possibilidade de vários ifs(separar as regras(ifs) em classes).
Chain Of Responsibility
Motivação: A intenção deste padrão é evitar o acoplamento do remetente de uma solicitação ao seu receptor, ao dar a mais de um objeto a oportunidade de tratar essa solicitação. Encadear os objetos receptores, passando a solicitação ao longo da cadeia até que um objeto a trate.
A delegação das solicitações pode formar uma árvore de recursão, com um mecanismo especial para inserção de novos receptores no final da cadeia existente.
Diminui o acoplamento por evitar a associação explícita do remetente de uma solicitação ao seu receptor e dar a mais de um objeto a oportunidade de tratar a solicitação.
Quando usar o Chain of Responsibility? quando temos vários comandos a serem executados em sequência, e sabemos também qual o próximo cenário que deve ser validado, caso o anterior não satisfaça a condição(substitui os if's).
pode ser utilizado com o parttern Template Method
Template Method
Motivação: Reaproveitar trechos de códigos comuns, evitando duplicações. Define o esqueleto de um algoritmo, adiando algumas etapas para as subclasses. O Método do Modelo permite que as subclasses redefinam certas etapas de um algoritmo sem alterar a estrutura do algoritmo.
Que problema ele resolve? é normalmente usado em estruturas de aplicativos baseadas em derivação fornecendo um conjunto de superclasses que fazem 90% do trabalho adiando operações específicas de aplicativos para métodos abstratos.
Quando usar o Template Method? quando se tem classes com comportamentos pareceidos.
Exemplo: Imagine que temos uma série de algoritmos matemáticos a serem implementados. Todos eles são bem parecidos, possuem a mesma estrutura. As variações são mínimas, por exemplo, um deles deve iterar até o fim da lista, enquanto o outro deve iterar até a metade dela.
pode ser utilizado com o parttern Chain Of Responsibility
Command
Encapsular uma unidade de trabalho(comando) em um Objeto, assim pode-se enfileirar/registrar os comandos a serem executados em sequência.
Quando usar o Command ?
Quando temos que separar os comandos que serão executados do objeto que ele pertence. Um bom exemplo disso é o uso de filas de trabalho, adicionamos a uma lista de comandos e no final chamamos um método que vai processar todos estes comandos.
Observação:
Qual a diferença entre Command e Strategy?
A ideia do Command é abstrair um comando que deve ser executado, pois não é possível executá-lo naquele momento (pois precisamos por em uma fila ou coisa do tipo).
Já no Strategy, a ideia é que você tenha uma estratégia (um algoritmo) para resolver um problema.
Podemos usar Memento para restaurar estados de objetos que foram alterados por um Command. Podemos compor comandos, usando Composite.
Interpreter
Implemente um interpretador para uma linguagem, primeiro definindo uma gramática formal para essa linguagem e, em seguida, implementando essa gramática com uma hierarquia de classes (uma subclasse por produção).
Quando usar o Interpreter?
O padrão Interpreter é geralmente útil para interpretar DSLs.
Iterator
- colocar aqui a descrição
Mediator
- colocar aqui a descrição
Memento
Um memento é um objeto que armazena um instantâneo do estado interno de outro objeto.
Encapsule o estado de um objeto de forma que nenhuma entidade externa possa saber como o objeto está estruturado. Um objeto externo pode armazenar ou restaurar o estado de um objeto sem violar a integridade do objeto.
Quando usar o Memento?
Quando você deseja criar instantâneos de um estado para um objeto.
Quando você precisa desfazer / refazer recursos.
Observer
Quando usar o Observer?
Quando o acoplamento da classe está crescendo, ou quando há diversas ações diferentes a serem executadas após um determinado processo.
Permite que diversas ações sejam executadas de forma transparente à classe principal, reduzindo o acoplamento entre essas ações, facilitando a manutenção e evolução do código.
Exemplo: Imagine que você precise avisar 3 sistemas externos (auditoria, financeiro, e agências), assim que uma conta bancária receber um depósito.
State
Permite alterar o comportamento do objeto quando seu estado interno muda. Deixando as subclasses mudarem o estado atual em tempo de execução conforme necessário.
Quando usar o State?
quando queremos representar diferentes estados de um marcadorStatus.
Exemplo: Um Contrato pode sofrer tipos de alterações, descontos, ajustes enquanto está EM ANDAMENTO. O mesmo pode acontecer quando ele está FALTANDO ASSINATURA DO CLIENTE. Mas, após ASSINADO, o contrato não pode mais sofrer alterações.
Visitor
Adicione operações a um objeto "host" fornecendo uma maneira para um visitante - um objeto que encapsula um algoritmo - acessar o estado interno do objeto host. 1YPically, esse padrão é usado para interagir com elementos de uma estrutura agregada. O visitante se move de um objeto para outro dentro do agregado.
Quando usar o Visitor?
Quando queremos adicionar métodos efetivamente a uma classe sem a necessidade de derivar classes. Os Visitors também podem coletar informações ou realizar operações em todos os elementos de alguma agregação.
Por exemplo, um Visitor pode testar a consistência de todos os elementos de uma agregação.
Considerações
Podemos utilizar padrões em conjunto dependendo da necessidade e tirar maior proveito deles.
Facade e Singleton são padrões de uso mais legados e são evitados de usar pois fazem uso de um variável global que percorre por todo sistema.