quarta-feira, outubro 23, 2013

Não deixe o básico da orientação a objetos de lado, nunca!

Quando olho para trás lembro-me de quando eu era um humilde padawan que queria ser um monge Jedi em Java, e lembro claramente dos conselheiros Jedi do RSJUG falando "vá pelo caminho da orientação a objetos". Se passaram alguns muitos anos e eu ainda lembro e sou fiel a esse princípio, e quando vejo por aí classes ou entidades de banco de dados, que eu irei ter que transformar em classes¹, contendo 181 campos (não é piada!) minha gastrite quase me mata!

Eu não sei por que as pessoas teimam em ter campos como CPF, CNPJ, data de inicio e de fim, etc em certas classes. A quem vai ficar destinada a tarefa de validar um CNPJ/CPF? Ou se a data de início não é maior do que a de fim? Não tenha receio de criar classes que não serão persistidas. As pessoas tem a imagem de que classes de domínio/modelo só devem existir se forem ser persistidas, o resto é classe útil, DAO, service, etc. e não é bem por aí.

É do domínio da minha aplicação um CNPJ/CPF! E eu tenho que ter um objeto para isso, com a responsabilidade de validar se ele mesmo é válido. Não vou jogar essa responsabilidade para uma classe megazord no pacote útil responsável por validar isso e, talvez, mais coisas em um único lugar (olha o acoplamento aíííííííí gente).

Bem, para exemplificar isso, vou mostrar um código/abordagem que gosto muito, onde eu separo o campo nome da classe usuário em um objeto a parte.

Se você parar para pensar, o nome deve conter sempre 3 campos: primeiro nome, inicial do meio e último(s) nome(s). Mas e o usuário SISTEMA? Ele não tem sobrenome. E o John Doe que não tem nome do meio?

Então eu tenho a classe PersonName:

using System;
using System.Text;

namespace com.blogspot.jeanjmichel.model
{
    public class PersonName
    {
        private String firstName;
        private String middleInitial;
        private String lastName;
        
        public String FirstName
        {
            set { this.firstName = value; }
            get { return this.firstName; }
        }
        
        public String MiddleInitial
        {
            set { this.middleInitial = value; }
            get { return this.middleInitial; }
        }
        
        public String LastName
        {
            set { this.lastName = value; }
            get { return this.lastName; }
        }
        
        public PersonName()
        {
        }
        
        public PersonName(String firstName)
        {
            this.FirstName = firstName;
        }
        
        public PersonName(String firstName, String middleInitial,
                          String lastName)
        {
            this.FirstName = firstName;
            this.MiddleInitial = middleInitial;
            this.LastName = lastName;
        }
        
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            
            sb.Append(this.LastName);
            sb.Append(", ");
            sb.Append(this.FirstName);
            sb.Append(" ");
            sb.Append(this.MiddleInitial);
            
            return sb.ToString();
        }
    }
}

E agora a classe User:

using System;
using System.Text;

namespace com.blogspot.jeanjmichel.model
{
    public class User
    {
        #region attributes
        
        Int64 id;
        String username;
        PersonName personName;
        char gender;
        String password;
        char active;
        
        #endregion
        
        #region properties
        
        public Int64 Id
        {
            set { this.id = value; }
            get { return this.id; }
        }
        
        public String Username
        {
            set { this.username = value; }
            get { return this.username; }
        }
        
        public PersonName PersonName
        {
            set { this.personName = value; }
            get { return this.personName; }
        }
        
        public char Gender
        {
            set { this.gender = value; }
            get { return this.gender; }
        }
        
        public String Password
        {
            set { this.password = value; }
            get { return this.password; }
        }
        
        public char Active
        {
            set { this.active = value; }
            get { return this.active; }
        }
        
        #endregion
        
