Uma quantidade significativa de tempo, não consigo pensar em uma razão para ter um objeto em vez de uma classe estática. Os objetos têm mais benefícios do que eu penso? [fechadas]

9

Entendo o conceito de um objeto e, como programador Java, sinto que o paradigma OO vem naturalmente para mim na prática.

No entanto, recentemente, me vi pensando:

Espere um segundo, quais são realmente os benefícios práticos de usar um objeto em vez de usar uma classe estática (com práticas adequadas de encapsulamento e OO)?

Eu poderia pensar em dois benefícios do uso de um objeto (ambos são significativos e poderosos):

  1. Polimorfismo: permite trocar funcionalidades de forma dinâmica e flexível durante o tempo de execução. Também permite adicionar novas funcionalidades 'partes' e alternativas ao sistema com facilidade. Por exemplo, se houver uma Carclasse projetada para trabalhar com Engineobjetos e você desejar adicionar um novo mecanismo ao sistema que o carro possa usar, você poderá criar uma nova Enginesubclasse e simplesmente passar um objeto dessa classe para o Carobjeto, sem precisar mudar qualquer coisa sobre Car. E você pode decidir fazer isso durante o tempo de execução.

  2. Ser capaz de 'passar a funcionalidade': você pode passar um objeto dinamicamente pelo sistema.

Mas existem mais vantagens em objetos do que em classes estáticas?

Frequentemente, quando adiciono novas 'partes' a um sistema, faço isso criando uma nova classe e instanciando objetos a partir dele.

Mas, recentemente, quando parei e pensei sobre isso, percebi que uma classe estática faria exatamente o mesmo que um objeto, em muitos lugares onde normalmente uso um objeto.

Por exemplo, estou trabalhando para adicionar um mecanismo de salvar / carregar arquivos ao meu aplicativo.

Com um objeto, a linha de código de chamada ficará assim: Thing thing = fileLoader.load(file);

Com uma classe estática, ficaria assim: Thing thing = FileLoader.load(file);

Qual é a diferença?

Com bastante frequência, simplesmente não consigo pensar em uma razão para instanciar um objeto quando uma classe estática simples e antiga atuaria da mesma maneira. Mas nos sistemas OO, as classes estáticas são bastante raras. Então, devo estar faltando alguma coisa.

Há mais vantagens em outros objetos que eu listei? Por favor explique.

EDIT: Para esclarecer. Acho objetos muito úteis ao trocar funcionalidades ou transmitir dados. Por exemplo, eu escrevi um aplicativo que compõe melodias. MelodyGeneratortinha várias subclasses que criam melodias de maneira diferente e os objetos dessas classes eram intercambiáveis ​​(padrão de estratégia).

As melodias também eram objetos, pois é útil transmiti-las. O mesmo aconteceu com os acordes e escalas.

Mas e as partes "estáticas" do sistema - que não serão divulgadas? Por exemplo - um mecanismo 'salvar arquivo'. Por que eu deveria implementá-lo em um objeto, e não em uma classe estática?

Aviv Cohn
fonte
Então, em vez de um objeto com campos e talvez métodos, sem objetos, sem registros, apenas um monte de valores escalares passados ​​e retornados de métodos estáticos? Edit: Seu novo exemplo sugere o contrário: Objetos, apenas sem métodos de instância? Caso contrário, o que é Thing?
O que acontece quando você precisa trocar o seu FileLoaderpor um que leia de um soquete? Ou uma simulação para testar? Ou um que abre um arquivo zip?
Benjamin Hodgson
11
@delnan Ok, eu descobri uma pergunta que a resposta vai me ajudar a entender: por que você implementaria uma parte 'estática' do sistema como um objeto? Como o exemplo da pergunta: um mecanismo de salvar arquivo. O que ganho com a implementação em um objeto?
Aviv Cohn
11
O padrão deve ser usar um objeto não estático. Use apenas classes estáticas se sentir que realmente expressa melhor sua intenção. System.Mathno .NET é um exemplo de algo que faz muito mais sentido como uma classe estática: você nunca precisará trocá-lo ou zombá-lo e nenhuma das operações logicamente poderá fazer parte de uma instância. Realmente não acho que o seu exemplo de "economia" se encaixa nessa conta.
Benjamin Hodgson

