O acoplamento com seqüências de caracteres é "mais flexível" do que com métodos de classe?


18

Estou iniciando um projeto de grupo escolar em Java, usando o Swing. É uma GUI simples no aplicativo de desktop do banco de dados.

O professor nos deu o código do projeto do ano passado para que pudéssemos ver como ele faz as coisas. Minha impressão inicial é que o código é muito mais complicado do que deveria ser, mas imagino que os programadores pensem isso frequentemente quando analisam o código que não escreveram apenas.

Espero encontrar razões pelas quais o sistema dele é bom ou ruim. (Perguntei ao professor e ele disse que veria mais tarde por que é melhor, o que não me satisfaz.)

Basicamente, para evitar qualquer acoplamento entre seus objetos persistentes, modelos (lógica de negócios) e visualizações, tudo é feito com seqüências de caracteres. Os objetos persistentes que são armazenados no banco de dados são hashtables de strings, e os modelos e visualizações "assinam" um ao outro, fornecendo chaves de string para os "eventos" nos quais eles assinam.

Quando um evento é acionado, a visualização ou modelo envia uma sequência para todos os assinantes que decidem o que fazer para esse evento. Por exemplo, em um dos métodos de ouvinte de ação de visualizações (acredito que isso apenas define o bicycleMakeField no objeto persistente):

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

Essa chamada finalmente chega a esse método no modelo Veículo:

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

O professor diz que essa maneira de fazer as coisas é mais extensível e sustentável do que ter a visão simplesmente chamar um método em um objeto de lógica de negócios. Ele diz que não há acoplamento entre as visões e os modelos porque eles não sabem da existência um do outro.

Eu acho que esse é um tipo pior de acoplamento, porque a visão e o modelo precisam usar as mesmas strings para funcionar. Se você remover uma exibição ou modelo ou digitar um erro de seqüência de caracteres, não haverá erros de compilação. Também torna o código muito mais longo do que eu acho que precisa ser.

Quero discutir isso com ele, mas ele usa sua experiência no setor para combater qualquer argumento que eu, o estudante inexperiente, possa apresentar. Há alguma vantagem em sua abordagem que estou perdendo?


Para esclarecer, quero comparar a abordagem acima, com a visão obviamente acoplada ao modelo. Por exemplo, você pode passar o objeto de modelo do veículo para a visualização e, em seguida, para alterar a "marca" do veículo, faça o seguinte:

vehicle.make = bicycleMakeField.getText();

Isso reduziria 15 linhas de código atualmente usadas SOMENTE para definir a marca do veículo em um só lugar, para uma única linha de código legível. (E como esse tipo de operação é realizado centenas de vezes em todo o aplicativo, acho que seria uma grande vitória em termos de legibilidade e segurança.)


Atualizar

Meu líder de equipe e eu reestruturamos a estrutura da maneira que queríamos usando digitação estática, informamos o professor e acabamos dando uma demonstração a ele. Ele é generoso o suficiente para nos deixar usar nossa estrutura, desde que não peça ajuda a ele e se pudermos manter o resto de nossa equipe em dia - o que parece justo para nós.


12
OT. Eu acho que seu professor deve se atualizar e não usar código antigo. Na academia, não há razão para usar uma versão Java a partir de 2006, quando é 2012.
Farmor

3
Mantenha-nos informados quando você receber a resposta dele.
Jeffo

4
Independentemente da qualidade do código ou da sabedoria da técnica, você deve soltar a palavra "mágica" da descrição das strings usadas como identificadores. "Cordas mágicas" implica que o sistema não é lógico e vai colorir sua visão e envenenar qualquer discussão que você tenha com seu instrutor. Palavras como "chaves", "nomes" ou "identificadores" são mais neutras, talvez mais descritivas e com maior probabilidade de resultar em uma conversa útil.
Caleb

1
Verdade. Eu não os chamei de cordas mágicas enquanto conversava com ele, mas você está certo, não há necessidade de fazer parecer pior do que é.
Philip