        /// <summary>
        /// Returns a string with this instance's data
        /// The format of the string is:
        /// namespace + class name + [ + the fields most representative
        /// of the class + ].
        /// </summary>
        /// <returns>Returns a string with this instance's data</returns>
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            
            sb.Append("com.blogspot.jeanjmichel.model.User [");
            sb.Append(this.PersonName);
            sb.Append(" as '");
            sb.Append(this.Username);
            sb.Append("'");
            sb.Append(" Active: ");
            sb.Append(this.Active);
            sb.Append("]");
            
            return sb.ToString();
        }
        
        public User()
        {
        }
        
        public User(String username, PersonName personName, String password,
                    char active)
        {
            this.Username = username;
            this.PersonName = personName;
            this.Password = password;
            this.Active = active;
        }
        
        public User(Int64 id, String username, PersonName personName,
                    char gender, String password, char active)
        {
            this.Id = id;
            this.Username = username;
            this.PersonName = personName;
            this.Gender = gender;
            this.Password = password;
            this.Active = active;
        }
    }
}

Agora criando um usuário:

using System;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel
{
    class Program
    {
        static void Main(string[] args)
        {
            PersonName pn = new PersonName("Jean", "J", "Michel");
            User u = new User("jmichel", pn, "Senha", 'Y');
            
            Console.WriteLine(u);
            Console.ReadKey();
        }
    }
}

A saída é: com.blogspot.jeanjmichel.model.User [Michel, Jean J as 'jmichel' Active: Y].

Quase tudo funcionou.
Mas no teste unitário quando eu digo que criando o usuário System assim:

using System;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel
{
    class Program
    {
        static void Main(string[] args)
        {
            PersonName pn = new PersonName("System");
            User u = new User("sysadmin", pn, "Senha", 'Y');
            
            Console.WriteLine(u);
            Console.ReadKey();
        }
    }
}

Eu espero o resultado: com.blogspot.jeanjmichel.model.User [System as 'sysadmin' Active: Y] o que acontece?

Failed TestUserToStringMethod TestProject Assert.AreEqual failed.
Expected:<com.blogspot.jeanjmichel.model.User [System as 'sysadmin' Active: Y]>.
Actual..:<com.blogspot.jeanjmichel.model.User [, System as 'sysadmin' Active: Y]>.


Holly crap! Então, a resposabilidade da minha classe PersonName é validar esse tipo de coisa. Refatorando-a teremos:

using System;
using System.Text;

namespace com.blogspot.jeanjmichel.model
{
    public class PersonName
    {
        private String firstName;
        private String middleInitial;
        private String lastName;
        
        public String FirstName
        {
            set { this.firstName = value; }
            get { return this.firstName; }
        }
        
        public String MiddleInitial
        {
            set { this.middleInitial = value; }
            get { return this.middleInitial; }
        }
        
        public String LastName
        {
            set { this.lastName = value; }
            get { return this.lastName; }
        }
        
        public PersonName()
        {
        }
        
        public PersonName(String firstName)
        {
            this.FirstName = firstName;
        }
        
        public PersonName(String firstName, String middleInitial,
                         String lastName)
        {
            this.FirstName = firstName;
            this.MiddleInitial = middleInitial;
            this.LastName = lastName;
        }
        
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            
            if (String.IsNullOrEmpty(this.LastName))
            {
                sb.Append(this.FirstName);
            }
            else
            {
                sb.Append(this.LastName);
                sb.Append(", ");
                sb.Append(this.FirstName);
                sb.Append(String.IsNullOrEmpty(this.MiddleInitial) ? "" :
                " " + this.MiddleInitial);
            }
            
            return sb.ToString();

        }
    }
}

Eu faço o mesmo com CNPJ/CPF, datas (eu crio uma classe chamada período) e outras.

¹ Eu não criei uma classe com 181 campos, mas também não pude refatorar a classe, a tabela, etc (software de terceiros). então criei uma classe com o set mínimo de informações da tabela, que deu mais de 30 campos, e faço o enxerto do resto com os valores padrão para cada campo. Uma pena eu não poder quebrar essa maldita tabela em meia dúzia de 8 tabelas mais produtivas e reutilizáveis. Enfim, coisas da TI que o McGyver aprovaria :)

Nenhum comentário: