Acoplamento. Melhores Práticas


11

Na sequência deste tópico eu comecei

O Padrão Singleton

Isso me fez pensar sobre como minhas aulas são acopladas e qual a melhor forma de obter acoplamentos soltos. Por favor, tenha em mente que sou um novo programador (quatro meses após o meu primeiro emprego) e essa é realmente a primeira consideração que eu tenho dado a isso, e estou muito interessada em entender o conceito.

Então, o que exatamente constitui acoplamento solto versus acoplamento pesado? No meu atual (e primeiro projeto), estou trabalhando no projeto ac # winforms, em que a seção GUI cria objetos e inscreve seus eventos; quando são acionados, a GUI cria outro objeto (neste exemplo, um datagridview (uma classe que eu criei que envolve um datagridview padrão e adiciona funcionalidade adicional) e o anexa à GUI.É um acoplamento ruim ou bom?

Eu realmente não quero entrar em maus hábitos e começar a programar mal, portanto, eu apreciaria suas respostas.

Respostas:


11

Para tornar seu código fracamente acoplado, aqui estão algumas coisas simples a serem lembradas:

Parte 1:

Tecnicamente conhecido como "Separação de Preocupação". Cada classe tem uma função específica; deve lidar com a lógica de negócios ou lógica de aplicativo. Tente ficar longe da classe que combina as duas responsabilidades. ou seja, uma classe que gerencia dados (de longo prazo) é lógica de aplicativo enquanto uma classe que usa dados é lógica de negócios.

Pessoalmente, refiro-me a isso (no meu próprio mundinho) como create it or use it. Uma classe deve criar um objeto ou usar um objeto que nunca deve fazer as duas coisas.

Parte 2:

Como implementar a separação de preocupações.
Como ponto de partida, existem duas técnicas simples:

Nota: Os padrões de design não são absolutos.
Eles devem ser personalizados para a situação, mas têm um tema subjacente semelhante a todos os aplicativos. Portanto, não olhe para os exemplos abaixo e diga que devo seguir isso rigidamente; estes são apenas exemplos (e levemente artificial nisso).

Injeção de Dependência :

É aqui que você passa um objeto que uma classe usa. O objeto que você passa com base em uma interface para que sua classe saiba o que fazer com ele, mas não precisa saber a implementação real.

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

Aqui injetamos o fluxo em um tokenizador. O tokenizer não sabe que tipo de fluxo é, desde que implemente a interface std :: istream.

Padrão do Localizador de Serviço :

O padrão do localizador de serviço é uma pequena variação na injeção de dependência. Em vez de fornecer um objeto que ele possa usar, você passa um objeto que sabe como localizar (criar) o objeto que deseja usar.

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

Aqui passamos o objeto de aplicativo como um objeto persistente. Quando você executa uma ação de salvar / carregar, ele usa o persistor para criar um objeto que realmente sabe como executar a ação. Nota: Novamente, o persistor é uma interface e você pode fornecer implementações diferentes, dependendo da situação.

Isso é útil quando um potentiallyobjeto exclusivo é necessário toda vez que você instancia uma ação.

Pessoalmente, acho que isso é particularmente útil na escrita de testes de unidade.

Nota dos padrões:

Padrões de design são um assunto enorme em si. Esta não é de forma alguma uma lista exclusiva de padrões que você pode usar para ajudar no acoplamento solto; este é apenas um ponto de partida comum.

Com a experiência, você perceberá que já está usando esses padrões, apenas que não usou seus nomes formais. Ao padronizar seus nomes (e fazer com que todos os aprendam), descobrimos que é fácil e rápido comunicar idéias.


3
@ Darren Young: Obrigado por aceitar. Mas sua pergunta tem apenas três horas. Gostaria de voltar em um dia para verificar se outras pessoas não forneceram respostas melhores.
Martin York

Obrigado pela excelente resposta. Com relação à separação de preocupações .... Eu tenho classes que exigem alguns dados para seu uso e, em seguida, fazemos algo com esses dados. Foi assim que, no passado, eu projetei classes. Portanto, seria melhor criar uma classe que obtenha os dados e os adapte ao formato correto e, em seguida, outra classe para realmente usar os dados?
Darren Young

@ Darren Young: Como em toda a programação, a linha é cinza e embaçada, não bem definida. É difícil dar uma resposta exata sem ler o seu código. Mas quando falo, managing the dataestou me referindo às variáveis ​​(não aos dados reais). Portanto, coisas como ponteiros precisam ser gerenciados para que não vazem. Mas os dados podem ser injetados ou a forma como os dados são recuperados pode ser abstraídos (para que sua classe possa ser reutilizada com diferentes métodos de recuperação de dados). Lamento não poder ser mais preciso.
Martin York

