Forums Développement Multimédia

Aller au contenu

Un problème de conception de classes

CODE Actionscript

8 réponses à ce sujet

#1 lilive

  • Moderateur
  • PipPipPipPipPipPipPipPip
  • 2993 messages

Posté 10 October 2008 - 18:16 PM

Bonjour,
Je m'interroge sur la bonne façon de faire:

Je souhaite créer des classes pour des pinceaux. Disons 2 classes, ClassicPen et BitmapBrush. ClassicPen permettrait de dessiner sur des Graphics avec l'API de dessin habituelle. BitmapBrush permettrais de dessiner à partir d'un motif bitmap sur des BitmapDatas.
Pour faciliter la gestion de la collection de pinceaux de mon application, je crée une interface IBrush, implémentée par ClassicPen et par BitmapBrush, qui me permet de manipuler ces pinceaux indifféremment.

Jusqu'ici ça va.

Après, il se trouve que les deux types de pinceaux ont des méthodes communes, par exemple:

Action Script


class ClassicPen implement IBrush {
protected var lineStyle:ClassicPenLineStyle;
public function setLineStyle(pLineStyle:ClassicPenLineStyle):void {
lineStyle = pLineStyle;
}
}

class BitmapBrush implement IBrush {
protected var lineStyle:BitmapBrushLineStyle;
public function setLineStyle(pLineStyle:BitmapBrushLineStyle): void {
lineStyle = pLineStyle;
}
}

Seuls change les types de lineStyle. Donc j'essaie d'écrire une super classe DefaultBrush qui contiendrait les méthodes communes:

Action Script


class DefaultBrush implement IBrush {
protected var lineStyle:ILineStyle;
public function setLineStyle(pLineStyle:ILineStyle):void {
lineStyle = pLineStyle;
}
}
Si mes deux classes de pinceau étendent cette classe, et si ClassicPenLineStyle et BitmapBrushLineStyle implémentent ILineStyle, alors tout va bien, je n'ai plus besoin d'écrire setLineStyle dans ClassicPen et dans BitmapBrush.

Mon problème, c'est que je voudrais dans le constructeur définir le lineStyle, en fonction du type du pinceau. J'ai donc fait:

Action Script


class DefaultBrush implement IBrush {

protected var lineStyle:ILineStyle;
protected var className:String = "DefaultBrush";

public function DefaultBrush() {
var LineStyleClass:Class = Class(getDefinitionByName(className + "LineStyle"));
setLineStyle(new LineStyleClass());
}

public function setLineStyle(pLineStyle:ILineStyle):void {
lineStyle = pLineStyle;
}
}

Et puis:

Action Script


class ClassicPen extends DefaultBrush {

public function ClassicPen() {
className = "ClassicPen";
super();
}

}

Mais le problème, c'est que quand je crée un nouveau ClassicPen, et que super() est appelé, alors className vaut toujours "DefaultBrush" dans le constructeur de DefaultBrush, et donc un mauvais type de lineStyle est généré.

Je ne vois pas trop comment m'y prendre. Une solution serait de passer className en paramètre au constructeur, mais ça ne me plait pas de laisser ouverte cette possibilité d'erreur pour l'usage de la classe DefaultBrush, plus tard.

Une autre solution serait de ne pas mettre de valeur par défaut pour className dans la déclaration de la classe Defaultbrush, et de tester dans son constructeur si className est définie, et lui affecter "DefaultBrush" uniquement dans le cas où elle ne l'est pas. Du genre:

Action Script


class DefaultBrush implement IBrush {

protected var lineStyle:ILineStyle;
protected var className;

public function DefaultBrush() {
if (!className) className = "DefaultBrush";
var LineStyleClass:Class = Class(getDefinitionByName(className + "LineStyle"));
setLineStyle(new LineStyleClass());
}

public function setLineStyle(pLineStyle:ILineStyle):void {
lineStyle = pLineStyle;
}
}


Enfin je trouve que tout cela tourne un peu à la bidouille. Peut-être que ma méthode n'est pas la bonne approche?

Auriez-vous de meilleures idées, ou pourriez-vous me confirmer que vous ne voyez pas mieux dans un cas pareil?



#2 dada

  • Honoris
  • PipPipPipPipPipPipPipPip
  • 8510 messages

Posté 10 October 2008 - 18:49 PM

Salut,

Place le code d'initialisation dans une méthode protected "init()", et dans le constructeur tu te contente de l'appeller. Tu pourras facilement la redéfinir dans les sous-classes.


#3 VisitorG

    Ceinture Marron

  • Members
  • PipPipPipPipPipPip
  • 193 messages

Posté 10 October 2008 - 19:30 PM

Le seul moyen de faire ça simple, c'est d'overrider une fonction.
Là peut importe ou que la fonction sois appelée dans l'héritage, elle ira chercher la fonctin redéfinie le plus près de la vrai nature de ton objet.

Action Script

package {

public class A {

protected function get ID():String { return "ClassA"; }

public function A() {
trace(ID);
}
}
}

Action Script

package {

public class B extends A {

override protected function get ID():String { return "ClassB"; }

}
}

newA () et new B() renvoient successivement "ClassA" et "ClassB".

C'est pas super élégant, et on peut pas le faire avec des fonctions statiques (alors qu'un ID on aurait intuitivement l'envie de la déclarer comme telle) mais il y a un avantage c'est qu'en tant que fonction elles peuvent être déclarer dans ton interface et t'économiser quelques casts.

#4 tiscars

    Ceinture Noire

  • Members
  • PipPipPipPipPipPipPip
  • 434 messages

Posté 10 October 2008 - 21:49 PM

si tu veux faire ca direct dans le constructeur du parent... c chaud
mais surtout si tu connais d avance le type de linestyle pour un type de classe...
instancie le direct ds ta classe ca coutera pas trop cher icon_rolleyes.gif

#5 lilive

  • Moderateur
  • PipPipPipPipPipPipPipPip
  • 2993 messages

Posté 11 October 2008 - 15:04 PM

Je suis en train de tester un mix entre la solution de dada et celle de VisitorG:

Action Script



class DefaultBrush implement IBrush {

protected var lineStyle:ILineStyle;
protected var className:String;

public function DefaultBrush() {
init();
var LineStyleClass:Class = Class(getDefinitionByName(className + "LineStyle"));
setLineStyle(new LineStyleClass());
}

public function setLineStyle(pLineStyle:ILineStyle):void {
lineStyle = pLineStyle;
}

protected function init():void {
className = "DefaultBrush";
}
}


class ClassicPen extends DefaultBrush {

protected override function init():void {
className = "ClassicPen";
}

}

Je choisis ça plutôt qu'une function get className():String parce-que à part le lineStyle, j'ai d'autres variables du genre qui contrôlent le comportement de l'objet. Et je peux toutes les définir dans une seule méthode init() plutôt qu'avoir un getter pour chacune d'entre elles.

Il me semble que c'est cette solution qui me demande de re-écrire le moins de choses possibles pour les sous-classes, ce qui était mon envie gropapa (surtout parce-que je n'ai pas que le lineStyle à gérer, mais ça je l'avais pas dit icon_wink.gif ).

Merci à tous icon_smile.gif






#6 lilive

  • Moderateur
  • PipPipPipPipPipPipPipPip
  • 2993 messages

Posté 13 October 2008 - 19:17 PM

Bon j'ai vraiment du mal à concevoir mes classes... (au fait vous pouvez prendre la lecture de ce post à ce point, pas grave, vous comprendrez quand même, enfin j'espère icon_wink.gif )

Je vous explique ce que j'aimerais faire. Je vous préviens, il faut aimer se creuser les méninges :

Je voudrais avoir un displayObject de classe Painting. Cette classe implémenterait des méthodes comme moveTo(x,y), line(x1,y1,x2,y2), lineTo(x,y), point(x,y).
On pourrait dessiner grâce à différents pinceaux. Ces pinceaux seraient comme des pinceaux physiques, c'est-à-dire:

- Un pinceau aurait une taille (épaisseur) définie et constante
- Un pinceau aurait un "type de poil" défini, plus ou moins flou sur les bords.
- Un pinceau pourrait appliquer plusieurs peintures (couleur et opacité).
- Quand on trace plusieurs lignes droites avec le pinceau, sans le lever, l'épaisseur du trait pourrait varier si on est en début ou en fin de trait (comme si le pinceau s'abaissait et se relevait), et aussi varier selon la courbure du trait.

