4. Comentário Técnico



. SportAgent e classes derivadas
A classe SportAgent serve de suporte às quatro classes que irão conter os dados sobre jogadores (nacionais e internacionais), treinadores, dirigentes e árbitros.
Esta classe inclui duas funções virtuais puras: Clone() para devolver um apontador para um SportAgent (onde existirá uma cópia do objecto que executou a função - logo do mesmo tipo); uma função Type() que retorna uma StringBasic correspondente ao tipo de objecto (jogador, árbitro, etc).

A classe Player define um jogador. Optamos por incluir nesta classe tanto jogadores nacionais como internacionais visto ser mais lógico e apenas se desperdiçarem 4+1 bytes (de um apontador para char* na StringBasic correspondente ao país) ao lidar com jogadores nacionais. Uma outra opção seria separar ambas as classes, criando por exemplo um Player sem país e depois derivar desta classe um PlayerInternational, mas esta organização seria menos lógica e de maior complexidade.
Sendo assim para distinguir jogadores nacionais de internacionais utiliza-se a função IsNational(). A posição do jogador é definida por um enumerado e criaram-se também duas funções static para converter de uma StringBasic para a posição e vice-versa.

As classes Manager, Director e Refree não trazem nada de novo em relacção à classe Player visto serem muito semelhantes, distinguindo-se apenas pelo facto de não terem tantos membros.


. SportAgentsVectorPolymorphic
Esta classe será talvez a mais complexa de todo o programa.
Tratando-se de um array (através de um apontador incial - data member) de apontadores para SportAgents permite armazenar qualquer tipo de objecto derivado da classe SportAgent. Contudo apesar da complexidade aderente a qualquer vector polimórfico exitem apenas dois pontos a destacar nesta classe:
O operador [] utiliza o overloading para vectores const e não const devolvendo assim um objecto constante ou não, o que nos irá permitir alterar informações directamente nesse objecto.
Para além disto utilizamos também um método de leitura e escrita para objectos deste tipo ao qual se chama Prototypical Read/Write, o que não é mais que um método que está preparado para ler e escrever objectos de determinadas classes derivadas de SportAgent. Contudo temos que conhecer estas classes e alterar estas funções se pretendermos potencia-las com o suporte de um novo tipo de classe derivada de SportAgent.

De resto será de mencionar que utilizando um método semelhante a este (apontadores para apontadores do tipo a armazenar) seria possivel obter uma maior efeciência em qualquer vector através de um processo de Growing que não eleminasse objectos mas sim os mantivesse e para acrescentar um novo (quando a capacidade tivesse sido atingida) se limitasse a copiar os valores dos apontadores utilizados, refazendo o array onde se encontravam (através do operador new). Desta forma poupar-se-ia o tempo de copiar objectos inteiros, contudo este não foi o método utilizado visto não ter sido adoptado pelo Professor Pedro Guerreiro em classes do mesmo caracter (mesmo assim penso que não traria problemas).


. StringSearchable
Esta classe é como que o núcleo da performance do programa. Qualquer alteração irá afectar a preformance durante o processamento dos ficheiros...
Na secção 5. Limites explicamos porque utilizamos este sistema de busca (incluindo o processo de leitura dos ficheiros a partir da função Process() da classe OurSelection) e não um sistema utilizando classes fornecidas pelo Professor (como por exemplo o Tokenizer).

Para já será importante descrever as 3 fases de desenvolvimento desta classe (em especial da função Search() visto a função SearchAndReplace() se basear grandemente na primeira).
Numa primeira fase o processo de busca era extremamente lento, por exemplo para um ficheiro de 400 Kb que era lido em 5 ou 6 segundos (novamente uma explicação na secção seguinte sobre este facto e como poderiamos optimiza-lo) a busca de uma simples String era quase interminável:

From StringSearchable VERSÃO 1.0
int StringSearchable::Search(const StringBasic& other,
      bool ignoreCase) const
 // Please note that function IndexOf(const StringBasic& other, int start)
 // was note used due to it's unability to deal with the ignore case option
{
    int count = 0;
    int j = 0; // Travels other's chars, used in while latter

    // Using Count() all over is what makes this function soo slow
    // (optimizing StringBasic could solve this)

    for(int i = 0; i < this->Count(); i++, j = 0)
    // I had programmed this without reseting j here and it worked fine
    // and probably faster, but the code was rather hard to read so... :-)
        while((*this)[i] == other[j] && j < other.Count())
        {
            j++, i++; // get next chars, only if while acts
            if (j == other.Count()) count ++;
            // *this has all of other
        }
    return count;
}

