Ir para conteúdo

Arquivado

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

Prove Yourself

Dependências entre um objeto / SRP

Recommended Posts

Criei uma classe Usuários:

<?php
class User
{
    private $_name;

    public function setName($name)
    {
        if (strlen($name) < 2) {
            throw new User_Exception('');
        }
        if (!preg_match('#^[\w -]+$#u', $name)) {
            throw new User_Exception('');
        }
        $this->_name = $name;
    }

    public function getName()
    {
        return $this->_name;
    }
    // Mais código ...
}

Percebi que o método setName faz muita coisa, quebrando o princípio da responsabilidade única. Primeiro ele verifica se o nome passado não está vazio, depois verifica se o nome é válido ... Então eu pensei em mover as validações para outros métodos privados, tendo cada método fazendo apenas uma coisa.

Em outras situações eu poderia separar em classes ... A validação de um e-mail, por exemplo. Um usuário não deve saber como validar um e-mail. Dessa forma, eu separaria os conceitos, só que teria que lidar com mais uma classe, gerando uma dependência. Por outro lado, eu poderia aproveitar a mesma classe em outras partes da aplicação.

Agora imaginem que meu objeto User tenha relação com mais classes de validação. Não quero instanciar os objetos de validação dentro da classe User porque isso geraria um acoplamento alto. Se eu passar as dependências por paramêtro terei muitos parâmetros ... Como vocês lidariam com isso? Até onde devo dividir uma classe/método? Quanto mais dividir, mas dependências/objetos para manipular.

Valeu.

Compartilhar este post


Link para o post
Compartilhar em outros sites

   public function setName($name)
   {
       if (strlen($name) < 2) {
           throw new User_Exception('');
       }
       if (!preg_match('#^[\w -]+$#u', $name)) {
           throw new User_Exception('');
       }
       $this->_name = $name;
   }

 

Percebi que o método setName faz muita coisa, quebrando o princípio da responsabilidade única.

 

Não confunda "fazer muita coisa" com "ter muitas responsabilidades".

 

Sua classe User tem como responsabilidade representar um usuário. Validar uma informação ao defini-la não representa uma violação a SRP.

 

Entendido que o código acima não viola S.R.P., ficamos com a questão da granularidade:

 

1. Repete em outros pontos da classe?

R. Separe em um método específico para isso.

 

2. Repete em outros participantes?

2.1. Esses participantes fazem parte da mesma família?

R. Separe em uma classe base e derive as classes filhas.

2.2 Esses participantes não fazem parte da mesma família?

2.2.1. Crie uma interface para validação e uma implementação para essa interface.

2.2.2. Faça com que os participantes dependam da interface, não da implementação.

2.2.3. Crie uma fábrica abstrata para toda a família de "validadores" e injete a fábrica nos participantes precisarem utilizá-la.

Compartilhar este post


Link para o post
Compartilhar em outros sites

João Batista Neto, antes de mais nada, obrigado por mais uma aula de OO ...

 

Acho que entendi a diferença de "fazer muita coisa" com "ter muitas responsabilidades". Fiz uma modificação rápida (sem testar):

<?php
class User
{
   private $_name;

   public function setName($name)
   {
       $this->_validateLength(2, $name);
       $this->_validateName($name);
       $this->_name = $name;
   }

   // Repete em outros participantes? Não, mas eu separei. Isso é ruim?
   private function _validateName($name)
   {
       if (!preg_match('#^[\w -]+$#u', $name)) {
           throw new User_Exception('');
       }
   }

   // Repete em outros participantes? Sim, pode ser usado por outros sets ...
   private function _validateLength($length, $value) {
       if (strlen($value) < $length) {
           throw new User_Exception('');
       }
   }

   public function getName()
   {
       return $this->_name;
   }
   // Mais código ...
}

"2. Repete em outros participantes?". Isso quer dizer que eu só vou colocar para fora dessa classe se outra classe no sistema precisar ... Não seria uma boa generalizar, mesmo que outros não precisem agora?

 

Muito obrigado.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Acho que entendi a diferença de "fazer muita coisa" com "ter muitas responsabilidades".

 

:natallaugh:

 

"2. Repete em outros participantes?". Isso quer dizer que eu só vou colocar para fora dessa classe se outra classe no sistema precisar ...

 

Exato.

 

Não seria uma boa generalizar, mesmo que outros não precisem agora?

 