2
A abordagem descrita pelo professor (notificar os ouvintes por meio de eventos nomeados como strings) pode ser boa. Na verdade, enums podem fazer mais sentido, mas esse não é o maior problema. O problema real que vejo aqui é que o objeto que recebe a notificação é o próprio modelo e, em seguida, você deve despachar manualmente com base no tipo de evento. Em um idioma em que as funções são de primeira classe, você registraria uma função , não um objeto em um evento. Em Java, eu acho que deve-se usar algum tipo de objeto envolvendo uma única função
Andrea

Respostas:


32

A abordagem que seu professor propõe é melhor descrita como tipicamente rigorosa e está errada em quase todos os níveis.

O acoplamento é melhor reduzido pela inversão de dependência , que o faz por um despacho polimórfico, em vez de conectar vários casos.


3
Você escreve "quase todos os níveis", mas não escreveu por que não é (na sua opinião) um caso adequado neste exemplo. Seria interessante saber.
hakre

1
@ hakre: Não há casos adequados, pelo menos não em Java. Eu disse, "está errado em quase todos os níveis", porque é melhor do que não usar nenhum indireção e apenas jogar todo o código em um só lugar. No entanto, o uso de constantes de seqüência mágica (global) como base para um envio manual é apenas uma maneira complicada de usar objetos globais no final.
back2dos

Portanto, seu argumento é: nunca existem casos adequados, então este também não é um? Isso dificilmente é um argumento e, na verdade, não é muito útil.
hakre

1
@hakre: Meu argumento é que essa abordagem é ruim por várias razões (parágrafo 1 - razões suficientes para evitar isso são fornecidas na explicação de caracteres estritamente digitados) e não devem ser usadas, porque existe uma melhor (parágrafo 2 )
back2dos

3
@hakre Eu posso imaginar uma situação que justificaria essa abordagem se um assassino em série o prendesse na sua cadeira e estivesse segurando uma serra elétrica sobre sua cabeça, rindo loucamente e ordenando que você usasse literais de cordas em todos os lugares para a lógica de despacho. Além disso, é preferível confiar nos recursos da linguagem para fazer esse tipo de coisa. Na verdade, posso pensar em outro caso envolvendo crocodilos e ninjas, mas isso é um pouco mais envolvido.
Rob Rob

19

Seu professor está fazendo errado . Ele está tentando desacoplar completamente a visualização e o modelo, forçando a comunicação a viajar via string nas duas direções (a visualização não depende do modelo e o modelo não depende da visualização) , o que derrota totalmente o objetivo do design orientado a objetos e do MVC. Parece que, em vez de escrever aulas e projetar uma arquitetura baseada em OO, ele está escrevendo módulos diferentes que simplesmente respondem a mensagens baseadas em texto.

Para ser um pouco justo com o professor, ele está tentando criar em Java o que se parece muito com a interface .NET altamente comum chamada INotifyPropertyChanged, que notifica os assinantes de eventos de mudança por meio da passagem de strings que especificam qual propriedade foi alterada. Pelo menos no .NET, essa interface é usada principalmente para se comunicar a partir de um modelo de dados para exibir objetos e elementos da GUI (ligação).

Se bem feito , adotar essa abordagem pode trazer alguns benefícios¹:

  1. A visualização depende do modelo, mas o modelo não precisa depender da visualização. Esta é a inversão de controle apropriada .
  2. Você tem um único local no seu código de exibição que consegue responder às notificações de alteração do modelo e apenas um local para assinar / cancelar a assinatura, reduzindo a probabilidade de um vazamento de memória de referência pendente.
  3. Há muito menos sobrecarga de codificação quando você deseja adicionar ou remover um evento do editor de eventos. No .NET, existe um mecanismo interno de publicação / assinatura chamado, eventsmas em Java você teria que criar classes ou objetos e escrever código de inscrição / cancelamento para cada evento.
  4. A maior queda com essa abordagem é que o código de inscrição pode ficar fora de sincronia com o código de publicação. No entanto, isso também é um benefício em alguns ambientes de desenvolvimento. Se um novo evento for desenvolvido no modelo e a visualização ainda não estiver atualizada para assiná-lo, a visualização ainda funcionará. Da mesma forma, se um evento for removido, você terá algum código morto, mas ele ainda poderá ser compilado e executado.
  5. No .NET, pelo menos, o Reflection é usado com muita eficiência aqui para chamar automaticamente getters e setters, dependendo da string que foi passada ao assinante.