Respostas:

14

lol você soa como uma equipe que eu costumava trabalhar;)

Java (e provavelmente C #) certamente suporta esse estilo de programação. E eu trabalho com pessoas cujo primeiro instinto é: "Eu posso fazer disso um método estático!" Mas existem alguns custos sutis que o acompanham com o tempo.

1) Java é uma linguagem orientada a objetos. E isso deixa os caras funcionais malucos, mas realmente se mantém muito bem. A idéia por trás do OO é agrupar funcionalidade com estado para ter pequenas unidades de dados e funcionalidade que preservam sua semântica ocultando o estado e expondo apenas as funções que fazem sentido nesse contexto.

Ao passar para uma classe apenas com métodos estáticos, você está quebrando a parte "state" da equação. Mas o estado ainda tem que morar em algum lugar. Então, o que vi ao longo do tempo é que uma classe com todos os métodos estáticos começa a ter listas de parâmetros cada vez mais complicadas, porque o estado está passando da classe para as chamadas de função.

Depois de criar uma classe com todos os métodos estáticos, percorra e apenas examine quantos desses métodos têm um único parâmetro comum. É uma dica de que esse parâmetro deve ser a classe que contém essas funções, ou então esse parâmetro deve ser um atributo de uma instância.

2) As regras para OO são muito bem compreendidas. Depois de um tempo, você pode observar um design de classe e verificar se ele atende a critérios como o SOLID. E após muitos testes de unidade de prática, você desenvolve um bom senso do que torna uma classe "do tamanho certo" e "coerente". Mas não existem boas regras para uma classe com todos os métodos estáticos, e não há nenhuma razão real para você não apenas agrupar tudo lá. A aula é aberta no seu editor, então que diabos? Basta adicionar seu novo método lá. Depois de um tempo, seu aplicativo se transforma em vários "Objetos Divinos" concorrentes, cada um tentando dominar o mundo. Novamente, refatorá-los em unidades menores é altamente subjetivo e difícil de dizer se você acertou.

3) As interfaces são um dos recursos mais poderosos do Java. A herança de classe provou ser problemática, mas a programação com interfaces continua sendo um dos truques mais poderosos da linguagem. (idem C #) As classes totalmente estáticas não podem se colocar nesse modelo.

4) Bate a porta em importantes técnicas OO das quais você não pode tirar proveito. Portanto, você pode trabalhar por anos apenas com um martelo na sua caixa de ferramentas, sem perceber o quanto as coisas seriam mais fáceis se você também tivesse uma chave de fenda.

4.5) Cria as dependências de tempo de compilação mais difíceis e mais inquebráveis ​​possíveis. Portanto, por exemplo, se você tiver FileSystem.saveFile(), não há como mudar isso, além de fingir sua JVM em tempo de execução. O que significa que toda classe que referencia sua classe de função estática tem uma dependência difícil em tempo de compilação dessa implementação específica, o que torna a extensão quase impossível e complica enormemente os testes. Você pode testar a classe estática isoladamente, mas fica muito difícil testar as classes que se referem a essa classe isoladamente.

5) Você deixará seus colegas de trabalho loucos. A maioria dos profissionais com quem trabalho leva o código a sério e presta atenção a pelo menos algum nível de princípios de design. Deixar de lado a intenção principal de uma linguagem fará com que eles arrancem os cabelos porque estarão constantemente refatorando o código.

Quando estou em um idioma, sempre tento usá-lo bem. Por exemplo, quando estou em Java, uso um bom design de OO, porque estou realmente aproveitando a linguagem para o que ela faz. Quando estou no Python, eu misturo funções no nível do módulo com classes ocasionais - eu só podia escrever aulas no Python, mas acho que não usaria a linguagem para o que é bom.

Outra tática é usar mal um idioma, e eles reclamam de todos os problemas que estão causando. Mas isso se aplica a praticamente qualquer tecnologia.

