Quando usar classes estáticas versus instanciadas

170

PHP é minha primeira linguagem de programação. Não consigo entender quando usar classes estáticas versus objetos instanciados.

Sei que você pode duplicar e clonar objetos. No entanto, em todo o meu tempo usando php, qualquer objeto ou função sempre terminava como um único retorno (array, string, int) ou nulo.

Entendo conceitos em livros como uma aula de personagem de videogame. duplicar objeto de carro e deixar o novo vermelho , tudo faz sentido, mas o que não faz é a sua aplicação em aplicativos php e web.

Um exemplo simples. Um blog. Quais objetos de um blog seriam melhor implementados como objetos estáticos ou instanciados? A classe DB? Por que não instanciar o objeto db no escopo global? Por que não tornar cada objeto estático? E o desempenho?

É tudo apenas estilo? Existe uma maneira adequada de fazer isso?

user73119
fonte

Respostas:

123

Esta é uma pergunta bastante interessante - e as respostas também podem ser interessantes ^^

A maneira mais simples de considerar as coisas pode ser:

  • use uma classe instanciada em que cada objeto tenha dados por conta própria (como um usuário tem um nome)
  • use uma classe estática quando for apenas uma ferramenta que funciona com outras coisas (como, por exemplo, um conversor de sintaxe do código BB para HTML; ele não tem vida própria)

(Sim, eu admito, realmente muito simplificado demais)

Uma coisa sobre métodos / classes estáticos é que eles não facilitam o teste de unidade (pelo menos em PHP, mas provavelmente em outras linguagens também).

Outra coisa sobre os dados estáticos é que apenas uma instância existe no seu programa: se você definir MyClass :: $ myData para algum valor em algum lugar, ele terá esse valor e somente ele, em todos os lugares - falando sobre o usuário, você seria capaz de ter apenas um usuário - o que não é tão bom, não é?

Para um sistema de blog, o que eu poderia dizer? Não há muito que eu escreva como estático, na verdade, acho; talvez a classe de acesso ao banco de dados, mas provavelmente não, no final ^^

Pascal MARTIN
fonte
Então, basicamente, use objetos ao trabalhar com coisas únicas, como uma foto, usuário, publicação etc., e use estática quando for para coisas gerais?
Robert Rocha
1
falando bem sobre o usuário, você tem apenas um usuário ativo em cada solicitação. assim haing o usuário ativo da estática pedido faria sentido
my1
69

Os dois principais motivos contra o uso de métodos estáticos são:

  • código usando métodos estáticos é difícil de testar
  • é difícil estender o código usando métodos estáticos

Ter uma chamada de método estático dentro de outro método é realmente pior do que importar uma variável global. No PHP, as classes são símbolos globais; portanto, toda vez que você chama um método estático, depende de um símbolo global (o nome da classe). Este é um caso em que global é mau. Eu tive problemas com esse tipo de abordagem com algum componente do Zend Framework. Existem classes que usam chamadas de método estático (fábricas) para construir objetos. Era impossível fornecer outra fábrica para essa instância para obter um objeto personalizado retornado. A solução para esse problema é usar apenas instâncias e instalar métodos e aplicar singletons e similares no início do programa.

Miško Hevery , que trabalha como treinador ágil no Google, tem uma teoria interessante, ou melhor, aconselha que devemos separar o tempo de criação do objeto do momento em que o usamos. Portanto, o ciclo de vida de um programa é dividido em dois. A primeira parte ( main()digamos o método), que cuida de toda a fiação do objeto em seu aplicativo e da parte que faz o trabalho real.

Então, ao invés de ter:

class HttpClient
{
    public function request()
    {
        return HttpResponse::build();
    }
}

Devemos fazer:

class HttpClient
{
    private $httpResponseFactory;

    public function __construct($httpResponseFactory)
    {
        $this->httpResponseFactory = $httpResponseFactory;
    }

    public function request()
    {
        return $this->httpResponseFactory->build();
    }
}

E então, na página principal / índice, faríamos (esta é a etapa de fiação do objeto ou o tempo para criar o gráfico de instâncias a serem usadas pelo programa):

$httpResponseFactory = new HttpResponseFactory;
$httpClient          = new HttpClient($httpResponseFactory);
$httpResponse        = $httpClient->request();

A idéia principal é separar as dependências de suas classes. Dessa forma, o código é muito mais extensível e, a parte mais importante para mim, testável. Por que é mais importante ser testável? Como nem sempre escrevo o código da biblioteca, a extensibilidade não é tão importante, mas a testabilidade é importante quando refatoro. De qualquer forma, o código testável geralmente gera código extensível, portanto, não é realmente uma situação do tipo "ou-ou".

