Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Manoel Barros

Dúvidas Object Calisthenics em PHP

Recommended Posts

Ola pessoal

Estou estudando object calisthenics, e tive algumas dúvidas, sobre:

1) Encapsule todos os tipos primitivos e Strings, mas caso ele tenha comportamento.
O que é uma variável que contem tipo primitivo possui um comportamento?


2) Use coleções de primeiro nível.
Qualquer classe que contenha uma coleção, não deve conter outras propriedades

Então a classe coleção teria somente uma única propriedade?

Ou essa regra diz que a classe coleção pode conter sim outras propriedades, porem que uma dessas propriedades, seja somente exclusiva para guardar as instâncias, que no caso seria as coleções. Correto? Somente uma propriedade teria o privilégio de guardar as coleções.

Exemplo:

class Itens extends Iterator
{
	private $itens = [];

	public function add(Item $item)
	{
		// do add
	}

	public function filter(array $filter)
	{
		// do filter
	}

	public function merge(Itens $itens)
	{
		// do merge
	}

}

No exemplo acima, see.. caso a classe Itens precisa-se de mais uma outra qualquer propriedade alem da $items, seria um erro fazer isso? pois só pode conter 1 propriedade




3) Um ÚNICO operador de objeto por linha

Vários níveis de instâncias estão sendo acessado e é isso que deve ser evitado. Correto?

$this->manager->getConfig()->getSegment()->setName("foo");

Neste próximo exemplo, somente um nível sendo acessado. O correto a se fazer.

$filterChain
	->addFilter(new Zend_Filter_Alpha())
	->addFilter(new Zend_Filter_StringToLower);

Então se for usar interface fluente, sempre usar o mesmo nome de método da instância? E evitar acessar vários níveis de instancias?



4) Sem getters / setters / propriedades
Isso que dizer que devemos evitar getters e setters, sempre que possível??

Li artigos onde falava que getters e setters são perigosos, e devem ser evitados e já outros artigos incentivando o uso.. então fiquei na dúvida dessa regra

Desde já agradeço

Compartilhar este post


Link para o post
Compartilhar em outros sites

É importante lembrar que Object Calisthenics são recomendações. Alguns poucos afetam a performance (else e o principal).

1) Encapsule todos os tipos primitivos e Strings, mas caso ele tenha comportamento.
O que é uma variável que contem tipo primitivo possui um comportamento?

Esse "mas" é que está estranho. Mas em linhas gerais, deve encapsular tudo para prevenir o Anti-Pattern Primitive Object.

O comportamento, dito ai, é voltado para usos que, por exemplo, um Integer não consegue expressar, por si só, o seu sentido ou não é tão bom quanto outro. Mas esse mesmo Integer, tem aplicações diferente, que é o caso do ValueObject.

Tanto horas quanto anos são integers, mas um não pode ser usado no lugar do outro. Se um método estiver pedindo uma entrada integer, o nome do método ou o nome do parâmetro deve especificar o que espera receber. Com a tipagem de uma classe, além do seu código possuir uma maior coesão, torna seu código mais fácil de ser lido.

Outro ponto são os ValueObjects, que pode ser visto no link acima ou no exemplo abaixo:
http://forum.imasters.com.br/topic/541651-entidade-x-value-object/

Como por exemplo, dinheiro é um float/double. Entretanto, não existe dinheiro negativo, você apenas pode creditar ou debitar uma quantia positiva de um valor. Logo, toda a classe que necessitar de um valor monetário, terá de possuir uma validação para isso.

2) Use coleções de primeiro nível.
Qualquer classe que contenha uma coleção, não deve conter outras propriedades

Então a classe coleção teria somente uma única propriedade?

Ou essa regra diz que a classe coleção pode conter sim outras propriedades, porem que uma dessas propriedades, seja somente exclusiva para guardar as instâncias, que no caso seria as coleções. Correto? Somente uma propriedade teria o privilégio de guardar as coleções.

Exemplo:

class Itens extends Iterator
{
	private $itens = [];

	public function add(Item $item)
	{
		// do add
	}

	public function filter(array $filter)
	{
		// do filter
	}

	public function merge(Itens $itens)
	{
		// do merge
	}

}
No exemplo acima, see.. caso a classe Itens precisa-se de mais uma outra qualquer propriedade alem da $items, seria um erro fazer isso? pois só pode conter 1 propriedade


Na verdade, é uma regra de substituição para separar responsabilidades. Uma classe que possua uma lista, não deve implementar comportamentos dessa lista.

Como os comportamentos padrões de um Array não são reproduzidos de forma orientada à objetos, você pode usar uma classe genérica ou implementar a sua própria classe.