Validação é um problema recorrente, então pode sim ser interessante ter um pacote específico para isso.

 

Um bom design para validação é Chain Of Responsibility

Compartilhar este post


Link para o post
Compartilhar em outros sites

Então nem vou mais perguntar a respeito do exemplo da validação do E-mail :)

 

Vou ler com calma sobre esse design pattern. Vi por cima mas já achei complicado, ainda não conheço o Command ...

 

João, você usa TDD para guiar seu design? Como você faz essas decisões, é intuição mesmo ("Validação é um problema recorrente ...")? Estou arranhando TDD e esse código aí foi feito apartir dos testes ...

 

Muito obrigado mais uma vez.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Vou ler com calma sobre esse design pattern. Vi por cima mas já achei complicado, ainda não conheço o Command ...

 

Na verdade, é muito simples.

 

O Command encapsula uma requisição em um objeto :seta: http://forum.imaster...30#entry1686430

 

Chain Of Responsibility: Comportamental

 

Intenção:

Desacoplar quem envia a requisição de quem recebe a requisição. Com CoR, mais do que um objeto tem a chance de manipular uma mesma requisição. Os objetos manipuladores são encadeados e a requisição é passada pela cadeia até que um objeto possa manipulá-la.

Motivação:

Em muitas situações teremos objetos capazes de manipular determinada requisição, mas que são dependentes de um contexto. No caso da validação, muitas vezes, precisaremos utilizar múltiplos validadores para determinada informação.

 

Imagine que você tenha um conjunto de dados enviado pelo usuário. Entre esses dados você terá um campo que contém um número de cartão de crédito.

 

1. O número do cartão, normalmente, possui uma máscara (formato com pontos e traços).

2. Dependendo da bandeira, deverá possuir entre 13 e 16 caracteres.

3. Possui um algorítimo que valida um dígito verificador baseado em módulo.

 

A validação de máscara, sozinha, é uma validação recorrente. (telefone, cep, cpf e várias informações a utilizam)

A validação de limite de caracteres é outra validação.

etc.

 

A ideia desse padrão é desacoplar os objetos que enviam determinada requisição daqueles que podem manipulá-la. Com Chain Of Responsibility, quem envia a requisição não precisa conhecer todos os objetos da cadeia.

Aplicações:

Você pode utilizar Chain Of Responsibility quando:

1. Vários objetos podem manipular uma mesma requisição.

2. Você quer passar uma requisição por vários objetos sem ter que especificar explicitamente o recebedor.

3. Os objetos que manipulam a requisição precisam ser especificados dinamicamente.

 

Estrutura:

 

gallery_94216_34_114.png

Participantes:

Handler:

Define a interface para manipulação da requisição.

Opcionalmente implementa o link para o sucessor na cadeia.

ConcreteHandler:

Manipula uma requisição se ele for responsável por ela.

Tem acesso ao seu sucessor.

Pode encaminhar a requisição ao seu sucessor, se não for capaz de manipular a requisição.

Client:

Inicializa a requisição no ConcreteHandler da cadeia.

Colaborações:

Quando o client inicia uma requisição, essa requisição propaga por toda a cadeia até que um objeto (ou vários) consiga(m) manipulá-la.

Consequências:

1. Acoplamento reduzido.
O padrão permite que os objetos desconheçam qual, ou quais, objetos manipulam determinada requisição. Um objeto só precisa saber que determinada requisição será manipulada. Nem quem envia a requisição, como quem a recebe, tem conhecimento explícito um do outro e o objeto que manipula a requisição não precisa ter conhecimento sobre a cadeia por onde a requisição propagou.

 

Como consequência, Chain Of Responsibility consegue simplificar um determinado processo pois, em vez dos objetos precisarem conhecer todos os possíveis candidatos para manipulação de determinada requisição, eles guardam apenas uma referência ao seu sucessor.

 

2. Flexibilidade na delegação de responsabilidades.
Chain Of Responsibility facilita o processo de delegar responsabilidades aos objetos, permitindo que você adicione um novo ou modifique um objeto na cadeia em tempo de execução.

 

Você também consegue, através de herança, especializar os manipuladores para tarefas mais específicas.

 

3. Não há garantia de que a requisição será manipulada.
Como uma requisição propaga-se por toda a cadeia, sem saber quem a manipulará, ela poderá chegar ao fim da cadeia sem que ninguém tenha assumido a responsabilidade pela tarefa.

