Acoplamento solto no projeto orientado a objetos


16

Estou tentando aprender GRASP e achei isso explicado ( aqui na página 3 ) sobre acoplamento baixo e fiquei muito surpreso quando descobri isso:

Considere o método addTrackpara uma Albumclasse, dois métodos possíveis são:

addTrack( Track t )

e

addTrack( int no, String title, double duration )

Qual método reduz o acoplamento? O segundo, porque a classe que usa a classe Album não precisa conhecer uma classe Track. Em geral, os parâmetros dos métodos devem usar tipos de base (int, char ...) e classes dos pacotes java. *.

Eu tendem a discordar disso; Eu acredito que addTrack(Track t)é melhor do que addTrack(int no, String title, double duration)devido a várias razões:

  1. É sempre melhor para um método o menor número possível de parâmetros (de acordo com o Código Limpo do tio Bob, nenhum ou um de preferência, 2 em alguns casos e 3 em casos especiais; mais de 3 precisam de refatoração - essas são, obviamente, recomendações, não regras de azevinho) .

  2. Se addTracké o método de uma interface, e os requisitos precisam que a Trackdeva ter mais informações (por exemplo, ano ou gênero), então a interface precisa ser alterada e, portanto, o método deve suportar outro parâmetro.

  3. O encapsulamento está quebrado; se addTrackestiver em uma interface, ele não deve conhecer os elementos internos do arquivo Track.

  4. Na verdade, é mais acoplado da segunda maneira, com muitos parâmetros. Suponha que o noparâmetro precise ser alterado de intpara longporque existem mais de MAX_INTtrilhas (ou por qualquer motivo); então Tracko método e o método precisam ser alterados, enquanto se o método for addTrack(Track track)apenas o Trackseria alterado.

Todos os quatro argumentos estão realmente conectados entre si, e alguns deles são consequências de outros.

Qual abordagem é melhor?


2
Esse documento foi elaborado por um professor ou treinador? Com base no URL do link que você forneceu, parece que era para uma classe, embora eu não veja nenhum crédito no documento sobre quem o criou. Se isso fazia parte de uma aula, sugiro que você faça essas perguntas à pessoa que forneceu o documento. A propósito, concordo com o seu raciocínio - parece-me evidente que uma classe de álbuns gostaria de saber inerentemente sobre uma classe de faixa.
Derek

Honestamente, sempre que leio sobre "Boas Práticas", tomo-as ​​com um grão de sal!
AraK 27/06

@Derek Encontrei o documento pesquisando no Google por "exemplos de padrões de compreensão"; Não sei quem o escreveu, mas, como era de uma universidade, acredito que seja confiável. Estou procurando um exemplo com base nas informações fornecidas e ignorando a fonte.
M3th0dman

4
@ m3th0dman "mas como era de uma universidade, acredito que seja confiável." Para mim, porque é de uma universidade, considero não confiável. Não confio em alguém que não tenha trabalhado em projetos de vários anos falando sobre as melhores práticas no desenvolvimento de software.
AraK 27/06

1
@AraK Confiável não significa inquestionável; e é isso que estou fazendo aqui, questionando.
M3th0dman

Respostas:


15

Bem, seus três primeiros pontos são realmente sobre outros princípios além do acoplamento. Você sempre precisa encontrar um equilíbrio entre os princípios de design frequentemente conflitantes.

Seu quarto ponto é sobre o acoplamento, e eu concordo plenamente com você. O acoplamento é sobre o fluxo de dados entre os módulos. O tipo de contêiner no qual os dados fluem é em grande parte irrelevante. Passar uma duração como um duplo em vez de como um campo de a Tracknão evita a necessidade de passá-lo. Os módulos ainda precisam compartilhar a mesma quantidade de dados e ainda têm a mesma quantidade de acoplamento.

Ele também está deixando de considerar todo o acoplamento no sistema como um agregado. Embora a introdução de uma Trackclasse acrescente outra dependência entre dois módulos individuais, ela pode reduzir significativamente o acoplamento do sistema , que é a medida importante aqui.

Por exemplo, considere um botão "Adicionar à lista de reprodução" e um Playlistobjeto. A introdução de um Trackobjeto pode ser considerada para aumentar o acoplamento se você considerar apenas esses dois objetos. Agora você tem três classes interdependentes em vez de duas. No entanto, esse não é o todo do seu sistema. Você também precisa importar a faixa, reproduzi-la, exibi-la etc. A adição de mais uma classe a esse mix é insignificante.

Agora considere a necessidade de adicionar suporte para reproduzir faixas pela rede, em vez de apenas localmente. Você só precisa criar um NetworkTrackobjeto que esteja em conformidade com a mesma interface. Sem o Trackobjeto, você teria que criar funções em qualquer lugar, como:

addNetworkTrack(int no, string title, double duration, URL location)