Tous les pinceaux n'implémenteraient pas chacune de ces caratéristiques.
Par exemple un type de pinceau dessinerait grâce à l'API de dessin classique, sans générer de flou sur les bords. J'appelerais la classe d'un tel pinceau ClassicPen.
Un autre type de pinceau dessinerait à l'aide de BitmapDatas (comme les brosses des logiciels de dessin) et pourrait être de formes différentes. C'est les pinceaux de ce type qui auraient des variations d'épaisseur au long du trait. J'appelle LoadedBrush la classe de pinceau qui charge une image depuis un fichier et s'en sert comme forme de brosse.

(Je précise que je n'ai pas spécialement de problèmes "techniques" pour réaliser ces pinceaux. Mon problème c'est vraiment de trouver la meilleure manière de concevoir les classes.)

Pour ma classe Painting, j'aimerais avoir les équivalents de lineStyle() pour un Graphics, qui s'appeleraient setBrush() et setBrushLineStyle().

Voilà des points sur lesquels je suis en train de buter:

Mettons que painting est un objet de classe Painting.

1- painting.setBrush(pBrush) définit la variable brush

Action Script


package drawing {
public class Painting {

private var brush:IBrush;

public function setBrush(pbrush:IBrush):void {
brush = pBrush;
}
}
}

2- Ensuite je dois écrire les fonctions Painting.moveTo() et Painting.lineTo(). Je n'ai pas envie d'écrire toutes les variantes de comportement selon les types de pinceaux à l'intérieur de la classe Painting. Je préfèrerais pouvoir écrire:

Action Script


package drawing {
public class Painting {

public function moveTo(x:Number, y:Number):void {
brush.moveTo(this, x, y);
}
public function lineTo(x:Number, y:Number):void {
brush.lineTo(this, x, y);
}
}
}

Déjà ici ça me pose un premier problème. Si je fais ça, ça demande à un pinceau de retenir pour chaque objet Painting sur lequel un pinceau dessine, les coordonnées du dernier point. Cela demande de créer un tableau targets dans la classe pinceau, qui mémorise les Painting où le pinceau dessine. J'ai essayé, mais l'inconvénient c'est de tenir ce tableau à jour, particulièrement quand des instances de Painting sont détruites, il faut alors les enlever du tableau targets. Ça me semble être une bonne source d'erreurs. Donc j'essaie de m'en passer et de faire:

Action Script


package drawing {
public class Painting {

private var previousX:Number, previousY:Number;

public function moveTo(x:Number, y:Number):void {
previousX = x;
previousY = y;
}
public function lineTo(x:Number, y:Number):void {
line(previousX, previousY, x, y);
previousX = x;
previousY = y;
}
public function line(x1:Number, y1:Number, x2:Number, y2:Number):void {
brush.line(x1, y1, x2, y2);
previousX = x2;
previousY = y2;
}
}
}


3- Cette méthode marche très bien pour un pinceau genre ClassicPen. Pour un loadedPen par contre, je voit un problème si je fais:

Action Script


painting.moveTo(100, 50);
painting.lineTo(200, 70);
painting.lineTo(300, 110);
Au point (200, 70), le BitmapData de la brosse est dessiné deux fois. Une première fois à la fin de la première ligne, une deuxième fois au début de la deuxième ligne. Conséquence: si la brosse à de la transparence, le fait d'appliquer deux fois la brosse au même endroit "additionne" les alphas, et ce point du tracé est plus opaque que le reste.

Donc j'ajoute une variable qui me permet de savoir si la brosse a déjà été appliquée en un point:

Action Script


package drawing {
public class Painting {

private var previousX:Number, previousY:Number;
private var isPreviousDrawn:Boolean;

public function moveTo(x:Number, y:Number):void {
if (previousX != x || previousY != y) {
previousX = x;
previousY = y;
isPreviousDrawn = false;
}
}
public function lineTo(x:Number, y:Number):void {
line(previousX, previousY, x, y);
previousX = x;
previousY = y;
}
public function line(x1:Number, y1:Number, x2:Number, y2:Number):void {
brush.line(x1, y1, x2, y2, isPreviousDrawn);
previousX = x2;
previousY = y2;
}
}
}

