Um ponto, 2 pontos ou lista de pontos?

Olá pessoal, essa é minha primeira mensagem para a lista.

Tenho uma dúvida bem simples até, que não deve ter uma resposta certa, então gostaria de ouvir opiniões à respeito dessas duas opções de implementação de um programa:

Enunciado: tenho uma classe FormaAbstrata, com subclasses Ponto, Reta e Polígono.

Opção 1: classe ponto possui um atributo chamado PontoCartesiano, classe reta possui 2 PCartesianos e o polígono uma lista de PCartesianos. Métodos das subclasses tratam os PCartesianos de acordo com a classe

Opção 2: FormaAbstrata possui como atributo uma lista de PCartesiano, de forma que suas subclasses também. Métodos da superclase tratam os PCartesianos como uma lista, valendo pra todas as subclasses.

Implicações: Pela opção 1, ficaria talvez mais certo conceitualmente e mais eficiente. Pela opção 2, menos repetição de código, visto que tem pelo menos 6 métodos que fazem a mesma coisa, mas possuem implementação variada de acordo com a qtd. de pontos.

Será que isso é suficiente para vcs opinarem ou preciso explicar melhor?

t+

Eu voto na 2 :alien!:

Teoria do menor esforço :mrgreen:

Uhm…você assistiu alguma aula do curso de Projeto OO? :smiley:

Este exemplo foi muito debatido lá. A implementação ficou parecida com a 1

Considere que tdoso são formas abstratas. Considere que uma forma abstrata é composta de pontos. Pronto, tua superclasse tá aí.

COnsidere que uma linha tem infinitos pontos, mas estes devem obedecer restrições de posicionamento, seu método addPonto() da classe linha vai ser sobrescrito. O mesmo para um triângulo [3 pontos não lineares] e etc.

A 2) implica em mais que “errado conceitualmente”, ela implica em uma possível quebra de invariantes. Imagine que alguém tenta colocar um ponto em uma posição proibida…sua linha se quebra :wink:

Para evitar reescreve código desnecessariamente, crie métodos na superclasse com as funções normais, tipo:

public void addPonto(Ponto p){
meusPontos.add(p);
}

E utilize numa subclasse quadno possível. QUando não for possível ser tão genérico, como na linha, coloque:

public void addPonto(Ponto p) throws FormaCorrompidaException{
if(estaNaMesmaReta(p))  meusPontos.add(p);
else throw new FormaCorrompidaException("Ei, você sabe algo de geometria?");
}

Fazer as coisas “mais rápido” pode ser complicado. Procure estuda a melhor maneira, e geralmente [mas não semrpe] a menira certa conceitualmente vai te dar menos dor de cabeça agora e [principalmente] amanhã de manhã, quando seu gerente te ligar furioso dizendo que o avião guiado pelo seu programa foi parar em uma rota pro afeganistão, só porque o piloto colocou um waypoint inválido [tá, sou péssimo em exemplos…]

[]s

Gostei de sua sugestão, mas acho que a questão na qual estou diante é manutenibilidade vs. eficiência. É mais fácil qd quero corrigir um método alterá-lo apenas uma claase do que ter que modificar várias. Tb é mais prático adicionar novas classes, pois só preciso criar um construtor para ela (contendo a limitação do número de pontos adequado e inicializar seu atributo Tipo adequadamente).

Ver um Ponto como uma lista de pontos não deixa de estar certo, pois uma lista pode possuir 0 ou mais elementos. No entanto, os algoritmos ficarão mais lentos do que seriam se fossem escritos pensando em apenas um ponto.

Obrigado pelas sugestões.

t+

[quote=“IceT”]Gostei de sua sugestão, mas acho que a questão na qual estou diante é manutenibilidade vs. eficiência. É mais fácil qd quero corrigir um método alterá-lo apenas uma claase do que ter que modificar várias. Tb é mais prático adicionar novas classes, pois só preciso criar um construtor para ela (contendo a limitação do número de pontos adequado e inicializar seu atributo Tipo adequadamente).
[/quote]

Uhm…não entendi. Você vai fazer um método genérico, ainda que quebre as regras das formas para poder alterá-lo de uma só vez?

Olha, pensando assim, não vejo porque você deveria utilizar subclasses. Crie uma classe FormaUnica com estes métodos, e faça instâncias dela para cada elemento, o que vai definir o que é cada forma é o número de pontos. Não tem sentido você fazer subclasses sem considerar as características de cada subtipo.

Se realmente você vai precisar ou não, depende do nível de detalhe que você precisa.

Se você realmente precisa se preocupar com performance a um nível tão microscópico, deveria tentar C++ e otimização em Assembly :wink:

[]s

É o seguinte:

Tenho 8 métodos por classe, 6 classes, incluindo Reta, Ponto, Wirefeame, Curva…

Os 8 métodos são IDÊNTICOS, e tem uns MUITO grandes (puro algoritmo). A única coisa que varia nos métodos, é o parâmetro. Um aceita UM ponto, outro aceita DOIS, outro uma LISTA.

Qd preciso corrigir um método, preciso corrigir 7 métodos. Entende a qeustão?

Qt a eficiência, quis dizer que fica mais lento utilizar a lista de pontos, mas que vou fazê-lo pois não me parece errado e facilita manutenção.

