Laden...

Beispiel für Dekorierer-Entwurfsmuster

Erstellt von sandromo vor 8 Jahren Letzter Beitrag vor 8 Jahren 1.602 Views
S
sandromo Themenstarter:in
7 Beiträge seit 2016
vor 8 Jahren
Beispiel für Dekorierer-Entwurfsmuster

Ich habe aus dem Buch "C# 3.0 Entwurfsmuster" folgendes Beispiel für das Dekorierer-Muster bestehend aus einer Klasse "Photo", zwei Dekorierer-Klassen und eine zum Ausführen


public class Photo:Form
{
	Image img;
	
	public Photo()
	{
		img = new Bitmap("Bild.JPG");
		this.Text = "Bild";
		this.Paint += Drawer;
		this.Size = img.Size;
	}
	
	public virtual void Drawer(Object soure, PaintEventArgs e)
	{
		e.Graphics.DrawImage(img, 10, 20);
	}
}


public class BorderedPhoto:Photo
{
	Photo photo;
	Color color;
	
	public BorderedPhoto(Photo p, Color c)
	{
		photo = p;
		color = c;
	}
	
	public override void Drawer(Object source, PaintEventArgs e)
	{
		photo.Drawer(source, e);
		e.Graphics.DrawRectangle(new Pen(color,10), 25,15,215,225);
	}
}


public class TaggedPhoto:Photo
{
	Photo photo;
	string tag;
	int number;
	static int count;
	List <string> tags = new List <string>();
	
	public TaggedPhoto(Photo p, string s)
	{
		photo = p;
		tag = s;
		tags.Add(s);
		number = ++count;
	}
	
	public override void Drawer(Object source, PaintEventArgs e)
	{
		photo.Drawer(source, e);
		e.Graphics.DrawString(tag,
							  new Font("Arial", 16),
							  new SolidBrush(Color.Green),
							  new PointF(80,100+number*20));
	}
	
	public string ListTaggedPhotos()
	{
		string s = "Tags: ";
		foreach (string t in tags) {
			s += t+" ";
		}
		return s;
	}
}


class Program
{
	public static void Main(string[] args)
	{
		Application.EnableVisualStyles();
		Application.SetCompatibleTextRenderingDefault(false);
		
		Photo photo;
		TaggedPhoto foodTaggedPhoto, colorTaggedPhoto, tag;
		BorderedPhoto composition;
		
		photo = new Photo();
		Application.Run(photo);
		foodTaggedPhoto = new TaggedPhoto(photo, "Food");
		colorTaggedPhoto = new TaggedPhoto(foodTaggedPhoto,"Yellow");
		composition = new BorderedPhoto(colorTaggedPhoto, Color.Blue);
		Application.Run(composition);
		Console.WriteLine(colorTaggedPhoto.ListTaggedPhotos());
	}
}

Soweit alles in Ordnung. Nun soll laut Aufgabenstellung die Methode "Draw" der Klasse Photo als nicht überschreibbar deklariert werden. Das habe ich getan und des Weiteren ein Interface IPHoto zur Verfügung gestellt, das von Photo und den beiden Dekorierern implementiert wird. Die Instanzen "photo" in den beiden Dekoriererklassen sind dann vom Typ IPhoto und der erste Parameter in deren Konstruktoren ebenfalls.

Nun habe ich allerdings ein Problem mit der Methode "Run", die als Parameter nur eine Instanz der Klasse "Form" oder einer davon abgeleiteten Klasse akzeptiert.

Wie muss ich meine Dekorierer instanzieren, um sie der "Run"-Methode übergeben zu können?

16.807 Beiträge seit 2008
vor 8 Jahren

Über das Buch C# 3.0 Entwurfsmuster gibt es kontroverse Ansichten, siehe auch Nicht empfehlenswert: C# 3.0 Entwurfsmuster
Ich finde das Beispiel für Decorator Pattern auch nicht wirklich gelungen.

Aber Deine Klasse Photo (besser wäre PhotoForm) erbt ja von Form.
Application.Run nimmt alle Klassen, die von Form erben, an.

Dahingehend geht problemlos

Application.Run(new Photo());

Das ist auch das Standardverhalten. Jede Form - auch das Standardtemplate Form1 von Visual Studio beim Anlegen eines Windows Form-Projekts - erbt von Form.

S
sandromo Themenstarter:in
7 Beiträge seit 2016
vor 8 Jahren

Ich glaube mein Anliegen ist nicht ganz deutlich geworden.

Die Aufgabe lautet:
Nehmen Sie an, dass in der Klasse _Photo _die Methode Drawer normal (und nicht virtuell) ist und deshalb nicht überschrieben werden kann. Schreiben Sie das Beispiel so um, dass es unter dieser Bedingung funktioniert. (Hinweis: Verwenden Sie ein Interface).

Und folgendes habe ich gemacht

Erstellen des Interface IPhoto

public interface IPhoto
{
	void Drawer(object sender, PaintEventArgs e);
}

Klasse _Photo _erbt weiterhin von Form und implementiert IPhoto; die Methode Draw ist nicht mehr überschreibbar.

public class Photo:Form,IPhoto
{
    Image img;

    public Photo()
    {
        img = new Bitmap("Bild.JPG");
        this.Text = "Bild";
        this.Paint += Drawer;
        this.Size = img.Size;
    }

    public void Drawer(Object soure, PaintEventArgs e)
    {
        e.Graphics.DrawImage(img, 10, 20);
    }
}

Veränderte Klasse TaggedPhoto