O principal recurso do Java é gerenciar a complexidade em unidades pequenas e testáveis ​​que se unem para facilitar a compreensão. Java enfatiza definições claras de interface, independentemente da implementação - o que é um grande benefício. É por isso que (e outras linguagens OO semelhantes) permanecem tão amplamente usadas. Apesar de toda a verbosidade e ritualismo, quando termino com um grande aplicativo Java, sempre sinto que as idéias são mais limpas em código do que meus projetos em uma linguagem mais dinâmica.

É difícil, no entanto. Eu já vi pessoas pegando o bug "tudo estático" e é meio difícil convencê-lo disso. Mas eu os vi sentir um grande alívio quando superaram isso.

Roubar
fonte
Você fala principalmente sobre manter as coisas pequenas e modulares e manter o estado e a funcionalidade juntos. Não há nada que me impeça de fazer isso com classes estáticas. Eles podem ter estado. E você pode encapsulá-los com getters-setters. E você pode dividir o sistema em pequenas classes que encapsulam funcionalidade e estado. Tudo isso se aplica a classes e objetos. E esse é exatamente o meu dilema: digamos que estou adicionando novas funcionalidades ao meu aplicativo. Obviamente, ele irá para uma aula separada para obedecer ao SRP. Mas por que exatamente eu deveria instanciar essa classe? Isto é o que eu não entendo.
Aviv Cohn
Quero dizer: às vezes fica claro por que quero objetos. Vou usar o exemplo da edição da minha pergunta: eu tinha um aplicativo que compõe melodias. Havia escalas diferentes que eu poderia conectar nos MelodyGenerators para gerar melodias diferentes. E as Melodias eram objetos, então eu poderia colocá-las em uma pilha e voltar a tocar antigas, etc. Em casos como esses, fica claro para mim por que devo usar objetos. O que não entendo é por que devo usar objetos para representar partes 'estáticas' do sistema: por exemplo, um mecanismo de salvar arquivo. Por que isso não deveria ser apenas uma classe estática?
Aviv Cohn
Uma classe com métodos estáticos precisa externalizar seu estado. O que, às vezes, é uma técnica de fatoração muito importante. Mas na maioria das vezes você deseja encapsular o estado. Um exemplo concreto: anos atrás, um colega de trabalho escreveu um "selecionador de data" em Javascript. E foi um pesadelo. Os clientes se queixavam disso constantemente. Então, refatorei-o em objetos de "calendário" e, de repente, instanciei vários deles, colocando-os lado a lado, pulando entre meses e anos etc. Era tão rico que realmente precisávamos desativar os recursos. A instanciação lhe dá essa escala.
Rob
3
"A idéia por trás do OO é agrupar funcionalidade com estado para ter pequenas unidades de dados e funcionalidade que preservam sua semântica ocultando o estado e expondo apenas as funções que fazem sentido nesse contexto". Isso é fundamentalmente errado. OO não implica estado mutável e a abstração não é exclusiva do OO. Existem duas maneiras de obter abstração e os objetos são apenas uma delas (a outra são tipos de dados abstratos).
Doval
11
@Doval Não entendo por que toda discussão sobre OO deve necessariamente se transformar em discussão sobre programação funcional.
Rob
6

Você perguntou:

Mas existem mais vantagens em objetos do que em classes estáticas?

Antes desta pergunta, você listava o polimorfismo e era transmitido como dois benefícios do uso de objetos. Quero dizer que essas são as características do paradigma OO. Encapsulamento é outra característica do paradigma OO.

No entanto, esses não são os benefícios. Os benefícios são:

  1. Melhores abstrações
  2. Melhor manutenção
  3. Melhor testabilidade

Você disse:

Mas, recentemente, quando parei e pensei sobre isso, percebi que uma classe estática faria exatamente o mesmo que um objeto, em muitos lugares onde normalmente uso um objeto.

Eu acho que você tem um ponto válido lá. No fundo, a programação nada mais é do que a transformação de dados e a criação de efeitos colaterais com base nos dados. Às vezes, a transformação de dados requer dados auxiliares. Outras vezes, não.