1
@ Darren Young: Como observado por @StuperUser em sua resposta. Não exagere (AKA minutae of loose coupling(ame essa palavra minutae)). O segredo da programação é aprender quando usar as técnicas. O uso excessivo pode levar a um emaranhado de código.
Martin York

@ Martin - obrigado pelo conselho. Eu acho que é aí que estou lutando no momento ..... Costumo me preocupar constantemente com a arquitetura do meu código e também tentar aprender o idioma específico em que estou usando C #. Eu acho que virá com experiência e meu desejo de aprender essas coisas. Agradeço seus comentários.
Darren Young

6

Sou desenvolvedor do ASP.NET, por isso não conheço muito sobre o acoplamento WinForms, mas conheço um pouco dos aplicativos da web de N camadas, assumindo uma arquitetura de aplicativo de três camadas da interface do usuário, domínio, DAL (camada de acesso a dados).

O acoplamento fraco é sobre abstrações.

Como o @MKO afirma que se você pode substituir um assembly por outro (por exemplo, um novo projeto de interface do usuário que usa seu projeto de domínio, um novo DAL que salva em uma planilha em vez de um banco de dados), há um acoplamento solto. Se o seu domínio e o DAL dependem de projetos mais adiante, o acoplamento pode ser mais frouxo.

Um aspecto de algo sendo pouco acoplado é se você pode substituir um objeto por outro que implementa a mesma interface. Não depende do objeto real, mas da descrição abstrata do que ele faz (sua interface).
Acoplamentos fracos, interfaces e Injetores de Dependência (DI) e Inversão de Controle (IoC) são úteis para o aspecto de isolamento do projeto para teste.

Por exemplo, um objeto no projeto de interface do usuário chama um objeto de repositório no projeto de domínio.
Você pode criar um objeto falso que implemente a mesma interface que o repositório usado pelo código em teste e, em seguida, escrever um comportamento especial para testes ( stubs para impedir que o código de produção salve / exclua / seja chamado e zombes que agem como stubs e acompanhem do estado do objeto falso para fins de teste).
Os meios pelos quais o único código de produção que está sendo chamado agora são apenas no seu objeto de interface do usuário, seu teste será somente contra esse método e quaisquer falhas de teste isolarão o defeito desse método.

Além disso, no menu Analisar no VS (dependendo da versão que você possui), existem ferramentas para calcular métricas de código para o seu projeto, uma das quais é o acoplamento de classe, mais informações sobre isso na documentação do MSDN.

Não fique TOO atolados na minutae de baixo acoplamento, porém, se não há nenhuma chance de que as coisas estão a ficar reutilizado (por exemplo, um projeto de domínio com mais de um UI) ea vida do produto é pequena, então o acoplamento fraco torna-se menos de prioridade (ainda será levado em consideração), mas ainda será responsabilidade dos arquitetos / líderes de tecnologia que revisarão seu código.


3

Acoplamento refere-se ao grau de conhecimento direto que uma classe possui de outra . Isso não deve ser interpretado como encapsulamento vs. não-encapsulamento. Não é uma referência ao conhecimento de uma classe sobre os atributos ou implementação de outra classe, mas ao conhecimento dessa outra classe em si. O acoplamento forte ocorre quando uma classe dependente contém um ponteiro diretamente para uma classe concreta que fornece o comportamento necessário. A dependência não pode ser substituída ou sua "assinatura" alterada, sem exigir uma alteração na classe dependente. O acoplamento fraco ocorre quando a classe dependente contém um ponteiro apenas para uma interface, que pode ser implementada por uma ou várias classes concretas.A dependência da classe dependente é de um "contrato" especificado pela interface; uma lista definida de métodos e / ou propriedades que as classes de implementação devem fornecer. Qualquer classe que implemente a interface pode, assim, satisfazer a dependência de uma classe dependente sem precisar alterar a classe. Isso permite extensibilidade no design de software; uma nova classe implementando uma interface pode ser escrita para substituir uma dependência atual em algumas ou todas as situações, sem exigir uma alteração na classe dependente; as classes novas e antigas podem ser trocadas livremente. Um acoplamento forte não permite isso. Ref Link.


3

Veja os 5 princípios do SOLID . Aderindo ao SRP, o ISP e o DIP reduzirão significativamente o acoplamento, sendo o DIP de longe o mais poderoso. É o princípio fundamental do DI já mencionado .

Além disso, vale a pena dar uma olhada no GRASP . É uma mistura estranha entre conceitos abstratos (você achará difícil de implementar a princípio) e padrões concretos (que podem realmente ajudar), mas a beleza é provavelmente a menor das suas preocupações no momento.

