Olá, tudo bem?

Esta é uma pergunta interessante, e gostaria de colaborar com uma provocação. Eu gosto de uma abordagem mais "poética". Não que eu acredite que ela seja mais ou menos correta, mas pode ajudar a compor uma boa definição de classe quando estiver escrevendo seus códigos (pelo menos me ajuda).

Eu vou pular toda a chatice sobre os 4 conceitos básicos de POO porque certamente você já viu sobre isso, e é fácil de encontrar pela web.

A classe representaria um contexto, e sua principal função é não só defini-lo, mas também protegê-lo.

Quando você faz uma classe chamada Pessoa, você deve pensar o que importa pro seu contexto. Geralmente pensamos em nome, altura, peso, etc. A questão é: para um sistema médico, a definição de uma pessoa pode ser diferente do que para um sistema veterinário. Para este último, seu peso, altura, tipo sanguíneo, nome de mãe e pai, alergia à medicamentos, entre outros, realmente não importa. Isto quer dizer que modelagem em POO não é sobre o mundo como ele é, mas sob a ótica e o viés do contexto que você está analisando/desenvolvendo.

Os objetos são o que? Se responder instância de uma classe, a pergunta muda para "e o que é uma instância?". Então é uma resposta um tanto pobre, eu diria. Acredito que são a individualização do contexto. Todos somos pessoas, mas certamente não somos iguais. O objeto é a definição de um elemento, com atributos que fazem sentido para aquele contexto, e que permitem diferenciar uns dos outros. Logo, eu sou eu porque meu atributos são diferentes dos seus, mesmo não deixando de ser pessoas. Podemos viajar um pouco mais aqui e falar sobre gêmeos identicos (que possuem atributos iguais mas certamente são pessoas diferentes), mas até isso vale, pois na maioria das linguagens que trabalham com POO dois objetos iguais não significa que são os mesmos objetos.

Aí vem sua colocação: e os métodos? O que são e porque faria sentido uma classe só de métodos?

Os métodos são algoritmos. Algoritmo é uma sequência de ações para alcançar um estado, objetivo, ou qualquer outro termo que te deixe confortável. Isto nos leva a pensar que um método seria "como realizar algo dentro de um contexto". E muitas vezes, um indivíduo fazendo algo tem resultado diferente de outro indivíduo, no mesmo contexto, fazendo a mesma coisa. Por exemplo, suponha que exista o método gritarProprioCPF na nossa classe Pessoa (não me pergunte o porquê). Dois indivíduos diferentes teriam resultados diferentes. Isto porque este é um método que depende diretamente de um dos atributos do indivíduo que está gritando, apesar do algoritmo ser o mesmo para todos:

  • Lembrar do CPF
  • Encher os pulmões
  • Abrir a boca
  • Gritar

É por isto que os métodos são definidos nas classes, mas executados através do objeto, para que fique claro os atributos de contexto que devem ser levados em consideração durante a execução.

Também explica um pouco dos puristas de POO reclamarem de JS e Pyhton, pois por padrão você pode "injetar" funções diretamente nos objetos, independente da definição de classe, e muitas vezes isto acaba se assemelhando a métodos.

Agora, existem algoritmos que não dependem de nenhum atributo do indivíduo, e o resultado é exatamente igual para todos eles, por exemplo jogarOvoNoChao. A questão é: que ovo? O que eu te der, claro! Logo, sendo mais completo, teriamos public OvoQuebrado jogarOvoNoChao(OvoDeGalinha ovo). Então perceba que os parâmetros definem atributos a serem considerados no algoritmo e que não estão contidos no próprio indivíduo daquele contexto, devem ser recebidos de fora dele. Considerando o mesmo ovo, tanto faz eu ou você (ou qualquer objeto da classe Pessoa) jogar ele no chão, o resultado será o mesmo. Isto é o que chamamos de um método estático, independe do objeto, mas ainda faz parte do contexto, e por isso são definidos na classe.

No Python existe ainda um terceiro "tipo" de método, o método de classe. De forma extremamente resumida e simplificada, apesar de ser acessado pelo objeto, é um método que o this armazena a classe ao invés do próprio objeto.

E existem coisas que não fazem muito sentido individualizar. Como o colega kht exemplificou, a matemática é um contexto único, individualizável. Não existem atributos que possam diferenciar uma matemática de outra. E ela define uma série de algoritmos: soma, divisão, derivação e etc. Todos eles são bem definidos, mas dependem de valores que devem ser fornecidos externamente (parâmetros). Portanto, poderiamos dizer que esta seria uma classe formada totalmente e apenas de métodos estáticos. E se não vamos individualizar, porque se preocupar em instanciar então? Para o Python, módulos são literalmente objetos de uma classe, e sua instanciação ocorre na importação dele.

Por último, existe o aspecto de proteção que citei inicialmente.

Eu não posso chegar e simplesmente mudar seu CPF. O nosso consultório poderia ficar com uma série de registros médicos falando sobre um indivíduo que já não é mais você. Teríamos então uma situação problemática, e há duas soluções: não permitir que CPF mudem, ou anotar em algum lugar (talvez no próprio registro) que seu CPF mudou para X. Então, em POO, um método se responsabiliza em proteger a integridade do contexto e de seus indivíduos, não permitindo idades negativas, pessoas sem nomes e propagando alterações importantes a outros pontos do código que possa interessar. É por isso dos benditos "getters/setters", bastante populares no Java. É uma forma de restringir acesso direto ao atributo e controlar tanto o acesso quanto a edição deste.

Bom, acredito que seja isto, espero que ter ajudado. Bons estudos!