Et là les ennuis arrivent. Je dois ajouter un paramètre aux fonctions line() des classes pinceau, et ce paramètre ne va servir que pour certaines d'entre elles (dans l'exemple, à LoadedBrush et pas à ClassicPen). Comme mes classe pinceau implémentent l'interface IBrush, je dois déclarer ce nouveau paramètre dans l'interface et dans toutes les classes, y compris celles qui n'utilisent pas ce paramètre.
C'est l'inconvénient de ne pas pouvoir écrire la même méthode avec plusieurs signatures en AS3.

Je me dis, bon, à la limite c'est pas trop grave.
Mais ensuite je commence à m'occuper de gérer la variation d'épaisseur lors du tracé d'un trait sans lever le pinceau. Si je m'y prend pareil je me retrouve avec une signature comme ça pour la méthode line() d'un IBrush:

Action Script


public function line(
target:Painting,
x1:Number, y1:Number, x2:Number, y2:Number,
isPreviousDrawn:Boolean,
totalLength:Number, currentLength:Number,
curvature:Number
)
Où totalLength est la longueur totale du trait en cours (trait = plusieurs segments de droite consécutifs sans lever le pinceau), currentLength est la longueur parcourue dans ce trait au segment courant, et curvature la valeur de la courbure en ce segment.
Et ClassicPen.line() va devoir inclure ces paramètres qui ne lui servent à rien.

Ça ne me semble vraiment pas top comme démarche.

Mon dieu, je me demande bien si quelqu'un va lire tout ça jusqu'au bout!
En tout cas si quelqu'un le fait et à des suggestions, j'en serais bien content icon_smile.gif

Modifié par lilive, 13 October 2008 - 19:19 PM.


#7 tiscars

    Ceinture Noire

  • Members
  • PipPipPipPipPipPipPip
  • 434 messages

Posté 13 October 2008 - 22:08 PM

pour ta classe painting, ne peux tu pas étendre flash.display.Graphics ?

pour les distances, en utilisant la classe Point tu dois pouvoir récupérer tout ça... enfin au moins pour des lignes directes entre 2 points

#8 tiscars

    Ceinture Noire

  • Members
  • PipPipPipPipPipPipPip
  • 434 messages

Posté 13 October 2008 - 22:09 PM

pour ta classe painting, ne peux tu pas étendre flash.display.Graphics ?

pour les distances, en utilisant la classe Point tu dois pouvoir récupérer tout ça... enfin au moins pour des lignes directes entre 2 points

#9 lilive

  • Moderateur
  • PipPipPipPipPipPipPipPip
  • 2993 messages

Posté 14 October 2008 - 15:22 PM

Citation (gropapa @ Oct 13 2008, 11:09 PM) Voir le message
pour ta classe painting, ne peux tu pas étendre flash.display.Graphics ?

pour les distances, en utilisant la classe Point tu dois pouvoir récupérer tout ça... enfin au moins pour des lignes directes entre 2 points

Ben vu que Graphics n'est pas un DisplayObjectContainer je ne peux pas lui ajouter un BitmapData. La seule méthode de la classe Graphics qui permet de dessiner avec un pinceau Bitmap c'est beginBitmapFill(). Ça ne me semble pas trop approprié, qu'en penses-tu ?

Pour les distances, mon problème n'est pas de les calculer, mais que le pinceau "sache" quelle distance il a parcouru depuis le début de ce que j'appelle un trait (une succession de lignes), et la longueur du trait en cours de dessin, afin de pouvoir dessiner plus fin au début et à la fin du trait. Ce n'est pas évident car je peux utiliser un même pinceau sur plusieurs Painting à la fois (c'est pour ça que je parlais d'un tableau targets dans mon message pécédent).

J'ai tout à coup l'impression que c'est un peu dur de discuter d'un sujet comme ça, et je me demande ce qu'il est possible de comprendre en me lisant icon_smile.gif . N'empêche qu'en te répondant ça me fait réfléchir, et je crois que je vais ajouter une méthode drawStroke() qui sera à utiliser pour dessiner ces fameux traits ou l'épaisseur varie.





1 utilisateur(s) li(sen)t ce sujet

0 membre(s), 1 invité(s), 0 utilisateur(s) anonyme(s)