E por último, você pode achar esta seção sobre IoC bastante útil, como um ponto de entrada para técnicas comuns.

De fato, encontrei uma pergunta no stackoverflow , onde demonstro a aplicação do SOLID em um problema concreto. Pode ser uma leitura interessante.


1

De acordo com a Wikipedia:

Na computação e no design de sistemas, um sistema fracamente acoplado é aquele em que cada um de seus componentes possui ou utiliza pouco ou nenhum conhecimento das definições de outros componentes separados.

O problema do acoplamento rígido torna difícil fazer alterações. (Muitos autores parecem sugerir que isso causa principalmente problemas durante a manutenção, mas, na minha experiência, também é relevante durante o desenvolvimento inicial.) O que tende a acontecer em sistemas fortemente acoplados é que uma alteração em um módulo no sistema requer alterações adicionais nos módulos aos quais está acoplado. Na maioria das vezes, isso requer mais alterações em outros módulos e assim por diante.

Por outro lado, em um sistema pouco acoplado, as mudanças são relativamente isoladas. Eles são, portanto, menos dispendiosos e podem ser fabricados com maior confiança.

No seu exemplo particular, o material de manipulação de eventos fornece alguma separação entre a GUI e os dados subjacentes. No entanto, parece que existem outras áreas de separação que você poderia explorar. Sem mais detalhes de sua situação específica, é difícil ser específico. No entanto, você pode começar com uma arquitetura de três camadas que se separa:

  • Lógica de armazenamento e recuperação de dados
  • Logíca de negócios
  • Lógica necessária para executar a interface do usuário

Algo a considerar é que, para aplicativos pequenos e únicos com um único desenvolvedor, os benefícios de impor acoplamentos soltos em todos os níveis podem não valer o esforço. Por outro lado, aplicativos maiores e mais complexos com vários desenvolvedores, eles são obrigatórios. Inicialmente, há um custo incorrido na introdução das abstrações e na educação dos desenvolvedores não familiarizados com o código referente à sua arquitetura. A longo prazo, no entanto, o acoplamento solto oferece vantagens que superam os custos.

Se você é sério sobre o design de sistemas com acoplamentos soltos, leia os princípios e padrões de projeto do SOLID.

O importante a perceber, no entanto, é que esses padrões e princípios são exatamente isso - padrões e princípios. Eles não são regras. Isso significa que eles precisam ser aplicados de forma pragmática e inteligente

No que diz respeito aos padrões, é importante entender que não existe uma implementação "correta" única de nenhum dos padrões. Também não são modelos de corte de biscoito para projetar sua própria implementação. Eles estão lá para informar qual a forma que uma boa solução pode ter e fornecer uma linguagem comum para comunicar decisões de design com outros desenvolvedores.

Muito bem sucedida.


1

Use injeção de dependência, padrões de estratégia e eventos. Em geral: leia os padrões de design, eles são sobre acoplamentos soltos e redução de dependências. Eu diria que os eventos são tão fracamente acoplados quanto você obteria, enquanto os padrões de injeção e estratégia de dependência exigem algumas interfaces.

Um bom truque é colocar classes em diferentes bibliotecas / assemblies e fazer com que elas dependam do menor número possível de bibliotecas, o que forçará você a refatorar o uso de menos dependências


4
Preservativo é um termo abstrato de TI ou simplesmente não estou conseguindo o trocadilho?
Darren Young

3
É outro nome para contêiner de injeção de dependência.
Mchl

2
Também é uma ótima maneira de impedir a propagação de vírus. Estamos falando sobre a mesma coisa?
sova

1
O acoplamento pesado geralmente é feito no back
#

1
Definir uma manchete dificilmente está abusando da formatação
Homde

0

Deixe-me fornecer uma visão alternativa. Eu só penso nisso em termos de cada classe ser uma boa API. A ordem em que os métodos são chamados é óbvia. O que eles fazem é óbvio. Você reduziu o número de métodos ao mínimo necessário. Por exemplo,

init, abrir, fechar

versus

setTheFoo, setBar, initX, getConnection, fechar

O primeiro é óbvio e parece uma boa API. O segundo pode causar erros se chamado na ordem errada.

Não me preocupo muito em ter que modificar e recompilar os chamadores. Eu mantenho MUITO código, alguns novos e 15 anos de idade. Eu normalmente quero erros de compilador quando faço alterações. Às vezes, eu quebro uma API de propósito por esse motivo. Isso me dá a chance de considerar as ramificações de cada chamador. Não sou muito fã de injeção de dependência, porque quero rastrear visualmente meu código sem caixas pretas e quero que o compilador capture o maior número possível de erros.

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.