Quando você está lidando com a primeira categoria de transformações, os dados auxiliares devem ser passados ​​como entrada ou armazenados em algum lugar. Um objeto é a melhor abordagem que as classes estáticas para essas transformações. Um objeto pode armazenar os dados auxiliares e usá-los no momento certo.

Para a segunda categoria de transformações, uma classe estática é tão boa quanto um Objeto, se não melhor. Funções matemáticas são exemplos clássicos dessa categoria. A maioria das funções padrão da biblioteca C também se enquadra nessa categoria.

Você perguntou:

Com um objeto, a linha de código de chamada ficará assim: Thing thing = fileLoader.load(file);

Com uma classe estática, ficaria assim: Thing thing = FileLoader.load(file);

Qual é a diferença?

Se FileLoader, nunca , for necessário armazenar dados, eu usaria a segunda abordagem. Se houver uma pequena chance de que sejam necessários dados auxiliares para executar a operação, a primeira abordagem é uma aposta mais segura.

Você perguntou:

Há mais vantagens em outros objetos que eu listei? Por favor explique.

Listei os benefícios (vantagens) do uso do paradigma OO. Espero que sejam auto-explicativos. Caso contrário, terei prazer em elaborar.

Você perguntou:

Mas e as partes "estáticas" do sistema - que não serão divulgadas? Por exemplo - um mecanismo 'salvar arquivo'. Por que eu deveria implementá-lo em um objeto, e não em uma classe estática?

Este é um exemplo em que uma classe estática simplesmente não funciona. Existem várias maneiras de salvar os dados do aplicativo em um arquivo:

  1. Salve-o em um arquivo CSV.
  2. Salve-o em um arquivo XML.
  3. Salve-o em um arquivo json.
  4. Salve-o como um arquivo binário, com despejo direto dos dados.
  5. Salve-o diretamente em alguma tabela em um banco de dados.

A única maneira de fornecer essas opções é criar uma interface e criar objetos que implementam a interface.

Em conclusão

Use a abordagem correta para o problema em questão. É melhor não ser religioso sobre uma abordagem versus a outra.

R Sahu
fonte
Além disso, se diversos tipos de objetos (classes) necessidade de ser salvo (por exemplo Melody, Chord, Improvisations, SoundSamplee outros), então será econômico para simplificar a implementação através de abstrações.
rwong
5

Depende da linguagem e do contexto, às vezes. Por exemplo, os PHPscripts usados ​​para atender às solicitações sempre têm uma solicitação para atender e uma resposta para gerar durante toda a vida útil do script, portanto, métodos estáticos para atuar na solicitação e gerar uma resposta podem ser apropriados. Mas em um servidor escrito Node.js, pode haver muitos pedidos e respostas diferentes, todos ao mesmo tempo. Portanto, a primeira pergunta é - você tem certeza de que a classe estática realmente corresponde a um objeto singleton?

Em segundo lugar, mesmo se você tiver singletons, o uso de objetos permite aproveitar o polimorfismo usando técnicas como Dependency_injection e Factory_method_pattern . Isso geralmente é usado em vários padrões Inversion_of_control e é útil para criar objetos simulados para teste, registro etc.

Você mencionou as vantagens acima, então aqui está uma que você não mencionou: herança. Muitos idiomas não têm capacidade de substituir métodos estáticos da mesma maneira que os métodos de instância podem ser substituídos. Em geral, é muito mais difícil herdar e substituir métodos estáticos.

Gregory Magarshak
fonte
Obrigado por responder. Na sua opinião: ao criar uma parte 'estática' do sistema, algo que não será 'repassado' - por exemplo, a parte do sistema que reproduz sons ou a parte do sistema que salva dados em um arquivo: devo implementá-lo em um objeto ou em uma classe estática?
Aviv Cohn
3

Além da postagem de Rob Y


Desde que a funcionalidade do seu load(File file) possa ser claramente separada de todas as outras funções que você usa, é bom usar um método / classe estático. Você externaliza o estado (o que não é uma coisa ruim) e também não recebe redundância, pois pode, por exemplo, aplicar ou aplicar parcial sua função para que você não precise se repetir. (na verdade, é o mesmo ou semelhante ao uso de um factorypadrão)