Entretanto, isso não é verdade para o PHP (mas é para C# e Java).

Por exemplo, no PHP, não existe um tipo de lista que é tipada, mas existe no Java e no C#. Você pode criar uma lista que permite apenas um tipo de objeto como parâmetro.

Pode ver um exemplo implementado no PHP aqui:
http://forum.imasters.com.br/topic/537066-tutorial-integridade-de-colecoes-com-uso-de-iterator/

3) Um ÚNICO operador de objeto por linha

Vários níveis de instâncias estão sendo acessado e é isso que deve ser evitado. Correto?

$this->manager->getConfig()->getSegment()->setName("foo");
Neste próximo exemplo, somente um nível sendo acessado. O correto a se fazer.
$filterChain
	->addFilter(new Zend_Filter_Alpha())
	->addFilter(new Zend_Filter_StringToLower);
Então se for usar interface fluente, sempre usar o mesmo nome de método da instância? E evitar acessar vários níveis de instancias?

Essa definição, conhecida como Law of Demeter, é exemplificada, por Robert C. Martin, com Train Wrecks. É uma regra baseada no "knows too much" (sabe de mais). Se um classe precisa ir "tão a fundo" em um objeto, ele ou está sabendo demais, ou está recebendo mais do que o necessário. Ou até pode estar fazendo mais do que a sua responsabilidade.

O segundo exemplo é uma exceção pois não se trata de saber demais, é apenas o uso de fluent interface.

Não há muita o que explicar sobre essa regra, existem casos e casos. Normalmente, quando a lei de Demeter é violada, outras princípios também o são.

4) Sem getters / setters / propriedades
Isso que dizer que devemos evitar getters e setters, sempre que possível??

Li artigos onde falava que getters e setters são perigosos, e devem ser evitados e já outros artigos incentivando o uso.. então fiquei na dúvida dessa regra


Desde já agradeço

Essa é outra interpretativa e relativa. Métodos get/setter devem ser usados para retornar/inserir algo. Entretanto, você deve dizer para que o objeto faça algo e não fazer perguntas sobre o seu estado para realizar as ações.

Nesse caso, é importante entender, também, sobre encapsulamento.

Veja abaixo, valores dispersos não fazem sentido algum:

$x = 2;
$y = 1;

As variáveis acima não possuem sentido algum sem nenhum contexto. Entretanto, encapsulando-as, entraremos em um contexto.

Class Rectangle 
{
    private $x;

    private $y;


    public function setX(int $x)
    {
        $this->x = $x;
    }

    public function setY(int $y)
    {
        $this->y = $y;
    }

    public function getX() : int
    {
        return $this->x;
    }

    public function getY() : int
    {
        return $this->y;
    }
}

E agora, eu preciso calcular a área desse retângulo:

$rectangle = new Rectangle();
$rectangle->setX(10);
$rectangle->setY(5);

printf("A área do retângulo é %d : " , $rectangle->getX() * $rectangle->getY());

É nesse momento que mora o problema. As dimensões de um retângulo não fazem o menor sentido fora do seu contexto. Outro ponto, é a necessidade de um cálculo externo. O cálculo nada mais é que a representatividade da área e, nesse caso, ela não faz o menor sentido fora do retângulo.

Logo, essa situação pode ser revertida adicionando a responsabilidade sobre o cálculo ao retângulo e removendo a possibilidade de usar os valores de forma externa.

Class Rectangle 
{
    private $x;

    private $y;

    public function setX(int $x)
    {
        $this->x = $x;
    }

    public function setY(int $y)
    {
        $this->y = $y;
    }

    public function getArea() : int
    {
        return $this->x * $this->y;
    }
}

Dessa forma, agora, o cálculo da área é específico do retângulo (tal qual é de um triângulo, trapézio, etc..). Outra situação, que pode ser considerada, é o fato do retângulo poder ter seu estado alterado após a sua instância (o que, nesse caso, não é muito comum).

Assim, pode-se mudar o setters para o seu construtor:

Class Rectangle 
{
    private $x;

    private $y;

    public function __construct(int $x , int $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getArea() : int
    {
        return $this->x * $this->y;
    }
}

E o seu uso:

$rectangle = new Rectangle(10 , 5);
printf("A área do retângulo é %d : " , $rectangle->getArea());

Todo o processo, acima demonstrado, é conhecido como Tell, don't ask.

Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.
— Alec Sharp

A explicação do link abaixo é interessante também:

http://stackoverflow.com/questions/565095/are-getters-and-setters-poor-design-contradictory-advice-seen

Entretanto, cada caso deve ser considerado diferente. Não é porquê, nesse exemplo, foi feito dessa forma, que todos os outros serão.

No mais, object calisthenics foram criadas pela comunidade Java e portadas para o PHP. Em versões mais antigas do PHP, encapsular tipos primitivos era ruim, pois o PHP não gerenciava bem quando uma aplicação criava muitos objetos. Hoje em dia, isso é diferente.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.