Quanto às subclasses, é preciso para verificar que a lista de pontos possua um tamanho adeuado, ou seja, 1 ponto pra classe Ponto, 2 pra classe reta, etc.

Tb tem umas 3 classes que sobrescrevem o metodo desenheSe.

t+

Ei…por que reescrever os métodos se são praticamente iguais? O que você precisa fazer é reorganizar melhor seu código,s eparando responsabilidades.

Se na minha classe-mãe eu tenho:

Vector pontos;
public void mover(int espaços){
 for(i=0;i<pontos.size();i++) //Para cada um dos pontos na lista...
     ((Ponto) pontos.elementAt(i)).moverPara(espacos); //faz o cast para a classe Ponto e o move
}

public void addPonto(Ponto p) throws FormaCorruptaException{
     pontos.add(p);
}

Um método que recebe a quantidade de espaços para mover a figura horizontalmente, este método move figuras trabalhanto na lista de pontos, não importa quantos elementos esta lista tenha. Não é responsabilidade dele saber isso, ele é “pago” para mexer os pontinhos!!

Quem tem que controla isso é o nosso addPonto(). Do jeito que está, ele quebra invariantes como quem troca de roupa. Numa classe triângulo, poderíamos sobrescrever com este aqui:

public void addPonto(Ponto p) throws FormaCorruptaException{

//Faltou verificar a posição dos pontos aqui :P
     if (pontos.size()<3)
          pontos.add(p);
     else
           throw new FormaCorruptaException("Ei, só posso ter 3 pontos!!");
}

Acho que agora deu pra ver. Se seu método “sabe demais”, você rpecisa dar um jeito nisso…

[]s

O que os métodos tem de iguais é que eles operam sobre os pontos do objeto. O método addPonto seria um dos métodos implementados na classe ponto, assim como seu construtor.

public Ponto(List listaPontos) {
    if (listaPontos.size() != 1) {
        throw new QtdPontosErradaException()
    }
     pontos = listaPontos;
}

Métodos como imprimaPontos, rotacione, translade, etc, estariam implementados apenas na superclasse.

Legal essa discussão…

Mas acho que essa dúvida já está na frente de uma outra, que deveria ter surgido bem antes: por que Ponto, Reta e Polígono são irmãs?

Separar as responsabilidades é muito importante. Me parece que vc está desenhando de baixo pra cima, e fez uma escolha com grandes implicações logo no começo: usar herança pra separar preocupações…

Se sua idéia é reaproveitar código, recomendo que vc imagine (não precisa implementar) interfaces com as suas preocupações. Por exemplo, vc quer desenhar esses objetos na tela? Implementa uma interface Desenhavel. Que ela tem que ter?

Não vejo motivo pra não considerar Reta uma subclasse de Polígono, e Ponto uma sublcasse de Reta. Mas tb não vejo grandes motivos pra ter sequer uma herança aí… se a herança não te ajuda a reaproveitar código, joga ela fora e pega outra idéia!! : ))

Se vc trabalha muito com listas de pontos, recomendo o padrão visitor. Por exemplo:

// Esse é o visitor
public class Transform {
   public void move(Ponto p, int deltaX, int deltaY) {...}
   public void move(List points, int deltaX, int deltaY) {...}
}
// AbstractShape é visitável
public abstract class AbstractShape implements Movable {
   public abstract List getPoints();
   public void move(int deltaX, int deltaY) {
      Transform t = new Transform();
      t.move(this.getPoints(), deltaX, deltaY);
  }
}
// implementação concreta
public class Point extends AbstractShape {
   public List getPoints() {
      List result = new ArrayList(1);
      result.add(this);
      return result;
   }
}
// interface
public interface Movable {
   void move(int deltaX, int deltaY);
}

Outro padrão que tá aí e surgiu como mágica é o Strategy: do ponto de vista da operação mover, o que difere um ponto de uma reta de um polígono é o número de pontos!

Mas ainda tá estranho, não tá? Ao longo do desenvolvimento, c pode acabar tirando ponto da hierarquia e dizendo que ele tb implementa Movable e pronto… : )

[]s!

Bem, elas não são. Se fossem, você teria um problema de conceito, já que uma forma é composta de pontos, mas um ponto não é uma forma. Onde você leu isso?

Heim? Você não tem subtipos assim. Como um ponto seria um polígono?!?

[]

Elas não são irmãs, são FormasGeométricas.

Tenho uma interface Desenhável. Sim comecei de baixo pra cima, e quando percebi que o código se repetia, e que a manutenção era trabalhosa, decidi criar uam classe abstrata (já possuia minha interface Desenhável antes disso).

Um poligono pode ser composto (ter agregado) retas, mas uma reta não é um polígono. Vc deve ter se confundido na nomenclatura :slight_smile:

Não utilizei o visitor pois estou delegando a tarefa de uma reta (ou outra forma) se mover à ela mesma. A reta possui todas as informações que precisa para se mover, logo ela possui o seguinte método:

public void movaSe(int distancia) {
      pontoInicial.movaSe(distancia); //incrementa as coordenadas do ponto
      pontoFinal.movaSe(distancia);
}

Esse era meu método inicial, que foi substituido por uma versão utilizando uma lista de pontos e um iterador.

[]s