public class TaggedPhoto:IPhoto
{
    IPhoto photo;
    string tag;
    int number;
    static int count;
    List <string> tags = new List <string>();

    public TaggedPhoto(IPhoto p, string s)
    {
        photo = p;
        tag = s;
        tags.Add(s);
        number = ++count;
    }

    public void Drawer(Object source, PaintEventArgs e)
    {
        photo.Drawer(source, e);
        e.Graphics.DrawString(tag,
                              new Font("Arial", 16),
                              new SolidBrush(Color.Green),
                              new PointF(80,100+number*20));
    }

    public string ListTaggedPhotos()
    {
        string s = "Tags: ";
        foreach (string t in tags) {
            s += t+" ";
        }
        return s;
    }
}

Veränderte Klasse BorderedPhoto

public class BorderedPhoto:IPhoto
{
    IPhoto photo;
    Color color;

    public BorderedPhoto(IPhoto p, Color c)
    {
        photo = p;
        color = c;
    }

    public void Drawer(Object source, PaintEventArgs e)
    {
        photo.Drawer(source, e);
        e.Graphics.DrawRectangle(new Pen(color,10), 25,15,215,225);
    }
}

Ausführende Klasse; den Code habe ich unverändert gelassen bis auf das Objekt photo, das nun vom Typ IPhoto ist. Die beiden Methodenaufrufe von Run führen dann zu Fehlern, weil keine Instanz von Form oder abgeleiteter Klasse übergeben wurde. Beim ersten Aufruf könnte man noch Casten, aber beim Zweiten weiß ich nicht.
Was muss ich verändern, damit die in der Aufgabenstellung genannte Bedingung erfüllt ist?

internal sealed class Program
{
	/// <summary>
	/// Program entry point.
	/// </summary>
	[STAThread]
	private static void Main(string[] args)
	{
		Application.EnableVisualStyles();
		Application.SetCompatibleTextRenderingDefault(false);
		
		IPhoto photo;
        TaggedPhoto foodTaggedPhoto, colorTaggedPhoto, tag;
        BorderedPhoto composition;

        photo = new Photo();
        Application.Run(photo);
        foodTaggedPhoto = new TaggedPhoto(photo, "Food");
        colorTaggedPhoto = new TaggedPhoto(foodTaggedPhoto,"Yellow");
        composition = new BorderedPhoto(colorTaggedPhoto, Color.Blue);
        Application.Run(composition);
        Console.WriteLine(colorTaggedPhoto.ListTaggedPhotos());
	}
	
}
T
50 Beiträge seit 2010
vor 8 Jahren

Hi,

wie Abt schon geschrieben hat, muss eine Klasse, die über Application.Run aufgerufen wird, von Form erben. Das tut Deine Klasse BorderedPhoto aber nicht. Deswegen funktioniert es nicht.

16.807 Beiträge seit 2008
vor 8 Jahren

Sein Problem ist, dass er nur das Interface IPhoto übergibt.

IPhoto photo; // hat nichts mit Form zutun

photo = new Photo();
Application.Run(photo); // Will Form, bekommt aber IPhoto

IPhoto hat aber nichts mit Form zutun.
Photo erbt von Form.

Korrekt(er) wäre an dieser Stelle auf das Interface zu verzichten.

Photo photo; // Erbt von Form

photo = new Photo();
Application.Run(photo); // Will Form und bekommt eine Klasse, die von Form erbt

Ich find das Beispiel für einen Decorator absolut nicht gut.
Eher Decorator Design Pattern .NET

S
sandromo Themenstarter:in
7 Beiträge seit 2016
vor 8 Jahren

Das leuchtet mir alles ein, aber es ist nicht die Lösung des Problems/der Aufgabe. Ihr habt mir erklärt was falsch läuft. Das weiß ich aber bereits 🙂

Was muss ich denn konkret verändern, damit meine Dekoriererklassen funktionieren?

Ich finde das Beispiel auch nicht so gut, aber es muss doch zu lösen sein.

3.003 Beiträge seit 2006
vor 8 Jahren

Beide Dekorierer müssen Form implementieren, sonst wird das nix. Wurde dir auch schon einige Male erklärt.


public class TaggedPhoto: Form, IPhoto

Der Grund dafür, und auch das wurde dir bereits erklärt, liegt darin, dass der Aufruf von Application.Run() als Parameter eine Klasse erwartet, die Form implementiert.

Das Decorator-Beispiel aus Head First: Design Patterns ist deinem übrigens um Längen überlegen. Plus, da das Buch für Java ist, lernt man sogar mehr, wenn man die Beispiele auf C# umsetzt (weil man nicht stur abtippen kann, sondern überlegen muss). Und ich würde wetten, dass die anderen Beispiele auch besser sind.

LaTino
Edit: anbei ein Vorschlag, wie das besser zu lösen ist. Die dekorierte Klasse sollte nicht von Forms erben, das ist nicht ihre Aufgabe. Stattdessen wird eine abstrakte Basisklasse für alle Dekorierer eingefuegt, die die Drawer-Methode überschreibbar implementiert oder als abstract vorgibt, und diese erbt von Form. So wird ein bisschen mehr ein Schuh draus. (Ich hatte ganz zum Schluss keine richtige Lust mehr, mich mit dem UML-Tool herumzuschlagen. Rechts unten die Klasse sollte eigtl. BorderedDrawer o.ä. heißen, halt ein konkreter Dekorierer.)

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

S
sandromo Themenstarter:in
7 Beiträge seit 2016
vor 8 Jahren

So hatte ich mir das mittlerweile auch überlegt, nur ohne die abstrakte Klasse.
Danke!