Portanto, em resumo (e para responder à sua pergunta), isso pode ser feito, mas a abordagem adotada pelo professor é falha por não permitir pelo menos uma dependência unidirecional entre a View e o Model. Não é apenas prático, excessivamente acadêmico e não é um bom exemplo de como usar as ferramentas disponíveis.


¹ Pesquise no Google para INotifyPropertyChangedencontrar um milhão de maneiras pelas quais as pessoas tentam evitar o uso de cordas mágicas ao implementá-las.


Parece que você descreveu o SOAP aqui. : D ... (mas sim, eu entendo seu ponto e você está certo)
Konrad Rudolph

11

enums (ou public static final Strings) devem ser usados ​​em vez de cordas mágicas.

Seu professor não entende OO . Por exemplo, ter uma classe de veículo concreta?
E para que essa classe entenda a marca de uma bicicleta?

O veículo deve ser abstrato e deve haver uma subclasse concreta chamada Bike. Eu seguia as regras dele para obter uma boa nota e depois esquecer o ensino dele.


3
A menos que o objetivo deste exercício seja melhorar o código usando melhores princípios de OO. Nesse caso, refatorar.
Joshin4colours

2
@ joshin4colours Se o StackExchange estivesse classificando, você estaria certo; mas como o aluno é a mesma pessoa que teve a ideia, ele nos daria notas ruins porque sabe que sua implementação foi melhor.
Dan Neely

7

Eu acho que você tem um bom argumento, as cordas mágicas são ruins. Alguém na minha equipe teve que depurar um problema causado por seqüências de caracteres mágicas algumas semanas atrás (mas foi no código delas que eles escreveram, então fiquei de boca calada). E aconteceu ... na indústria !!

A vantagem dessa abordagem é que pode ser mais rápido codificar, especialmente para pequenos projetos de demonstração. As desvantagens são que é propenso a erros de digitação e quanto mais a string é usada, maior a chance de um erro de digitação estragar as coisas. E se você quiser alterar uma string mágica, refatorar strings é mais difícil do que refatorar variáveis, pois as ferramentas de refatoração também podem tocar em strings que contêm a string mágica (como uma substring), mas não são elas mesmas a string mágica e não devem ser tocadas. Além disso, você pode precisar manter um dicionário ou índice de strings mágicas para que novos desenvolvedores não comecem a inventar novas strings mágicas com o mesmo objetivo e não perca tempo pesquisando as strings mágicas existentes.

Nesse contexto, parece que um Enum pode ser melhor do que strings mágicas ou mesmo constantes globais de strings. Além disso, os IDEs geralmente oferecem algum tipo de assistência ao código (preenchimento automático, refatoração, encontrar todas as referências, etc ...) quando você usa seqüências de caracteres constantes ou Enums. Um IDE não oferece muita ajuda se você usar seqüências de caracteres mágicas.


Concordo que enums são melhores que literais de string. Mas, mesmo assim, é realmente melhor chamar um "stateChangeRequest" com uma chave enum ou simplesmente chamar um método no modelo diretamente? De qualquer maneira, o modelo e a vista são acoplados nesse ponto.
Philip

@Philip: Bem, então, eu acho que para dissociar o modelo e vista naquele ponto, você pode precisar de algum tipo de controlador de classe para fazer isso ...
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner - Sim. Uma arquitetura MVC é a mais dissociada
cdeszaq

Certo, se outra camada ajudar a dissociá-los, eu não argumentaria contra isso. Mas essa camada ainda pode ser digitada estaticamente e usar métodos reais, em vez de mensagens de string ou enum para conectá-las.
Philip

6

Usar 'experiência no setor' é um argumento fraco. Seu professor precisa apresentar um argumento melhor que esse :-).

Como outros já declararam, o uso de Strings mágicas certamente é desaprovado, o uso de enums é considerado muito mais seguro. Um enum tem significado e escopo , cordas mágicas não. Além disso, a maneira pela qual seu professor está dissociando as preocupações é considerada uma técnica mais antiga e mais frágil do que a usada atualmente.