Implementação:

gallery_94216_5_2110.png

 

Nesse caso, AbstractDataValidation é nosso Handler. O link para o sucessor da cadeia é implementado aqui e um método de interface é oferecido para que os ConcreteHandlers possam acessá-lo.

 

Outro detalhe dessa implementação específica é que todos os objetos da cadeia "manipularão" a requisição. Cada um com sua responsabilidade, mas a verificação falhará se qualquer objeto da cadeia invalidar a requisição.

 

com/imasters/data/validation/ValidationContext.php

<?php
namespace com\imasters\data\validation;

use \ArrayAccess;
use \InvalidArgumentException;
use \UnexpectedValueException;

class ValidationContext implements ArrayAccess {
/**
 * @var	array
 */
private $context = array();

/**
 * @param	string $key
 * @return	mixed
 * @throws	\InvalidArgumentException
 */
public function get( $key ) {
	if ( $this->has( $key ) ) {
		return $this->context[ $key ];
	}
}

/**
 * @param	string $key
 * @return	boolean
 * @throws	\InvalidArgumentException
 */
public function has( $key ) {
	if ( !is_string( $key ) ) {
		var_dump( $key );
		throw new InvalidArgumentException( '$key deve ser uma string' );
	}

	return isset( $this->context[ $key ] );
}

/**
 * @param	string $key
 * @return	boolean
 * @see		\ArrayAccess::offsetGet()
 * @see		com\imasters\data\validation\ValidationContext::get()
 * @throws	\InvalidArgumentException
 */
public final function offsetExists( $key ) {
	return !!$this->has( $key );
}

/**
 * @param	string $key
 * @return	mixed
 * @see		\ArrayAccess::offsetGet()
 * @see		com\imasters\data\validation\ValidationContext::get()
 * @throws	\InvalidArgumentException
 */
public final function offsetGet( $key ) {
	return $this->get( $key );
}

/**
 * @param	string $key
 * @param	mixed $value
 * @see		\ArrayAccess::offsetSet()
 * @see		com\imasters\data\validation\ValidationContext::set()
 * @throws	\InvalidArgumentException
 * @throws	\UnexpectedValueException
 */
public final function offsetSet( $key , $value ) {
	$this->set( $key , $value );
}

/**
 * @param	string $key
 * @see		\ArrayAccess::offsetUnset()
 * @see		com\imasters\data\validation\ValidationContext::remove()
 * @throws	\InvalidArgumentException
 */
public final function offsetUnset( $key ) {
	$this->remove( $key );
}

/**
 * @param	string $key
 * @throws	\InvalidArgumentException
 */
public function remove( $key ) {
	if ( $this->has( $key ) ) {
		unset( $this->context[ $key ] );
	}
}

/**
 * @param	string $key
 * @param	mixed $value
 * @throws	\InvalidArgumentException
 * @throws	\UnexpectedValueException
 */
public function set( $key , $value ) {
	if ( $this->has( $key ) ) {
		throw new UnexpectedValueException( $key . ' já está definida.' );
	}

	$this->context[ $key ] = $value;
}
}

 

ValidationContext é apenas para mapearmos algumas chaves com seus respectivos valores. Eu ia implementar uma validação em lote também, mas ia ficar muito grande para postar aqui.

 

com/imasters/data/validation/AbstractDataValidation.php

<?php
namespace com\imasters\data\validation;

abstract class AbstractDataValidation {
/**
 * @var	com\imasters\data\validation\ValidationContext
 */
private $context;

/**
 * @var	AbstractDataValidation
 */
private $successor;

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

/**
 * @param	string $key
 * @return	boolean
 */
public function check( $key ) {
	if ( $this->hasSuccessor() ) {
		return $this->successor()->check( $key );
	}

	return true;
}

/**
 * @return	com\imasters\data\validation\ValidationContext
 */
public function context() {
	if ( $this->context == null ) {
		$this->createContext();
	}

	return $this->context;
}

/**
 * @return	com\imasters\data\validation\ValidationContext
 */
public function createContext() {
	$context = new ValidationContext();

	$this->setContext( $context );

	return $context;
}

/**
 * @return	boolean
 */
public function hasSuccessor() {
	return $this->successor !== null;
}

/**
 * @param	com\imasters\data\validation\ValidationContext $context
 */
public function setContext( ValidationContext $context ) {
	$this->context = $context;

	if ( $this->hasSuccessor() ) {
		$successor = $this->successor();
		$successor->setContext( $this->context );
	}
}

/**
 * @param	com\imasters\data\validation\AbstractDataValidation $successor
 * @return	com\imasters\data\validation\AbstractDataValidation
 */
public function setSuccessor( AbstractDataValidation $successor ) {
	$this->successor = $successor;
	$this->successor->setContext( $this->context() );

	return $successor;
}

/**
 * @return	com\imasters\data\validation\AbstractDataValidation
 */
public function successor() {
	return $this->successor;
}
}

 

