Por exemplo, considere que tenho uma classe para outras classes estender:
public class LoginPage {
public String userId;
public String session;
public boolean checkSessionValid() {
}
}
e algumas subclasses:
public class HomePage extends LoginPage {
}
public class EditInfoPage extends LoginPage {
}
De fato, a subclasse não possui métodos para substituir, também não acessaria a HomePage de maneira genérica, ou seja: Eu não faria algo como:
for (int i = 0; i < loginPages.length; i++) {
loginPages[i].doSomething();
}
Eu só quero reutilizar a página de login. Mas de acordo com https://stackoverflow.com/a/53354 , eu devo preferir composição aqui porque não preciso da interface LoginPage, portanto não uso herança aqui:
public class HomePage {
public LoginPage loginPage;
}
public class EditInfoPage {
public LoginPage loginPage;
}
mas o problema vem aqui, na nova versão, o código:
public LoginPage loginPage;
duplica quando uma nova classe é adicionada. E se o LoginPage precisar de setter e getter, mais códigos deverão ser copiados:
public LoginPage loginPage;
private LoginPage getLoginPage() {
return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
this.loginPage = loginPage;
}
Portanto, minha pergunta é: "composição sobre herança" está violando "princípio seco"?
inheritance
composition
dry
ocomfd
fonte
fonte
extends LoginPage
todo o lugar. VERIFICAR MATE!LoginPage
um decorador. Chega de duplicação, apenas um simplespage = new LoginPage(new EditInfoPage())
e pronto. Ou você usa o princípio aberto-fechado para criar o módulo de autenticação que pode ser adicionado a qualquer página dinamicamente. Existem várias maneiras de lidar com a duplicação de código, principalmente envolvendo encontrar uma nova abstração.LoginPage
é provavelmente um nome ruim, o que você deseja garantir é que o usuário seja autenticado enquanto navega nessa página, sendo redirecionado para aLoginPage
mensagem de erro adequada ou mostrada uma mensagem de erro adequada.Respostas:
Er espere, você está preocupado que repetir
em dois lugares viola SECO? Por essa lógica
agora só pode existir em um objeto em toda a base de código. Bleh.
SECA é uma coisa boa a ter em mente, mas vamos lá. Além de
está sendo duplicado em sua alternativa, mesmo sendo anal sobre DRY não fará sentido.
Preocupações DRY válidas tendem a se concentrar no comportamento idêntico necessário em vários locais, sendo definido em vários locais, de modo que a necessidade de alterar esse comportamento acaba exigindo uma alteração em vários locais. Tome decisões em um só lugar e você só precisará alterá-las em um só lugar. Isso não significa que apenas um objeto possa manter uma referência à sua LoginPage.
DRY não deve ser seguido cegamente. Se você está duplicando porque copiar e colar é mais fácil do que pensar em um bom método ou nome de classe, provavelmente está errado.
Mas se você deseja colocar o mesmo código em um local diferente, porque esse local diferente está sujeito a uma responsabilidade diferente e provavelmente precisará mudar de forma independente, provavelmente é aconselhável relaxar a aplicação do DRY e deixar esse comportamento idêntico ter uma identidade diferente . É o mesmo tipo de pensamento que proíbe os números mágicos.
DRY não é apenas sobre a aparência do código. Trata-se de não espalhar os detalhes de uma idéia com repetição irracional, forçando os mantenedores a consertar as coisas usando repetição irracional. É quando você tenta dizer a si mesmo que a repetição irracional é apenas sua convenção de que as coisas estão indo de uma maneira muito ruim.
O que eu acho que você realmente está tentando reclamar é chamado código clichê. Sim, usar composição em vez de herança exige código padrão. Nada é exposto de graça, você precisa escrever um código que o exponha. Com esse clichê, vem a flexibilidade do estado, a capacidade de restringir a interface exposta, de dar nomes diferentes às coisas que são apropriados para seu nível de abstração, bom e indireto, e você está usando o que é composto de fora, não o por dentro, então você está enfrentando sua interface normal.
Mas sim, é muita digitação extra no teclado. Desde que eu possa impedir o problema do ioiô de subir e descer uma pilha de herança enquanto eu leio o código, vale a pena.
Agora não é que eu me recuso a usar herança. Um dos meus usos favoritos é dar novos nomes às exceções:
fonte
int x;
pode não mais do que zero vezes existem em uma base de códigoint a; int b; int x;
, pressionarei para remover 'você'.Um mal-entendido comum com o princípio DRY é que, de alguma forma, está relacionado à não repetição de linhas de código. O princípio DRY é "Todo conhecimento deve ter uma representação única, inequívoca e autorizada dentro de um sistema" . É sobre conhecimento, não código.
LoginPage
sabe como desenhar a página para efetuar login. SeEditInfoPage
soubesse fazer isso, isso seria uma violação. A inclusãoLoginPage
via composição não é de forma alguma uma violação do princípio DRY.O princípio DRY é talvez o princípio mais mal utilizado na engenharia de software e deve sempre ser pensado não como um princípio para não duplicar o código, mas como um princípio para não duplicar o conhecimento abstrato do domínio. Na verdade, em muitos casos, se você aplicar o DRY corretamente, duplicará o código , e isso não é necessariamente uma coisa ruim.
fonte
Resposta curta: sim, sim - em um grau menor e aceitável.
À primeira vista, às vezes a herança pode economizar algumas linhas de código, pois tem o efeito de dizer "minha classe de reutilização conterá todos os métodos e atributos públicos de maneira 1: 1". Portanto, se houver uma lista de 10 métodos em um componente, não será necessário repeti-los no código da classe herdada. Quando, em um cenário de composição, 9 desses 10 métodos devem ser expostos publicamente por meio de um componente de reutilização, é necessário anotar 9 chamadas de delegação e deixar a restante fora, não há maneira de contornar isso.
Por que isso é tolerável? Observe os métodos que são replicados em um cenário de composição - esses métodos delegam exclusivamente chamadas à interface do componente, portanto, sem lógica real.
O coração do princípio DRY é evitar ter dois lugares no código em que as mesmas regras lógicas são codificadas - porque quando essas regras lógicas mudam, no código não-DRY, é fácil adaptar um desses locais e esquecer o outro, o que introduz um erro.
Porém, como delegar chamadas não contém lógica, elas normalmente não estão sujeitas a essa alteração, portanto não causam um problema real ao "preferir composição a herança". E mesmo que a interface do componente seja alterada, o que pode induzir mudanças formais em todas as classes usando o componente, em uma linguagem compilada, o compilador nos dirá quando esquecemos de alterar um dos chamadores.
Uma observação para o seu exemplo: não sei como você
HomePage
e suaEditInfoPage
aparência, mas se eles tiverem funcionalidade de login e umHomePage
(ou umEditInfoPage
) for umLoginPage
, a herança poderá ser a ferramenta correta aqui. Um exemplo menos discutível, em que a composição será a melhor ferramenta de uma maneira mais óbvia, provavelmente tornaria as coisas mais claras.Supondo que
HomePage
nemEditInfoPage
seja umLoginPage
, e se deseja reutilizá-lo, como você escreveu, é provável que exija apenas algumas partes doLoginPage
, não tudo. Se for esse o caso, uma abordagem melhor do que usar a composição da maneira mostrada pode serextrair a parte reutilizável
LoginPage
em um componente próprioreutilize esse componente
HomePage
eEditInfoPage
da mesma maneira que é usado agora dentroLoginPage
Dessa forma, normalmente ficará muito mais claro por que e quando a composição sobre herança é a abordagem correta.
fonte