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):
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
Car
classe projetada para trabalhar comEngine
objetos e você desejar adicionar um novo mecanismo ao sistema que o carro possa usar, você poderá criar uma novaEngine
subclasse e simplesmente passar um objeto dessa classe para oCar
objeto, sem precisar mudar qualquer coisa sobreCar
. E você pode decidir fazer isso durante o tempo de execução.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. MelodyGenerator
tinha 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?
fonte
Thing
?FileLoader
por um que leia de um soquete? Ou uma simulação para testar? Ou um que abre um arquivo zip?System.Math
no .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.Respostas:
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.
fonte
Você perguntou:
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:
Você disse:
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:
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:
Listei os benefícios (vantagens) do uso do paradigma OO. Espero que sejam auto-explicativos. Caso contrário, terei prazer em elaborar.
Você perguntou:
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:
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.
fonte
Melody
,Chord
,Improvisations
,SoundSample
e outros), então será econômico para simplificar a implementação através de abstrações.Depende da linguagem e do contexto, às vezes. Por exemplo, os
PHP
scripts 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 escritoNode.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.
fonte
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 umfactory
padrã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
load
função, mas também umahasValidSyntax
função. O que você vai fazer?Veja as duas referências
myfile
aqui? 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 ofile
) para que você possa fazer assim:fonte
hasValidSyntax()
eload()
não estão autorizados a estado reutilização (o resultado de um arquivo parcialmente analisado).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:
Bem, a julgar pela assinatura, uma pessoa só precisa de um nome, certo? Bem, não se a implementação for:
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:
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
DBConnection
necessidades inicializadas e conectadas. AsFileLoader
necessidades inicializadas com umFileFormat
objeto (comoXMLFileFormat
ouCSVFileFormat
). 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
DBConnection
classe 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
Person
instância específica , uma que passe por um determinado caminho problemático. Poderíamos colocar o logonDBConnection
, 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 umaDBConnection
instâ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.fonte
new
criar 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.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.Apenas meus dois centavos.
Para sua pergunta:
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', comojava.lang.Math
, por outro lado, usar a abordagem orientada a objetos.fonte