Vejo que o @backtodos cobriu isso enquanto respondia, então adicionarei minha opinião de que, usando Inversão de controle (da qual a injeção de dependência é um formulário, você percorrerá toda a estrutura do Spring na indústria antes de muito tempo). ..) é considerada uma maneira mais moderna de lidar com esse tipo de dissociação.


2
Concordo plenamente que a academia deve ter requisitos muito mais altos do que a indústria. A academia == melhor maneira teórica; A indústria == melhor maneira prática
Farmor

3
Infelizmente, alguns dos que ensinam essas coisas na academia o fazem porque não conseguem cortar na indústria. Do lado positivo, alguns acadêmicos são brilhantes e não devem ser mantidos escondidos em algum escritório corporativo.
FrustratedWithFormsDesigner

4

Sejamos claros; inevitavelmente, há algum acoplamento lá. O fato de existir um tópico assinável é um ponto de acoplamento, assim como a natureza do restante da mensagem (como “cadeia única”). Dado isso, a questão torna-se uma questão de saber se o acoplamento é maior quando realizado por meio de strings ou tipos. A resposta para isso depende se você está preocupado com o acoplamento distribuído ao longo do tempo ou no espaço: se as mensagens serão preservadas em um arquivo antes de serem lidas posteriormente, ou serão enviadas para outro processo (especialmente se que não está escrito no mesmo idioma), o uso de strings pode tornar as coisas muito mais simples. A desvantagem é que não há verificação de tipo; os erros não serão detectados até o tempo de execução (e às vezes nem isso).

Uma estratégia de mitigação / comprometimento para Java pode ser usar uma interface digitada, mas onde essa interface digitada está em um pacote separado. Ele deve consistir apenas em Java interfacee tipos de valor básico (por exemplo, enumerações, exceções). Não deve haver nenhum implementações de interfaces em que o pacote. Todo mundo implementa isso e, se for necessário transmitir as mensagens de maneira complexa, uma implementação específica de um delegado Java pode usar seqüências de caracteres mágicas, conforme necessário. Também facilita a migração do código para um ambiente mais profissional, como JEE ou OSGi, além de ajudar muito em pequenas coisas, como testes ...


4

O que seu professor está propondo é o uso de "cordas mágicas". Embora ele "junte frouxamente" classes entre si, permitindo mudanças mais fáceis, é um antipadrão; As seqüências de caracteres devem muito, MUITO raramente, ser usadas para conter ou controlar instruções de código e, quando isso absolutamente acontecer, o valor das seqüências de caracteres que você está usando para controlar a lógica deve ser definido central e constantemente, para que haja UMA autoridade no valor esperado de qualquer string em particular.

A razão pela qual é muito simples; as seqüências de caracteres não são verificadas pelo compilador quanto à sintaxe ou consistência com outros valores (na melhor das hipóteses, elas são verificadas quanto à forma adequada em relação aos caracteres de escape e a outra formatação específica do idioma na sequência). Você pode colocar o que quiser em uma string. Como tal, você pode obter um algoritmo / programa irremediavelmente quebrado para compilar, e não é até que seja executado que ocorre um erro. Considere o seguinte código (C #):

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

... todo esse código é compilado, primeira tentativa, mas o erro é óbvio com uma segunda olhada. Existem inúmeros exemplos desse tipo de programação e todos estão sujeitos a esse tipo de erro, mesmo que você defina cadeias como constantes (porque as constantes no .NET são gravadas no manifesto de cada biblioteca na qual são usadas, o que deve todos serão recompilados quando o valor definido for alterado para que o valor seja alterado "globalmente"). Como o erro não é detectado no tempo de compilação, ele deve ser detectado durante o teste em tempo de execução, e isso é garantido apenas com 100% de cobertura de código em testes de unidade com exercício adequado de código, o que geralmente é encontrado apenas no mais seguro contra falhas absoluto , sistemas em tempo real.


2

Na verdade, essas classes ainda estão fortemente acopladas. Só que agora eles estão fortemente acoplados de tal maneira que o compilador não pode dizer quando as coisas estão quebradas!

Se alguém alterar "BicycleMake" para "BicyclesMake", ninguém descobrirá que as coisas estão quebradas até o tempo de execução.

Erros de tempo de execução são muito mais caros de corrigir do que erros de tempo de compilação - isso é apenas todo tipo de mal.

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.