Isso dobra seu acoplamento efetivamente, exigindo que mesmo os módulos que não se importam com o material específico da rede continuem acompanhando-o, para poder transmiti-lo.

Seu teste de efeito cascata é bom para determinar sua verdadeira quantidade de acoplamento. O que nos preocupa é limitar os lugares que uma mudança afeta.


1
+ O acoplamento a primitivos ainda está acoplando, não importa como fatiado.
23613 JustinCelebrC

+1 por mencionar a opção "Adicionar uma opção de URL / efeito cascata".
user949300

4
Uma leitura interessante sobre isso também seria a discussão do Princípio de Inversão da Dependência no DIP, na natureza, onde o uso de tipos primitivos é realmente visto como um "cheiro" de Obsessão Primitiva com o Objeto de Valor como a correção. Para mim, parece que seria melhor transmitir um objeto Track a um conjunto de tipos primitivos ... E se você quiser evitar a dependência / acoplamento a classes específicas, use interfaces.
Marjan Venema

Resposta aceita devido à boa explicação sobre a diferença entre o acoplamento total do sistema e o acoplamento dos módulos.
M3th0dman

10

Minha recomendação é:

Usar

addTrack( ITrack t )

mas verifique se ITracké uma interface e não uma classe concreta.

O álbum não conhece os elementos internos dos ITrackimplementadores. É apenas acoplado ao contrato definido pelo ITrack.

Eu acho que essa é a solução que gera a menor quantidade de acoplamento.


1
Acredito que o Track seja apenas um simples objeto de transferência de dados / beans, onde apenas tenha campos e getters / setters sobre eles; é necessária uma interface neste caso?
M3th0dman

6
Requeridos? Provavelmente não. Sugestivo, sim. O significado concreto de uma pista pode e evoluirá, mas o que a classe consumidora exige dela provavelmente não o fará.
JustinC

2
@ m3th0dman Sempre dependa de abstrações, não de concreções. Isso se aplica independentemente de Trackser burro ou inteligente. Tracké uma concreção. ITrackinterface é uma abstração. Dessa forma, você poderá ter diferentes tipos de faixas no futuro, desde que cumpram ITrack.
Tulains Córdova

4
Concordo com a ideia, mas perco o prefixo 'eu'. De Clean Code, de Robert Martin, página 24: "O I precedente, tão comum nos maquinários atuais de hoje, é uma distração na melhor das hipóteses e muita informação na pior das hipóteses. Não quero que meus usuários saibam que estou entregando a eles um interface."
Benjamin Brumfield

1
@BenjaminBrumfield Você está certo. Também não gosto do prefixo, mas deixarei a resposta por uma questão de clareza.
Tulains Córdova

4

Eu argumentaria que o segundo método de exemplo provavelmente aumenta o acoplamento, pois provavelmente instancia um objeto Track e o armazena no objeto Album atual. (Como sugerido no meu comentário acima, eu presumo que seja inerente que uma classe Album tenha o conceito de classe Track em algum lugar dentro dela.)

O primeiro método de exemplo assume que uma faixa está sendo instanciada fora da classe Album, portanto, pelo menos, podemos assumir que a instanciação da classe Track não está acoplada à classe Album.

Se as práticas recomendadas sugerissem que nunca tivéssemos uma referência de classe por segunda, toda a programação orientada a objetos seria lançada pela janela.


Não vejo como ter uma referência implícita a outra classe a torna mais acoplada do que ter uma referência explícita. De qualquer maneira, as duas classes são acopladas. Eu acho que é melhor ter o acoplamento explícito, mas não acho que seja "mais" associado de qualquer maneira.
TMN

1
@TMN, o acoplamento extra é como eu implico que o segundo exemplo provavelmente acabaria criando internamente um novo objeto Track. A instanciação do objeto está sendo acoplada a um método que, de outra forma, deveria apenas adicionar um objeto Track a algum tipo de lista no objeto Album (quebrando o Princípio de Responsabilidade Única). Se a maneira como o Track for criado precisar ser alterada, o método addTrack () também precisará ser alterado. Não é assim no primeiro exemplo.
Derek

3

O acoplamento é apenas um dos muitos aspectos a serem obtidos no seu código. Ao reduzir o acoplamento, você não está necessariamente melhorando seu programa. Em geral, essa é uma prática recomendada, mas nesse caso específico, por que não deveria Trackser conhecido?

Ao usar uma Trackclasse a ser transmitida Album, você facilita a leitura do código, mas, mais importante, como você mencionou, está transformando uma lista estática de parâmetros em um objeto dinâmico. Em última análise, isso torna sua interface muito mais dinâmica.

