O que significa quando alguém diz "Encapsular o que varia"?


25

Um dos princípios de POO que me deparei é: - Encapsular o que varia.

Entendo qual é o significado literal da frase, ou seja, oculto o que varia. No entanto, não sei exatamente como isso contribuiria para um design melhor. Alguém pode explicar isso usando um bom exemplo?


Veja en.wikipedia.org/wiki/Encapsulation_(computer_programming) que explica bem. Eu acho que o "o que varia" não está correto, já que você também deve encapsular constantes.
Qwerty_so 3/12/16

I don't know how exactly would it contribute to a better designDetalhes de encapsulamento são sobre acoplamentos frouxos entre o "modelo" e os detalhes de implementação. Quanto menos vinculado o "modelo" aos detalhes da implementação, mais flexível é a solução. E torna mais fácil evoluí-lo. "Abstraia-se dos detalhes".
LAIV

@Laiv Então, "varia" refere-se ao que evolui ao longo do ciclo de vida do seu software ou o que muda durante a execução do seu programa ou de ambos?
Haris Ghauri

2
@HarisGhauri both. Agrupe o que varia. Isole o que varia de forma independente. Suspeite do que você supõe que nunca mudará.
Candied_orange

1
@laiv pensar "abstrato" é um bom ponto. Pode parecer esmagador fazer isso. Em qualquer objeto, você deve ter uma responsabilidade. O bom disso é que você só precisa pensar cuidadosamente sobre isso aqui. Quando os detalhes do restante do problema são de outra pessoa, isso facilita as coisas.
Candied_orange 04/04/19

Respostas:


30

Você pode escrever um código parecido com este:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

ou você pode escrever um código parecido com este:

pet.speak();

Se o que varia for encapsulado, você não precisará se preocupar com isso. Você só se preocupa com o que precisa e o que quer que esteja usando, descobre como fazer o que realmente precisa com base no que varia.

Encapsule o que varia e você não precisa espalhar o código que se importa com o que varia. Você apenas define o animal de estimação como um tipo que sabe falar como esse tipo e, depois disso, pode esquecer o tipo e tratá-lo como um animal de estimação. Você não precisa perguntar qual tipo.

Você pode pensar que o tipo está encapsulado porque é necessário um getter para acessá-lo. Eu não. Getter realmente não encapsula. Eles apenas se esquivam quando alguém quebra seu encapsulamento. Eles são um bom decorador, como um gancho orientado a aspectos, que é mais frequentemente usado como código de depuração. Não importa como você corta, você ainda está expondo o tipo.

Você pode olhar para este exemplo e pensar que estou polimorfismo e encapsulamento conflitantes. Eu não estou. Estou confundindo "o que varia" e "detalhes".

O fato de seu animal de estimação ser um cachorro é um detalhe. Um que pode variar para você. Um que não pode. Mas certamente um que pode variar de pessoa para pessoa. A menos que acreditemos que este software só será usado pelos amantes de cães, é inteligente tratar o cachorro como um detalhe e encapsulá-lo. Dessa forma, algumas partes do sistema ignoram alegremente os cães e não serão afetadas quando nos fundimos com "papagaios somos nós".

Desacoplar, separar e ocultar detalhes do restante do código. Não deixe o conhecimento dos detalhes se espalhar pelo seu sistema e você seguirá "encapsular o que varia" muito bem.


3
Isso é realmente estranho. "Encapsular o que varia" para mim significa ocultar mudanças de estado, por exemplo, nunca ter variáveis ​​globais. Mas você responder faz sentido também, mesmo se ele se sente mais uma resposta para o polimorfismo de encapsulamento :)
David Arno

2
@DavidArno O polimorfismo é uma maneira de fazer isso funcionar. Eu poderia apenas colocar a estrutura if em pet e as coisas ficariam bem aqui graças ao encapsulamento do pet. Mas isso seria apenas mudar a bagunça ao invés de limpá-la.
Candied_orange

1
"Encapsular o que varia" para mim significa ocultar as mudanças de estado . Não, não. Eu gosto do comentário do CO. A resposta de Derick Elkin é mais profunda, leia-a mais de uma vez. Como @JacquesB disse "Este princípio é realmente muito profunda"
radarbob

16

"Varia" aqui significa "pode ​​mudar com o tempo devido a alterações nos requisitos". Este é um princípio básico de design: Separar e isolar partes de código ou dados que talvez precisem ser alterados separadamente no futuro. Se um único requisito mudar, o ideal é exigir apenas que alteremos o código relacionado em um único local. Mas se a base de código for mal projetada, ou seja, altamente interconectada e lógica para o requisito espalhado em muitos lugares, a mudança será difícil e terá um alto risco de causar efeitos inesperados.