No entanto, assim que duas dessas funções começarem a ter um uso comum, você poderá agrupá-las de alguma forma. Imagine que você não apenas tem uma loadfunção, mas também uma hasValidSyntaxfunção. O que você vai fazer?

     if (FileLoader.hasValidSyntax(myfile))
          Thing thing = FileLoader.load(myfile);
     else
          println "oh noes!"

Veja as duas referências myfileaqui? Você começa a se repetir porque seu estado externalizado deve ser passado para todas as chamadas. Rob Y descreveu como você internaliza o estado (aqui o file) para que você possa fazer assim:

     FileLoader myfileLoader = new FileLoader(myfile)
     if (myfileLoader.hasValidSyntax())
          Thing thing = myfileLoader.load();
     else
          println "oh noes!"
valenterry
fonte
Ter que passar duas vezes no mesmo objeto é um sintoma superficial; o problema real pode indicar é que algum trabalho é realizado de forma redundante - o arquivo pode ter que ser aberto duas vezes, analisado duas vezes, e fechado duas vezes, se hasValidSyntax()e load()não estão autorizados a estado reutilização (o resultado de um arquivo parcialmente analisado).
rwong
2

Uma questão importante ao usar classes estáticas é que eles forçá -lo a esconder suas dependências, e forçá -lo a depender de implementações. Na seguinte assinatura do construtor, quais são suas dependências:

public Person(String name)

Bem, a julgar pela assinatura, uma pessoa só precisa de um nome, certo? Bem, não se a implementação for:

public Person(String name) {
    ResultSet rs = DBConnection.getPersonFilePathByName(name);
    File f = FileLoader.load(rs.getPath());
    PersonData.setDataFile(f);
    this.name = name;
    this.age = PersonData.getAge();
}

Portanto, uma pessoa não é apenas instanciada. Na verdade, extraímos dados de um banco de dados, o que nos fornece um caminho para um arquivo, que deve ser analisado e, em seguida, os dados reais que desejamos devem ser extraídos. Este exemplo é claramente exagerado, mas prova o ponto. Mas espere, pode haver muito mais! Eu escrevo o seguinte teste:

public void testPersonConstructor() {
    Person p = new Person("Milhouse van Houten");
    assertEqual(10, p.age);
}

Isso deve passar, certo? Quero dizer, encapsulamos todas essas outras coisas, certo? Bem, na verdade, explode com uma exceção. Por quê? Ah, sim, as dependências ocultas que não conhecíamos têm um estado global. As DBConnectionnecessidades inicializadas e conectadas. As FileLoadernecessidades inicializadas com um FileFormatobjeto (como XMLFileFormatouCSVFileFormat ). Nunca ouviu falar disso? Bem, esse é o ponto. Seu código (e seu compilador) não pode lhe dizer que você precisa dessas coisas porque as chamadas estáticas ocultam essas dependências. Eu disse teste? Eu quis dizer que o novo desenvolvedor júnior acabou de lançar algo parecido com isto em seu lançamento mais recente. Afinal, compilado = funciona, certo?

Além disso, diga que você está em um sistema que não possui uma instância do MySQL em execução. Ou, digamos que você esteja em um sistema Windows, mas sua DBConnectionclasse só será enviada para o servidor MySQL em uma caixa Linux (com caminhos do Linux). Ou, digamos que você esteja em um sistema em que o caminho retornado peloDBConnection não seja de leitura / gravação para você. Isso significa que a tentativa de executar ou testar esse sistema sob qualquer uma dessas condições falhará, não por causa de um erro de código, mas por um erro de design que limita a flexibilidade do código e o vincula a uma implementação.

Agora, digamos, queremos registrar todas as chamadas no banco de dados para uma Personinstância específica , uma que passe por um determinado caminho problemático. Poderíamos colocar o logon DBConnection, mas isso registrará tudo , colocando muita confusão e dificultando a distinção do caminho de código específico que queremos rastrear. Se, no entanto, estivermos usando injeção de dependência com uma DBConnectioninstância, poderíamos simplesmente implementar a interface em um decorador (ou estender a classe, pois temos as duas opções disponíveis com um objeto). Com uma classe estática, não podemos injetar a dependência, não podemos implementar uma interface, não podemos envolvê-la em um decorador e não podemos estender a classe. Só podemos chamá-lo diretamente, em algum lugar oculto no nosso código. Assim somos forçados ter uma dependência oculta em uma implementação.