com/imasters/data/validation/AllowedCharacterCheck.php

<?php
namespace com\imasters\data\validation;

class AllowedCharacterCheck extends AbstractDataValidation {
/**
 * @var	string
 */
private $pattern = '/^$/';

/**
 * @see		com\imasters\data\validation\AbstractDataValidation::check()
 */
public function check( $key ) {
	$value = $this->context()->get( $key );

	if ( preg_match( $this->pattern , $value ) ) {
		return parent::check( $key );
	}

	return false;
}

/**
 * @param	string $pattern
 * @return	com\imasters\data\validation\datatype\AllowedCharacterCheck
 */
public function pattern( $pattern ) {
	$this->pattern = $pattern;

	return $this;
}
}

 

com/imasters/data/validation/CheckDigit.php

<?php
namespace com\imasters\data\validation;

abstract class CheckDigit extends AbstractDataValidation {
}

 

com/imasters/data/validation/checkdigit/CPFCheckDigit.php

<?php
namespace com\imasters\data\validation\checkdigit;

use com\imasters\data\validation\CheckDigit;

class CPFCheckDigit extends CheckDigit {
/**
 * @see		com\imasters\data\validation\AbstractDataValidation::check()
 */
public function check( $key ) {
	$cpf = (string) $this->context()->get( $key );

	if ( ( strlen( $cpf = preg_replace ( '/[^\d]/', '', $cpf ) ) == 11 )&&
		 ( str_repeat( $cpf{1} , 11 ) != $cpf ) ) {

		for( $i = 0, $v = 0, $t = 10; $i < 11; ++ $i ) if ( $i < 9 )
			$v += $cpf{$i} * $t --;

		$v1 = ($x = $v % 11) < 2 ? 0 : 11 - $x;

		for( $i = 0, $v = 0, $t = 11; $i < 10; ++ $i )
			$v += $cpf{$i} * $t --;

		$v2 = ($x = $v % 11) < 2 ? 0 : 11 - $x;

		if ( sprintf ( '%s%s', $v1, $v2 ) == substr ( $cpf, 9, 2 ) ) {
			return parent::check( $key );
		}
	}

	return false;
}
}

 

com/imasters/data/validation/DataTypeCheck.php

<?php
namespace com\imasters\data\validation;

abstract class DataTypeCheck extends AbstractDataValidation {
}

 

com/imasters/data/validation/datatype/StringTypeCheck.php

<?php
namespace com\imasters\data\validation\datatype;

use com\imasters\data\validation\DataTypeCheck;

class StringTypeCheck extends DataTypeCheck {
/**
 * @see		com\imasters\data\validation\AbstractDataValidation::check()
 */
public function check( $key ) {
	$value = $this->context()->get( $key );

	if ( is_string( $value ) ) {
		return parent::check( $key );
	}

	return false;
}
}

 

Usando:

 

<?php
use com\imasters\data\validation\checkdigit\CPFCheckDigit;
use com\imasters\data\validation\AllowedCharacterCheck;
use com\imasters\data\validation\datatype\StringTypeCheck;

$v = new StringTypeCheck();
$v->setSuccessor( new AllowedCharacterCheck() )
 ->pattern( '/^\d{3}\.\d{3}\.\d{3}-\d{1,2}$/' )
 ->setSuccessor( new CPFCheckDigit() );

$v->context()->set( ':cpf' , $someValidCPF );

var_dump( $v->check( ':cpf' ) ); //true

 

Se você colocar um número, alguma string que não esteja no padrão permitido ou se o CPF for inválido, você receberá um false.

 

João, você usa TDD para guiar seu design?

 

Sim.

 

Como você faz essas decisões, é intuição mesmo ("Validação é um problema recorrente ...")?

 

Experiência.

 

;)

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.