Miško Hevery também faz uma distinção clara entre singletons e singletons (com ou sem S maiúsculo). A diferença é muito simples. Singletons com minúsculas "s" são aplicados pela fiação no índice / principal. Você instancia um objeto de uma classe que não implementa o padrão Singleton e cuida para que somente passe essa instância para qualquer outra instância que precise. Por outro lado, Singleton, com letra maiúscula "S", é uma implementação do padrão clássico (anti). Basicamente um disfarçado global que não tem muito uso no mundo PHP. Eu não vi um até este ponto. Se você deseja que uma única conexão de banco de dados seja usada por todas as suas classes, é melhor fazê-lo assim:

$db = new DbConnection;

$users    = new UserCollection($db);
$posts    = new PostCollection($db);
$comments = new CommentsCollection($db);

Ao fazer o exposto, fica claro que temos um singleton e também temos uma boa maneira de injetar um mock ou um stub em nossos testes. É surpreendente como os testes de unidade levam a um design melhor. Mas faz muito sentido quando você pensa que os testes o forçam a pensar na maneira como você usaria esse código.

/**
 * An example of a test using PHPUnit. The point is to see how easy it is to
 * pass the UserCollection constructor an alternative implementation of
 * DbCollection.
 */
class UserCollection extends PHPUnit_Framework_TestCase
{
    public function testGetAllComments()
    {
        $mockedMethods = array('query');
        $dbMock = $this->getMock('DbConnection', $mockedMethods);
        $dbMock->expects($this->any())
               ->method('query')
               ->will($this->returnValue(array('John', 'George')));

        $userCollection = new UserCollection($dbMock);
        $allUsers       = $userCollection->getAll();

        $this->assertEquals(array('John', 'George'), $allUsers);
    }
}

A única situação em que eu usaria (e os usei para imitar o objeto de protótipo JavaScript no PHP 5.3) membros estáticos é quando eu sei que o respectivo campo terá o mesmo valor entre instâncias. Nesse ponto, você pode usar uma propriedade estática e talvez um par de métodos estáticos getter / setter. De qualquer forma, não se esqueça de adicionar a possibilidade de substituir o membro estático por um membro da instância. Por exemplo, o Zend Framework estava usando uma propriedade estática para especificar o nome da classe do adaptador DB usada nas instâncias de Zend_Db_Table. Faz algum tempo desde que eu os usei, para que não seja mais relevante, mas é assim que me lembro.

Métodos estáticos que não lidam com propriedades estáticas devem ser funções. PHP tem funções e devemos usá-las.

Ionuț G. Stan
fonte
1
@ Ionut, não duvido nem um pouco. Sei que também há valor na posição / papel; no entanto, como tudo relacionado ao XP / Agile, ele tem um nome realmente floozy.
jason
2
"Injeção de dependência" Eu acredito que é o termo complicado demais para isso. MUITAS e muitas leituras sobre o SO e o google-land. No que diz respeito aos "singletons", aqui está algo que realmente me fez pensar: slideshare.net/go_oh/… Uma palestra sobre por que os singletons do PHP realmente não fazem nenhum sentido. Espero que isso contribui um pouco para uma velha questão :)
dudewad
22

Assim, no PHP estático pode ser aplicado a funções ou variáveis. Variáveis ​​não estáticas estão vinculadas a uma instância específica de uma classe. Métodos não estáticos agem em uma instância de uma classe. Então, vamos criar uma classe chamada BlogPost.

titleseria um membro não estático. Ele contém o título dessa postagem no blog. Também podemos ter um método chamado find_related(). Não é estático porque requer informações de uma instância específica da classe de postagem do blog.

Essa classe seria algo parecido com isto:

class blog_post {
    public $title;
    public $my_dao;

    public function find_related() {
        $this->my_dao->find_all_with_title_words($this->title);
    }
}

Por outro lado, usando funções estáticas, você pode escrever uma classe como esta:

class blog_post_helper {
    public static function find_related($blog_post) {
         // Do stuff.
    }
}

Nesse caso, como a função é estática e não está atuando em nenhuma postagem do blog em particular, você deve transmiti-la como argumento.

Fundamentalmente, essa é uma pergunta sobre design orientado a objetos. Suas classes são os substantivos em seu sistema, e as funções que atuam sobre eles são os verbos. As funções estáticas são procedurais. Você passa o objeto das funções como argumentos.