Ora como o Count() de ambas as Strings se mantem constante durante todo o processo é desnecessário estar sempre a calcula-lo. Portanto pode-se assim optimizar esta função tal como a SearchAndReplace() que calcula (aritmeticamente, se usar a função Count()) quando o comprimento de alguma das Strings é alterado. Assim no mesmo ficheiro de 400Kb a busca de uma só palavra tornou-se imediata (mantendo-se os 5/6 segundos de leitura do ficheiro).

From StringSearchable VERSÃO 2.0
int StringSearchable::Search(const StringBasic& other,
      bool ignoreCase) const
 // Please note that function IndexOf(const StringBasic& other, int start)
 // was note used due to it's unability to deal with the ignore case option
{
    int count = 0;
    int j = 0; // Travels other's chars, used in while latter

    // Optimizing for large strings! (huge amount of time gained)
    int thisLen = this->Count();
    int otherLen = other.Count();

    for(int i = 0; i < thisLen; i++, j = 0)
    // I had programmed this without reseting j here and it worked fine
    // and probably faster, but the code was rather hard to read so... :-)
        while((*this)[i] == other[j] && j < otherLen)
        {
            j++, i++; // get next chars, only if while acts
            if (j == otherLen) count ++;
            // *this has all of other
        }
    return count;
}

Ora, como em sempre ainda foi possível proceder a mais uma optimização a quando da introdução do suporte do ignoreCase, que, como se pode constatar, ainda não tinha sido implementado nesta fase.
Dentro da expressão avaliada pelo while estamos constantemente a ler o caracter j da other String mas grande parte das ocasiões j será 0 portanto estamos a perder muito mais tempo do que é necessário. Podemos ler o caracter j da String para um caracter declarado incialmente, e isto só quando j é alterado. Assim obtivemos a classe StringSearchable actual que utiliza a função SmallLetter() da classe Character.

From StringSearchable VERSÃO 3.2
int StringSearchable::Search(const StringBasic& other,
      bool ignoreCase, int thisEND) const
{
    int count = 0;
    int j = 0;

    char otherChar = ignoreCase ? Character::SmallLetter(other[j]) : other[j];

    int thisLen = thisEND < 0 ? this->Count() : thisEND;
    int otherLen = other.Count();

    for(int i = 0; i < thisLen; i++,
            j != 0 ? (j = 0, (ignoreCase ?
            Character::SmallLetter(other[j]) : other[j])) : 0)
        while(j < otherLen && (ignoreCase ?
            Character::SmallLetter((*this)[i]) : (*this)[i]) == otherChar)
        {
            j++, i++;
            if (j == otherLen)
                count ++;
            otherChar = ignoreCase ? Character::SmallLetter(other[j]) : other[j];
        }
    return count;
}

Pois, mas apesar do esforço a perfomance, mesmo da 3ª versão, não se demonstrou próximo sequer do pretendido... Essa foi a razão porque mesmo durante o desenvolvimento deste relatório aproveitamos para voltar a alterar esta função. Utilizando a função IndexOf (que usa a função strstr() da Standart Library, e aí está a razão pela qual ganhamos tanta performance) e uma programação extremamente simples. Com o objectivo de optimizar a velocidade, a opção de ignore case está separada em duas funções diferentes (uma default sem ignoreCase, que já não é usado visto usarmos um método mais rápido a partir da função Process() da classe OurSelection).


. OurSelection
A classe OurSelection utiliza (e controla) as funções da classe MenuDisplay para a UserInterface. A classe MenuDisplay não tem nada a destacar excepto o facto de tornar o suporte de outras linguagens bastante mais simples e implementável através de um ficheiro de configuração.
As funções Run() e MainMenu() da classe OurSelection são bastante simples, sendo a primeira como que uma lista sequêncial do que o programa tem a fazer e a segunda um controlo do User Interface (após a primeira fase de leitura dos dados e processamento) implementado pela classe MenuDisplay.
De destacar na função Run() é o suporte de erros como por exemplo a inexistência de ficheiros, informando o utilizador do erro e abortando o programa quando algo corre mal.
Outro suporte de erros existe no operador >> do SportAgentsVectorPolymorphic que ignora linhas com um formato diferente do esperado e quando não reconhece a posição de um jogador é exactamente isso que indica quando o escreve!