Digamos que você tenha um aplicativo que use o cálculo do imposto sobre vendas em muitos lugares. Se a taxa do imposto sobre vendas mudar, o que você prefere:

  • a taxa do imposto sobre vendas é um literal codificado em qualquer lugar do aplicativo em que o imposto sobre vendas é calculado.

  • a taxa do imposto sobre vendas é uma constante global, usada em qualquer lugar do aplicativo em que o imposto sobre vendas é calculado.

  • existe um método único chamado calculateSalesTax(product)qual é o único local em que a taxa de imposto sobre vendas é usada.

  • a taxa de imposto sobre vendas é especificada em um arquivo de configuração ou em um campo de banco de dados.

Como a taxa do imposto sobre vendas pode mudar devido a uma decisão política independente de outro requisito, preferimos tê-la isolada em uma configuração, para que possa ser alterada sem afetar nenhum código. Mas também é concebível que a lógica para calcular o imposto sobre vendas possa mudar, por exemplo, taxas diferentes para produtos diferentes, então também gostamos de ter a lógica de cálculo encapsulada. A constante global pode parecer uma boa ideia, mas é realmente ruim, pois pode incentivar o uso do imposto sobre vendas em diferentes locais do programa, e não em um único local.

Agora considere outra constante, Pi, que também é usada em muitos lugares do código. O mesmo princípio de design é válido? Não, porque Pi não vai mudar. Extraí-lo para um arquivo de configuração ou campo de banco de dados apenas introduz complexidade desnecessária (e tudo o resto é igual, preferimos o código mais simples). Faz sentido transformá-lo em uma constante global, em vez de codificá-lo em vários lugares, para evitar inconsistências e melhorar a legibilidade.

A questão é que, se apenas analisarmos como o programa funciona agora , a taxa do imposto sobre vendas e o Pi são equivalentes, ambos são constantes. Somente quando consideramos o que pode variar no futuro , é que percebemos que precisamos tratá-los de maneira diferente no design.

Na verdade, esse princípio é bastante profundo, porque significa que você precisa olhar além do que a base de código deve fazer hoje e também considerar as forças externas que podem fazer com que ela mude, e até entender as diferentes partes interessadas por trás dos requisitos.


2
Os impostos são um bom exemplo. Os cálculos de leis e impostos podem mudar de um dia para outro. Se você estiver implementando um sistema de declaração de impostos, estará fortemente vinculado a esse tipo de alteração. Também muda de um local para outro (países, províncias, ...)
LAIV

"Pi não vai mudar" me fez rir. É verdade que é improvável que o Pi mude, mas suponha que você não tenha mais permissão para usá-lo? Se algumas pessoas tiverem o que querem, Pi será preterido. Suponha que isso se torne um requisito? Espero que você tenha um feliz dia Tau . Boa resposta BTW. Profundo mesmo.
Candied_orange 07/12/16

14

Ambas as respostas atuais parecem atingir apenas parcialmente a marca e se concentram em exemplos que obscurecem a idéia central. Este também não é (apenas) um princípio de POO, mas um princípio de design de software em geral.

O que está "variando" nesta frase é o código. Christophe está certo ao dizer que geralmente é algo que pode variar, ou seja, você costuma antecipar isso. O objetivo é proteger-se de futuras alterações no código. Isso está intimamente relacionado à programação em uma interface . No entanto, Christophe está incorreto ao limitar isso a "detalhes de implementação". De fato, o valor desse conselho geralmente é devido a alterações nos requisitos .

Isso está indiretamente relacionado ao estado de encapsulamento, que é o que eu acredito que David Arno está pensando. Esse conselho nem sempre (mas geralmente o sugere) sugere um estado de encapsulamento e também se aplica a objetos imutáveis. De fato, apenas nomear constantes é uma forma (muito básica) de encapsular o que varia.

CandiedOrange confunde explicitamente "o que varia" com "detalhes". Isso está apenas parcialmente correto. Concordo que qualquer código que varie seja "detalhes" em algum sentido, mas um "detalhe" pode não variar (a menos que você defina "detalhes" para tornar isso tautológico). Pode haver razões para encapsular detalhes não variáveis, mas esse ditado não é um deles. Grosso modo, se você estava altamente confiante de que "cachorro", "gato" e "pato" seriam os únicos tipos com os quais você precisaria lidar, esse ditado não sugere a refatoração que o CandiedOrange realiza.