Atualização: eu também acrescentaria que a decisão raramente ocorre entre métodos de instância e métodos estáticos e mais entre o uso de classes e o uso de matrizes associativas. Por exemplo, em um aplicativo de blog, você lê postagens de blog do banco de dados e as converte em objetos ou as deixa no conjunto de resultados e as trata como matrizes associativas. Em seguida, você escreve funções que usam matrizes associativas ou listas de matrizes associativas como argumentos.

No cenário OO, você escreve métodos em sua BlogPostclasse que atuam em postagens individuais e métodos estáticos que atuam em coleções de postagens.

Rafe
fonte
4
Esta resposta é muito boa, especialmente com a atualização que você fez, porque foi por isso que acabei com essa pergunta. Entendo a funcionalidade de "::" vs "$ this", mas quando você adiciona em conta, o exemplo que você deu dos posts do blog sendo extraídos de um banco de dados em um array associativo. Ele adiciona uma nova dimensão, ou seja, também no tom subjacente desta questão.
Gerben Jacobs 28/03
Esta é uma das melhores respostas práticas (versus teóricas) para essa pergunta muito solicitada sobre PHP, o adendo é muito útil. Eu acho que esta é a resposta que muitos programadores de PHP estão procurando, votados!
precisa saber é
14

É tudo apenas estilo?

Um longo caminho, sim. Você pode escrever programas perfeitamente orientados a objetos sem usar membros estáticos. De fato, algumas pessoas argumentam que os membros estáticos são uma impureza em primeiro lugar. Eu sugeriria que - como iniciante no oop - você tente evitar membros estáticos todos juntos. Isso o forçará a escrever em um estilo orientado a objetos e não a procedimentos .

Troelskn
fonte
13

Eu tenho uma abordagem diferente para a maioria das respostas aqui, especialmente ao usar PHP. Eu acho que todas as classes devem ser estáticas, a menos que você tenha um bom motivo. Alguns dos motivos "por que não" são:

  • Você precisa de várias instâncias da classe
  • Sua classe precisa ser ampliada
  • Partes do seu código não podem compartilhar as variáveis ​​de classe com outras partes

Deixe-me dar um exemplo. Como todo script PHP produz código HTML, minha estrutura possui uma classe de gravador html. Isso garante que nenhuma outra classe tente escrever HTML, pois é uma tarefa especializada que deve ser concentrada em uma única classe.

Normalmente, você usaria a classe html assim:

html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();

Toda vez que get_buffer () é chamado, ele redefine tudo para que a próxima classe a usar o gravador html inicie em um estado conhecido.

Todas as minhas classes estáticas têm uma função init () que precisa ser chamada antes que a classe seja usada pela primeira vez. Isso é mais por convenção do que uma necessidade.

A alternativa para uma classe estática nesse caso é confusa. Você não gostaria que todas as classes que precisam escrever um pouquinho de html tivessem que gerenciar uma instância do gravador de html.

Agora vou dar um exemplo de quando não usar classes estáticas. Minha classe de formulário gerencia uma lista de elementos de formulário, como entradas de texto, listas suspensas e muito mais. É normalmente usado assim:

$form = new form(stuff here);
$form->add(new text(stuff here));
$form->add(new submit(stuff here));
$form->render(); // Creates the entire form using the html class

Não há como você fazer isso com classes estáticas, especialmente considerando que alguns dos construtores de cada classe adicionada fazem muito trabalho. Além disso, a cadeia de herança de todos os elementos é bastante complexa. Portanto, este é um exemplo claro de onde classes estáticas não devem ser usadas.

A maioria das classes de utilitários, como as que convertem / formatam strings, são boas candidatas por serem uma classe estática. Minha regra é simples: tudo fica estático no PHP, a menos que haja uma razão para isso não acontecer.

JG Estiot
fonte
você pode escrever um exemplo do ponto abaixo "Partes do seu código não podem compartilhar as variáveis ​​de classe com outras partes" #JG Estiot
khurshed alam
10

"Ter uma chamada de método estático dentro de outro método é realmente pior do que importar uma variável global". (defina "pior") ... e "Métodos estáticos que não lidam com propriedades estáticas devem ser funções".

Essas são duas declarações bastante amplas. Se eu tiver um conjunto de funções relacionadas ao assunto, mas os dados da instância forem totalmente inapropriados, prefiro defini-los em uma classe e não cada um no espaço de nomes global. Estou apenas usando a mecânica disponível no PHP5 para

  • dê a todos um espaço para nome - evitando conflitos de nome
  • mantenha-os fisicamente localizados juntos, em vez de se espalharem por um projeto - outros desenvolvedores podem encontrar mais facilmente o que já está disponível e são menos propensos a reinventar a roda
  • deixe-me usar consts de classe em vez de definições globais para quaisquer valores mágicos