Você menciona que o encapsulamento está quebrado, mas não está. Albumdeve conhecer os elementos internos Tracke, se você não usou um objeto, Albumteria que conhecer todas as informações passadas antes de poder fazer uso da mesma maneira. O chamador também deve conhecer os elementos internos Track, pois deve construir um Trackobjeto, mas o chamador deve conhecer essas informações da mesma forma, se forem passadas diretamente para o método. Em outras palavras, se a vantagem do encapsulamento não é conhecer o conteúdo de um objeto, ele não pode ser usado nesse caso, pois Albumdeve fazer uso da Trackinformação da mesma forma.

Onde você não gostaria de usar Tracké se Trackcontém lógica interna à qual você não gostaria que o chamador tivesse acesso. Em outras palavras, se Albumfosse uma classe que um programador usando sua biblioteca deveria usar, você não gostaria que ele usasse Trackse usasse para dizer, chame um método para persistir no banco de dados. O verdadeiro problema disso reside no fato de a interface estar emaranhada com o modelo.

Para corrigir o problema, seria necessário separar Trackos componentes de interface e os componentes lógicos, criando duas classes separadas. Para o chamador, Tracktorna-se uma classe leve destinada a reter informações e oferecer pequenas otimizações (dados calculados e / ou valores padrão). Por dentro Album, você usaria uma classe nomeada TrackDAOpara executar o trabalho pesado associado ao salvamento das informações Trackno banco de dados.

Claro, isso é apenas um exemplo. Tenho certeza de que esse não é o seu caso e, portanto, fique à vontade para usar sem Trackculpa. Lembre-se de manter o chamador em mente ao criar classes e criar interfaces quando necessário.


3

Ambos estão corretos

addTrack( Track t ) 

é melhor (como você já argumentou) enquanto

addTrack( int no, String title, double duration ) 

é menos acoplado porque o código que usa addTracknão precisa saber que existe uma Trackclasse. A faixa pode ser renomeada, por exemplo, sem a necessidade de atualizar o código de chamada.

Enquanto você está falando sobre um código mais legível / de manutenção, o artigo está falando sobre acoplamento . Um código menos acoplado não é necessariamente mais fácil de implementar e entender.


Veja argumento 4; Não vejo como o segundo seja menos acoplado.
M3th0dman

3

Baixo acoplamento não significa sem acoplamento. Algo, em algum lugar, precisa saber sobre objetos em outras partes da base de código e, quanto mais você reduz a dependência de objetos "personalizados", mais motivos você dá para o código mudar. O que o autor que você menciona está promovendo com a segunda função é menos associado, mas também menos orientado a objetos, o que contraria toda a idéia do GRASP de ser uma metodologia de design orientada a objetos . O ponto principal é como projetar o sistema como uma coleção de objetos e suas interações; evitá-los é como ensiná-lo a dirigir, dizendo que você deveria andar de bicicleta.

Em vez disso, o caminho certo é reduzir a dependência de objetos concretos , que é a teoria do "acoplamento solto". Quanto menos tipos concretos definidos um método precisar conhecer, melhor. Apenas por essa afirmação, a primeira opção é realmente menos acoplada, porque o segundo método que utiliza os tipos mais simples deve conhecer todos esses tipos mais simples. Claro que eles estão embutidos, e o código dentro do método pode ter que se importar, mas a assinatura do método e os chamadores do método definitivamente não se importam . Alterar um desses parâmetros relacionados a uma faixa de áudio conceitual exigirá mais alterações quando elas estiverem separadas e quando estiverem contidas em um objeto Track (que é o ponto dos objetos; encapsulamento).

Indo um passo adiante, se se esperava que o Track fosse substituído por algo que fizesse o mesmo trabalho melhor, talvez uma interface que definisse a funcionalidade necessária estivesse em ordem, um ITrack. Isso poderia permitir implementações diferentes, como "AnalogTrack", "CdTrack" e "Mp3Track", que forneciam informações adicionais mais específicas a esses formatos, além de fornecer a exposição básica de dados do ITrack que representa conceitualmente uma "faixa"; uma sub-parte finita de áudio. O Track também pode ser uma classe base abstrata, mas isso exige que você sempre deseje usar a implementação inerente ao Track; reimplemente-o como BetterTrack e agora você precisa alterar os parâmetros esperados.

Assim, a regra de ouro; programas e seus componentes de código sempre terão motivos para mudar. Você não pode escrever um programa que nunca exija a edição do código que você já escreveu para adicionar algo novo ou modificar seu comportamento. Seu objetivo, em qualquer metodologia (GRASP, SOLID, qualquer outro acrônimo ou palavra-chave que você possa imaginar) é simplesmente identificar as coisas que terão que mudar ao longo do tempo e projetar o sistema para que essas mudanças sejam o mais fácil possível. (traduzido; tocando no menor número de linhas de código e afetando o menor número possível de outras áreas do sistema além do escopo da alteração pretendida possível). Nesse caso, o que provavelmente mudará é que um Track ganhará mais membros de dados que addTrack () pode ou não se importar, não essa faixa será substituída pelo BetterTrack.

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.