Fundindo o exemplo de CandiedOrange em um contexto diferente, suponha que tenhamos uma linguagem processual como C. Se eu tiver algum código que contenha:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

Posso razoavelmente esperar que esse pedaço de código mude no futuro. Eu posso "encapsular" simplesmente definindo um novo procedimento:

void speak(pet) {
  if (pet.type() == dog) {
    pet.bark();
  } else if (pet.type() == cat) {
    pet.meow();
  } else if (pet.type() == duck) {
    pet.quack()
  }
}

e usando esse novo procedimento em vez do bloco de código (ou seja, uma refatoração de "método de extração"). Neste ponto, adicionar um tipo de "vaca" ou o que requer apenas a atualização do speakprocedimento. Obviamente, em um idioma OO, você pode aproveitar o envio dinâmico, como aludido pela resposta de CandiedOrange. Isso acontecerá naturalmente se você acessar petatravés de uma interface. A eliminação da lógica condicional via despacho dinâmico é uma preocupação ortogonal, que foi parte do motivo pelo qual fiz essa versão processual. Também quero enfatizar que isso não requer recursos específicos do OOP. Mesmo em uma linguagem OO, encapsular o que varia não significa necessariamente que uma nova classe ou interface precise ser criada.

Como um exemplo mais arquetípico (que é mais próximo, mas não exatamente do OO), digamos que queremos remover as duplicatas de uma lista. Digamos que implementamos iterando a lista, mantendo o controle dos itens que vimos até agora em outra lista e removendo os itens que vimos. É razoável supor que podemos querer mudar a maneira como mantemos o controle dos itens vistos, por pelo menos por razões de desempenho. O ditado para encapsular o que varia sugere que devemos construir um tipo de dados abstrato para representar o conjunto de itens vistos. Nosso algoritmo agora está definido nesse tipo de dados abstrato Set e, se decidirmos mudar para uma árvore de pesquisa binária, nosso algoritmo não precisará ser alterado ou importado. Em uma linguagem OO, podemos usar uma classe ou interface para capturar esse tipo de dado abstrato. Em um idioma como SML / O '

Para um exemplo orientado a requisitos, diga que você precisa validar algum campo com relação a alguma lógica de negócios. Embora você possa ter requisitos específicos agora, você suspeita fortemente que eles irão evoluir. Você pode encapsular a lógica atual em seu próprio procedimento / função / regra / classe.

Embora essa seja uma preocupação ortogonal que não faz parte de "encapsular o que varia", muitas vezes é natural abstrair, que é parametrizado pela lógica agora encapsulada. Isso normalmente leva a um código mais flexível e permite que a lógica seja alterada substituindo em uma implementação alternativa em vez de modificar a lógica encapsulada.


Oh amarga e doce ironia. Sim, este não é apenas um problema de POO. Você me pegou deixando um detalhe de paradigma de linguagem poluir minha resposta e, com razão, me puniu por "variar" o paradigma.
Candied_orange 07/12/16

"Mesmo em uma linguagem OO, encapsulando o que varia não significa necessariamente uma nova necessidades classe ou interface a ser criadas" - é difícil imaginar uma situação em que não criar uma nova classe ou interface não violaria SRP
taurelas

11

"Encapsular o que varia" refere-se à ocultação de detalhes de implementação que podem mudar e evoluir.

Exemplo:

Por exemplo, suponha que a classe Coursemonitore o Studentsque pode registrar (). Você pode implementá-lo com LinkedListae expor o contêiner para permitir a iteração:

class Course { 
    public LinkedList<Student> Atendees; 
    public bool register (Student s);  
    ...
}

Mas isso não é uma boa ideia:

  • Primeiro, as pessoas podem não ter um bom comportamento e usá-lo como autoatendimento, adicionando diretamente os alunos à lista, sem passar pelo método register ().
  • Mas ainda mais irritante: isso cria uma dependência do "código de uso" para os detalhes de implementação internos da classe usada. Isso pode impedir futuras evoluções da classe, por exemplo, se você preferir usar uma matriz, um vetor, um mapa com o número do assento ou sua própria estrutura de dados persistente.

Se você encapsular o que varia (ou melhor, o que pode variar), mantenha a liberdade de que o código de uso e a classe encapsulada evoluam mutuamente por conta própria. É por isso que é um princípio importante no POO.

Leitura adicional:

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.