é apenas uma maneira conveniente de impor maior coesão e menor acoplamento.

E FWIW - não existe, pelo menos no PHP5, como "classes estáticas"; métodos e propriedades podem ser estáticos. Para impedir a instanciação da classe, pode-se declarar abstrata também.

grantwparks
fonte
2
"Para impedir a instanciação da classe, pode-se declara-la abstrata, também" - eu gosto muito disso. Eu já li sobre pessoas fazendo __construct () uma função particular, mas acho que se eu precisar, eu provavelmente vai usar abstrato
Kavi Siegel
7

Primeiro pergunte a si mesmo: o que esse objeto vai representar? Uma instância de objeto é boa para operar em conjuntos separados de dados dinâmicos.

Um bom exemplo seria o ORM ou a camada de abstração do banco de dados. Você pode ter várias conexões com o banco de dados.

$db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1));
$db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2));

Agora essas duas conexões podem operar independentemente:

$someRecordsFromDb1 = $db1->getRows($selectStatement);
$someRecordsFromDb2 = $db2->getRows($selectStatement);

Agora, dentro deste pacote / biblioteca, pode haver outras classes, como Db_Row, etc. para representar uma linha específica retornada de uma instrução SELECT. Se essa classe Db_Row fosse uma classe estática, isso supõe que você tenha apenas uma linha de dados em um banco de dados e seria impossível fazer o que uma instância de objeto poderia. Com uma instância, agora você pode ter um número ilimitado de linhas em um número ilimitado de tabelas em um número ilimitado de bancos de dados. O único limite é o hardware do servidor;).

Por exemplo, se o método getRows no objeto Db retornar uma matriz de objetos Db_Row, agora você poderá operar em cada linha independentemente uma da outra:

foreach ($someRecordsFromDb1 as $row) {
    // change some values
    $row->someFieldValue = 'I am the value for someFieldValue';
    $row->anotherDbField = 1;

    // now save that record/row
    $row->save();
}

foreach ($someRecordsFromDb2 as $row) {
    // delete a row
    $row->delete();
}

Um bom exemplo de uma classe estática seria algo que lida com variáveis ​​de registro ou variáveis ​​de sessão, pois haverá apenas um registro ou uma sessão por usuário.

Em uma parte do seu aplicativo:

Session::set('someVar', 'toThisValue');

E em outra parte:

Session::get('someVar'); // returns 'toThisValue'

Como sempre haverá um usuário por vez por sessão, não há sentido em criar uma instância para a Sessão.

Espero que isso ajude, juntamente com as outras respostas para ajudar a esclarecer as coisas. Como nota lateral, consulte " coesão " e " acoplamento ". Eles descrevem algumas práticas muito, muito boas para usar ao escrever seu código que se aplica a todas as linguagens de programação.

Tres
fonte
6

Se sua classe é estática, isso significa que você não pode passar seu objeto para outras classes (já que não há instância possível). Isso significa que todas as suas classes usarão diretamente essa classe estática, o que significa que seu código agora está fortemente associado à classe .

Um acoplamento rígido torna seu código menos reutilizável, frágil e propenso a erros. Você deseja evitar classes estáticas para poder passar a instância da classe para outras classes.

E sim, essa é apenas uma das muitas outras razões, algumas das quais já foram mencionadas.

Muhammad Hasan Khan
fonte
3

Em geral, você deve usar variáveis ​​de membro e funções de membro, a menos que seja absolutamente necessário compartilhar entre todas as instâncias ou a menos que você esteja criando um singleton. O uso de dados e funções de membro permite reutilizar suas funções para várias partes de dados diferentes, enquanto você só pode ter uma cópia dos dados nos quais opera se usar dados e funções estáticas. Além disso, embora não seja aplicável ao PHP, funções e dados estáticos fazem com que o código não seja reentrante, enquanto os dados da classe facilitam a reentrada.

Michael Aaron Safyan
fonte
3

Eu gostaria de dizer que há definitivamente um caso em que eu gostaria de variáveis ​​estáticas - em aplicativos em vários idiomas. Você pode ter uma classe para a qual passa uma linguagem (por exemplo, $ _SESSION ['language']) e, por sua vez, acessa outras classes projetadas da seguinte forma:

Srings.php //The main class to access
StringsENUS.php  //English/US 
StringsESAR.php  //Spanish/Argentina
//...etc

O uso de Strings :: getString ("somestring") é uma boa maneira de abstrair o uso do idioma do seu aplicativo. Você poderia fazê-lo como quisesse, mas nesse caso, cada arquivo de strings possui constantes com valores de strings acessados ​​pela classe Strings, que funcionam muito bem.

cara
fonte