Isso é sempre ruim? Não necessariamente, mas pode ser melhor reverter seu ponto de vista e dizer "Existe uma boa razão para que isso não deva ser uma instância?" em vez de "Existe uma boa razão para que isso deva ser uma instância?" Se você realmente pode dizer que seu código será tão inabalável (e sem estado) quanto Math.abs(), tanto na implementação quanto na maneira como será usado, considere torná-lo estático. Ter instâncias sobre classes estáticas, no entanto, oferece um mundo de flexibilidade que nem sempre é fácil de recuperar após o fato. Também pode fornecer mais clareza sobre a verdadeira natureza das dependências do seu código.

cbojar
fonte
Dependências ocultas são um ponto extremamente bom. Quase esqueci meu idioma de que "a qualidade do código deve ser julgada pelo modo como é usado, não como é implementado".
Euphoric
Mas dependências ocultas podem existir em uma classe não estática também, certo? Portanto, não vejo por que isso seria uma desvantagem de classes estáticas, mas não de classes não estáticas.
valenterry
@valenterry Sim, também podemos ocultar dependências não estáticas, mas o ponto principal é que ocultar dependências não estáticas é uma opção . Eu poderia newcriar vários objetos no construtor (o que geralmente não é aconselhável), mas também posso injetá-los. Com classes estáticas, não há como injetá-las (não é fácil em Java de qualquer maneira, e isso seria uma discussão totalmente diferente para linguagens que permitem isso), portanto não há escolha. É por isso que enfatizo a ideia de ser forçado a esconder tão fortemente as dependências em minha resposta.
cbojar
Mas você pode injetá-los sempre que chamar o método, fornecendo-os como argumentos. Então você não é forçado a escondê-los, mas a torná-los explícitos para que todos vejam "aha, esse método depende desses argumentos" em vez de "aha, esse método depende desses (parte de) argumentos e de algum estado interno oculto que eu não conhecer".
valenterry
Em Java, que eu saiba, você não pode fornecer uma classe estática como parâmetro sem passar por todo um rigmarole de reflexão. (Tentei de todas as maneiras possíveis. Se você tem uma maneira, eu gostaria de vê-la.) E se você optar por fazer isso, estará basicamente subvertendo o sistema de tipos interno (o parâmetro é um Class, certificamo-nos de que é a classe certa) ou você está digitando duck (garantimos que ela responda ao método correto). Se você deseja fazer o último, deve reavaliar sua escolha de idioma. Se você quer fazer o primeiro, casos fazer muito bem e são construídos dentro.
cbojar
1

Apenas meus dois centavos.

Para sua pergunta:

Mas e as partes "estáticas" do sistema - que não serão divulgadas? Por exemplo - um mecanismo 'salvar arquivo'. Por que eu deveria implementá-lo em um objeto, e não em uma classe estática?

Você pode se perguntar se o mecanismo da parte do sistema que reproduz sons ou a parte do sistema que salva dados em um arquivo MUDARÁ . Se sua resposta for sim, isso indica que você deve abstraí-las usando abstract class/ interface. Você pode perguntar, como posso saber as coisas futuras? Definitivamente, não podemos. Portanto, se a coisa é sem estado, você pode usar 'classe estática', como java.lang.Math, por outro lado, usar a abordagem orientada a objetos.

user3663635
fonte
Há um conselho explícito: três greves e você refatorar , o que sugere que se pode adiar até que a mudança seja realmente necessária. O que quero dizer com "mudança" é: mais de um tipo de objeto precisa ser salvo; ou a necessidade de salvar em mais de um formato de arquivo (tipo de armazenamento); ou um aumento drástico na complexidade dos dados salvos. Obviamente, se alguém tiver certeza de que a mudança acontecerá em breve, poderá incorporar um design mais flexível antecipadamente.
rwong