Rechtecke: Die Klasse Rectangle

1. Aufgabe

Definieren und implementieren Sie eine Klasse Rectangle, die ein Rechteck im Sinne des kartesischen Koordinatensystems verwaltet. Das Rechteck soll dabei durch zwei Punkte (x1, y1) und (x2, y2) festgelegt werden, die die linke obere und rechte untere Ecke markieren.

Folgende Anforderungen an die Klasse Rectangle sind mit geeigneten programmiersprachlichen Konstrukten umzusetzen:

  • Bei der Erzeugung eines Rectangle-Objekts mit dem Standard-Konstruktor werden beide Koordinaten auf den Wert (0, 0) gesetzt.

  • Bei der Erzeugung eines Rectangle-Objekts mit einem benutzerdefinierten Konstruktor werden die Koordinaten entsprechend der Parameter des Konstruktors gesetzt. Dabei soll für zwei Punkte (x1, y1) und (x2, y2) im Objekt stets gelten: x1x2 und y1y2. Erfüllen die als Parameter übergebenen Koordinaten diese Eigenschaft nicht, so sind im Objekt zwei modifizierte Punkte an Hand der Original-Punkte abzuleiten. Mit dieser Forderung soll erreicht werden, dass die beiden Punkte im Objekt stets die linke obere und rechte untere Ecke des Rechtecks beschreiben. Die Implementierung der nachfolgend beschriebenen Methoden vereinfacht sich auf diese Weise nicht unerheblich!

  • Ergänzen Sie die Klasse um Eigenschaften (Properties, get- und set-Methoden) zum Lesen und Schreiben der Koordinaten des Rechtecks. Achten Sie darauf, dass beim Ändern einer Koordinate die Regel x1x2 und y1y2 beibehalten bleibt.

  • Schreiben Sie eine Methode Print, um ein Rectangle-Objekt in der Konsole ausgeben zu können. Sollten Ihnen bereits Kenntnisse im Umfeld der Vererbung in C# vertraut sein, dann überschreiben Sie doch die ToString-Methode aus der Basisklasse Object mit derselben Zielsetzung.

  • Schreiben Sie eine Methode Circumference zur Berechnung des Umfangs eines Rechtecks.

  • Die Methode Diagonal berechnet die Länge der Diagonalen des Rechtecks.

  • Schreiben Sie eine Methode Area zur Berechnung der Fläche eines Rechtecks.

  • Die Methode IsSquare überprüft, ob das Rechteck ein Quadrat ist oder nicht.

  • Schreiben Sie eine Methode Center, die den Punkt im Zentrum des Rechtecks berechnet. Definieren Sie eine zweite Klasse Point, die von der Center-Methode als Rückgabetyp verwendet wird.

  • Schreiben Sie eine Methode AdjustWidth, die die Breite eines Rechtecks ändert: x2 = x1 + width.

    Beispiel: Für ein Rechteck mit den Koordinaten (1, 1) und (4, 5) besitzt nach dem Aufruf

    AdjustWidth(10);

    die Koordinate x2 den Wert 11.

  • Schreiben Sie eine Methode AdjustHeight, die die Höhe eines Rechtecks ändert:

    y2 = y1 - height.

  • Schreiben Sie eine Methode Intersection, die ein zweites Rectangle-Objekt als Parameter übergeben bekommt und dasjenige Rechteck berechnet, das die beiden Rechtecke gemeinsam haben. Das Resultat-Rechteck ist als Rückgabewert der Intersection-Methode zurückzuliefern.

    Beispiel:

    Rectangle rect1 = new Rectangle (1, 4, 4, 1);
    Rectangle rect2 = new Rectangle (2, 5, 6, 2);
    Rectangle rect = rect1.Intersection(rect2);

    liefert ein Rechtecht rect mit den Eckpunkten (2, 4) und (4, 2) zurück.

  • Schreiben Sie eine Methode Move, die ein Rechteck in x- und/oder y-Richtung verschieben kann:

    Beispiel:

    Rectangle rect = new Rectangle (1,1,4,5);
    rect.MoveTo(3, 6);

    Das Rechteck rect wird um 3 in x- und 6 in y-Richtung verschoben.

Testen Sie die geforderte Funktionalität durch entsprechende Testfunktionen im Hauptprogramm.

2. Lösung

Quellcode: Siehe auch github.com/peterloos/CSharp_Rectangles.git.

Wir stellen dieses Mal gleich zwei Lösungen zu dieser Aufgabe vor. Eine erste Lösung setzt die Anforderungen der Aufgabenstellung direkt in eine Klasse Rectangle um. In einer zweiten Lösung erstellen wir zwei Klassen Point und Rectangle. In Listing 1 finden Sie meinen Lösungsvorschlag zur ersten Variante vor:

001: class Rectangle
002: {
003:     private double x1;
004:     private double y1;
005:     private double x2;
006:     private double y2;
007: 
008:     // c'tors
009:     public Rectangle()
010:     {
011:         this.x1 = 0;
012:         this.y1 = 0;
013:         this.x2 = 0;
014:         this.y2 = 0;
015:     }
016: 
017:     public Rectangle(double x1, double y1, double x2, double y2)
018:     {
019:         this.x1 = x1;
020:         this.y1 = y1;
021:         this.x2 = x2;
022:         this.y2 = y2;
023:         this.Normalize();
024:     }
025: 
026:     // properties
027:     public double X1
028:     {
029:         get
030:         {
031:             return this.x1;
032:         }
033: 
034:         set
035:         {
036:             this.x1 = value;
037:             this.Normalize();
038:         }
039:     }
040: 
041:     public double X2
042:     {
043:         get
044:         {
045:             return this.x2;
046:         }
047: 
048:         set
049:         {
050:             this.x2 = value;
051:             this.Normalize();
052:         }
053:     }
054: 
055:     public double Y1
056:     {
057:         get
058:         {
059:             return this.y1;
060:         }
061: 
062:         set
063:         {
064:             this.y1 = value;
065:             this.Normalize();
066:         }
067:     }
068: 
069:     public double Y2
070:     {
071:         get
072:         {
073:             return this.y2;
074:         }
075: 
076:         set
077:         {
078:             this.y2 = value;
079:             this.Normalize();
080:         }
081:     }
082: 
083:     public double Circumference
084:     {
085:         get
086:         {
087:             return 2 * ((this.x2 - this.x1) + (this.y1 - this.y2));
088:         }
089:     }
090: 
091:     public double Diagonal
092:     {
093:         get
094:         {
095:             return
096:                 Math.Sqrt(
097:                     ((this.x2 - this.x1) * (this.x2 - this.x1) +
098:                     (this.y1 - this.y2) * (this.y1 - this.y2)));
099:         }
100:     }
101: 
102:     public double Area
103:     {
104:         get
105:         {
106:             return (this.x2 - this.x1) * (this.y1 - this.y2);
107:         }
108:     }
109: 
110:     public bool IsSquare
111:     {
112:         get
113:         {
114:             return (this.x2 - this.x1) == (this.y1 - this.y2);
115:         }
116:     }
117: 
118:     public Point Center
119:     {
120:         get
121:         {
122:             return
123:                 new Point(
124:                     this.x1 + (this.x2 - this.x1) / 2.0,
125:                     this.y2 + (this.y1 - this.y2) / 2.0);
126:         }
127:     }
128: 
129:     // public interface
130:     public void AdjustWidth(double width)
131:     {
132:         this.x2 = this.x1 + width;
133:         this.Normalize();
134:     }
135: 
136:     public void AdjustHeight(double height)
137:     {
138:         this.y2 = this.y1 - height;
139:         this.Normalize();
140:     }
141: 
142:     public void Move(double x, double y)
143:     {
144:         this.x1 += x;
145:         this.y1 += y;
146:         this.x2 += x;
147:         this.y2 += y;
148:     }
149: 
150:     public Rectangle Intersection(Rectangle rect)
151:     {
152:         double x1, y1, x2, y2;
153: 
154:         if (this.x2 <= rect.x1 || this.x1 >= rect.x2 ||
155:             this.y1 <= rect.y2 || this.y2 >= rect.y1)
156:         {
157:             return new Rectangle();
158:         }
159: 
160:         if (this.x1 < rect.x1)
161:         {
162:             x1 = rect.x1;
163:         }
164:         else
165:         {
166:             x1 = this.x1;
167:         }
168: 
169:         if (this.x2 < rect.x2)
170:         {
171:             x2 = this.x2;
172:         }
173:         else
174:         {
175:             x2 = rect.x2;
176:         }
177: 
178:         if (this.y1 > rect.y1)
179:         {
180:             y1 = rect.y1;
181:         }
182:         else
183:         {
184:             y1 = this.y1;
185:         }
186: 
187:         if (this.y2 > rect.y2)
188:         {
189:             y2 = this.y2;
190:         }
191:         else
192:         {
193:             y2 = rect.y2;
194:         }
195: 
196:         return new Rectangle(x1, y1, x2, y2);
197:     }
198: 
199:     // private helper methods
200:     private void Normalize()
201:     {
202:         if (this.x1 > this.x2)
203:         {
204:             double tmp = this.x1;
205:             this.x1 = this.x2;
206:             this.x2 = tmp;
207:         }
208: 
209:         if (this.y1 < this.y2)
210:         {
211:             double tmp = this.y1;
212:             this.y1 = this.y2;
213:             this.y2 = tmp;
214:         }
215:     }
216: 
217:     // overrides
218:     public override String ToString()
219:     {
220:         String s1 = String.Format(
221:             "Rectangle: ({0:0.00},{1:0.00}),({2:0.00},{3:0.00})",
222:             this.x1, this.y1, this.x2, this.y2);
223: 
224:         String s2 = String.Format("[Area={0:0.00}, Circumference={1:0.00}, ",
225:             this.Area, this.Circumference);
226: 
227:         String s3 = String.Format("Diagonal={0:0.00}, IsSquare={1:0.00}]",
228:             this.Diagonal, this.IsSquare);
229: 
230:         return s1 + Environment.NewLine + s2 + s3;
231:     }
232: }

Beispiel 1. Klasse Rectangle: Implementierung Variante 1.


Es folgt ein zweiter Lösungsvorschlag auf Basis zweier Klassen Klassen Point und Rectangle (Listing 2 und Listing 3):

01: class Point
02: {
03:     private double x;
04:     private double y;
05: 
06:     // c'tors
07:     public Point ()
08:     {
09:         this.x = 0;
10:         this.y = 0;
11:     }
12: 
13:     public Point(double x, double y)
14:     {
15:         this.x = x;
16:         this.y = y;
17:     }
18: 
19:     // properties
20:     public double X
21:     {
22:         get { return this.x; }
23:         set { this.x = value; }
24:     }
25: 
26:     public double Y
27:     {
28:         get { return this.y; }
29:         set { this.y = value; }
30:     }
31: 
32:     // overrides
33:     public override String ToString()
34:     {
35:         return String.Format("({0:.##},{1:.##})", this.x, this.y);
36:     }
37: 
38:     public override bool Equals(Object obj)
39:     {
40:         if (!(obj is Point))
41:             return false;
42: 
43:         Point p = (Point) obj;
44: 
45:         return this.X == p.X && this.Y == p.Y;
46:     }
47: 
48:     public override int GetHashCode()
49:     {
50:         return base.GetHashCode();
51:     }
52: }

Beispiel 2. Klasse Point: Implementierung Variante 2.


001: class Rectangle
002: {
003:     private Point left_top;
004:     private Point right_bottom;
005: 
006:     // c'tors
007:     public Rectangle()
008:     {
009:         this.left_top = new Point();
010:         this.right_bottom = new Point();
011:     }
012: 
013:     public Rectangle(double x1, double y1, double x2, double y2)
014:     {
015:         this.left_top = new Point(x1, y1);
016:         this.right_bottom = new Point(x2, y2);
017:         this.Normalize();
018:     }
019: 
020:     // properties
021:     public double X1
022:     {
023:         get
024:         {
025:             return this.left_top.X;
026:         }
027: 
028:         set
029:         {
030:             this.left_top.X = value;
031:             this.Normalize();
032:         }
033:     }
034: 
035:     public double Y1
036:     {
037:         get
038:         {
039:             return this.left_top.Y;
040:         }
041: 
042:         set
043:         {
044:             this.left_top.Y = value;
045:             this.Normalize();
046:         }
047:     }
048: 
049:     public double X2
050:     {
051:         get
052:         {
053:             return this.right_bottom.X;
054:         }
055: 
056:         set
057:         {
058:             this.right_bottom.X = value;
059:             this.Normalize();
060:         }
061:     }
062: 
063: 
064:     public double Y2
065:     {
066:         get
067:         {
068:             return this.right_bottom.Y;
069:         }
070: 
071:         set
072:         {
073:             this.right_bottom.Y = value;
074:             this.Normalize();
075:         }
076:     }
077: 
078:     public double Circumference
079:     {
080:         get
081:         {
082:             return 2 * ((this.right_bottom.X - this.left_top.X) + (this.left_top.Y - this.right_bottom.Y));
083:         }
084:     }
085: 
086:     public double Diagonal
087:     {
088:         get
089:         {
090:             return
091:                 Math.Sqrt(
092:                     ((this.right_bottom.X - this.left_top.X) * (this.right_bottom.X - this.left_top.X) +
093:                     (this.left_top.Y - this.right_bottom.Y) * (this.left_top.Y - this.right_bottom.Y)));
094:         }
095:     }
096: 
097:     public double Area
098:     {
099:         get
100:         {
101:             return (this.right_bottom.X - this.left_top.X) * (this.left_top.Y - this.right_bottom.Y);
102:         }
103:     }
104: 
105:     public bool IsSquare
106:     {
107:         get
108:         {
109:             return (this.right_bottom.X - this.left_top.X) == (this.left_top.Y - this.right_bottom.Y);
110:         }
111:     }
112: 
113:     public Point Center
114:     {
115:         get
116:         {
117:             return
118:                 new Point(
119:                     this.left_top.X + (this.right_bottom.X - this.left_top.X) / 2.0,
120:                     this.right_bottom.Y + (this.left_top.Y - this.right_bottom.Y) / 2.0);
121:         }
122:     }
123: 
124:     // public interface
125:     public void AdjustWidth(double width)
126:     {
127:         this.right_bottom.X = this.left_top.X + width;
128:         this.Normalize();
129:     }
130: 
131:     public void AdjustHeight(double height)
132:     {
133:         this.right_bottom.Y = this.left_top.Y - height;
134:         this.Normalize();
135:     }
136: 
137:     public void Move(double x, double y)
138:     {
139:         this.left_top.X += x;
140:         this.left_top.Y += y;
141:         this.right_bottom.X += x;
142:         this.right_bottom.Y += y;
143:     }
144: 
145:     public Rectangle Intersection(Rectangle rect)
146:     {
147:         double x1, y1, x2, y2;
148: 
149:         if (this.right_bottom.X <= rect.left_top.X || this.left_top.X >= rect.right_bottom.X ||
150:             this.left_top.Y <= rect.right_bottom.Y || this.right_bottom.Y >= rect.left_top.Y)
151:         {
152:             return new Rectangle();
153:         }
154: 
155:         if (this.left_top.X < rect.left_top.X)
156:         {
157:             x1 = rect.left_top.X;
158:         }
159:         else
160:         {
161:             x1 = this.left_top.X;
162:         }
163: 
164:         if (this.right_bottom.X < rect.right_bottom.X)
165:         {
166:             x2 = this.right_bottom.X;
167:         }
168:         else
169:         {
170:             x2 = rect.right_bottom.X;
171:         }
172: 
173:         if (this.left_top.Y > rect.left_top.Y)
174:         {
175:             y1 = rect.left_top.Y;
176:         }
177:         else
178:         {
179:             y1 = this.left_top.Y;
180:         }
181: 
182:         if (this.right_bottom.Y > rect.right_bottom.Y)
183:         {
184:             y2 = this.right_bottom.Y;
185:         }
186:         else
187:         {
188:             y2 = rect.right_bottom.Y;
189:         }
190: 
191:         return new Rectangle(x1, y1, x2, y2);
192:     }
193: 
194:     // private helper methods
195:     private void Normalize()
196:     {
197:         if (this.left_top.X > this.right_bottom.X)
198:         {
199:             double tmp = this.left_top.X;
200:             this.left_top.X = this.right_bottom.X;
201:             this.right_bottom.X = tmp;
202:         }
203: 
204:         if (this.left_top.Y < this.right_bottom.Y)
205:         {
206:             double tmp = this.left_top.Y;
207:             this.left_top.Y = this.right_bottom.Y;
208:             this.right_bottom.Y = tmp;
209:         }
210:     }
211: 
212:     // overrides
213:     public override String ToString()
214:     {
215:         String s1 = String.Format(
216:             "Rectangle: ({0:0.00},{1:0.00}),({2:0.00},{3:0.00})",
217:             this.left_top.X, this.left_top.Y, this.right_bottom.X, this.right_bottom.Y);
218: 
219:         String s2 = String.Format("[Area={0:0.00}, Circumference={1:0.00}, ",
220:             this.Area, this.Circumference);
221: 
222:         String s3 = String.Format("Diagonal={0:0.00}, IsSquare={1:0.00}]",
223:             this.Diagonal, this.IsSquare);
224: 
225:         return s1 + Environment.NewLine + s2 + s3;
226:     }
227: }

Beispiel 3. Klasse Rectangle: Implementierung Variante 2.


Da wir die Klasse Point an der öffentlichen Schnittstelle der Klasse Rectangle nicht in Erscheinung treten lassen, können wir beide Realisierungen der Klasse Rectangle mit einem einzigen Testrahmen testen, siehe Listing 4:

01: class Program
02: {
03:     public static void Main()
04:     {
05:        Test01_Ctors();
06:        Test02_Properties();
07:        Test03_Center();
08:        Test04_Adjust();
09:        Test05_Move();
10:        Test06_Intersection();
11:     }
12: 
13:     public static void Test01_Ctors()
14:     {
15:         Rectangle rect1 = new Rectangle();
16:         Console.WriteLine(rect1);
17:         Rectangle rect2 = new Rectangle(3, 3, 5, 5);
18:         Console.WriteLine(rect2);
19:         Rectangle rect3 = new Rectangle(3, 3, 5, 1);
20:         Console.WriteLine(rect3);
21:         Rectangle rect4 = new Rectangle(3, 3, 1, 1);
22:         Console.WriteLine(rect4);
23:         Rectangle rect5 = new Rectangle(3, 3, 1, 5);
24:         Console.WriteLine(rect5);
25:     }
26: 
27:     public static void Test02_Properties()
28:     {
29:         Rectangle rect = new Rectangle(3, 4, 9, 10);
30:         Console.WriteLine(rect);
31:         Console.WriteLine("Circumference: " + rect.Circumference);
32:         Console.WriteLine("Diagonal:      " + rect.Diagonal);
33:         Console.WriteLine("Area:          " + rect.Area);
34:         Console.WriteLine("IsSquare:      " + rect.IsSquare);
35:     }
36: 
37:     public static void Test03_Center()
38:     {
39:         Rectangle rect1 = new Rectangle(1, 3, 3, 1);
40:         Console.WriteLine(rect1);
41:         Point p1 = rect1.Center;
42:         Console.WriteLine("Center: " + p1.ToString());
43: 
44:         Rectangle rect2 = new Rectangle(1, 4, 4, 1);
45:         Console.WriteLine(rect2);
46:         Point p2 = rect2.Center;
47:         Console.WriteLine("Center: " + p2.ToString());
48:     }
49: 
50:     public static void Test04_Adjust()
51:     {
52:         Rectangle rect = new Rectangle(3, 3, 1, 1);
53:         Console.WriteLine(rect);
54:         rect.AdjustWidth(3);
55:         Console.WriteLine(rect);
56:         rect.AdjustWidth(-1);
57:         Console.WriteLine(rect);
58:         rect.AdjustHeight(3);
59:         Console.WriteLine(rect);
60:         rect.AdjustHeight(-1);
61:         Console.WriteLine(rect);
62:     }
63: 
64:     public static void Test05_Move()
65:     {
66:         Rectangle rect = new Rectangle(1, 1, 4, 5);
67:         Console.WriteLine(rect);
68:         rect.Move(5, -5);
69:         Console.WriteLine(rect);
70:     }
71: 
72:     public static void Test06_Intersection()
73:     {
74:         Rectangle rect1 = new Rectangle(4, 8, 9, 5);
75:         Rectangle rect2 = new Rectangle(2, 10, 8, 6);
76:         Rectangle rect = rect1.Intersection(rect2);
77:         Console.WriteLine(rect);
78: 
79:         Rectangle rect3 = new Rectangle(7, 9, 9, 4);
80:         rect = rect1.Intersection(rect3);
81:         Console.WriteLine(rect);
82: 
83:         rect = rect3.Intersection(rect3);
84:         Console.WriteLine(rect);
85: 
86:         Rectangle rect4 = new Rectangle(6, 7, 10, 5);
87:         rect = rect1.Intersection(rect4);
88:         Console.WriteLine(rect);
89:     }
90: }

Beispiel 4. Testrahmen


Bei der Ausführung des Programms aus Listing 4 sollten Sie folgendes Resultat erhalten:

Rectangle: (0.00,0.00),(0.00,0.00)
[Area=0.00, Circumference=0.00, Diagonal=0.00, IsSquare=True]
Rectangle: (3.00,5.00),(5.00,3.00)
[Area=4.00, Circumference=8.00, Diagonal=2.83, IsSquare=True]
Rectangle: (3.00,3.00),(5.00,1.00)
[Area=4.00, Circumference=8.00, Diagonal=2.83, IsSquare=True]
Rectangle: (1.00,3.00),(3.00,1.00)
[Area=4.00, Circumference=8.00, Diagonal=2.83, IsSquare=True]
Rectangle: (1.00,5.00),(3.00,3.00)
[Area=4.00, Circumference=8.00, Diagonal=2.83, IsSquare=True]
Rectangle: (3.00,10.00),(9.00,4.00)
[Area=36.00, Circumference=24.00, Diagonal=8.49, IsSquare=True]
Circumference: 24
Diagonal:      8.48528137423857
Area:          36
IsSquare:      True
Rectangle: (1.00,3.00),(3.00,1.00)
[Area=4.00, Circumference=8.00, Diagonal=2.83, IsSquare=True]
Center: (2,2)
Rectangle: (1.00,4.00),(4.00,1.00)
[Area=9.00, Circumference=12.00, Diagonal=4.24, IsSquare=True]
Center: (2.5,2.5)
Rectangle: (1.00,3.00),(3.00,1.00)
[Area=4.00, Circumference=8.00, Diagonal=2.83, IsSquare=True]
Rectangle: (1.00,3.00),(4.00,1.00)
[Area=6.00, Circumference=10.00, Diagonal=3.61, IsSquare=False]
Rectangle: (0.00,3.00),(1.00,1.00)
[Area=2.00, Circumference=6.00, Diagonal=2.24, IsSquare=False]
Rectangle: (0.00,3.00),(1.00,0.00)
[Area=3.00, Circumference=8.00, Diagonal=3.16, IsSquare=False]
Rectangle: (0.00,4.00),(1.00,3.00)
[Area=1.00, Circumference=4.00, Diagonal=1.41, IsSquare=True]
Rectangle: (1.00,5.00),(4.00,1.00)
[Area=12.00, Circumference=14.00, Diagonal=5.00, IsSquare=False]
Rectangle: (6.00,0.00),(9.00,-4.00)
[Area=12.00, Circumference=14.00, Diagonal=5.00, IsSquare=False]
Rectangle: (4.00,8.00),(8.00,6.00)
[Area=8.00, Circumference=12.00, Diagonal=4.47, IsSquare=False]
Rectangle: (7.00,8.00),(9.00,5.00)
[Area=6.00, Circumference=10.00, Diagonal=3.61, IsSquare=False]
Rectangle: (7.00,9.00),(9.00,4.00)
[Area=10.00, Circumference=14.00, Diagonal=5.39, IsSquare=False]
Rectangle: (6.00,7.00),(9.00,5.00)
[Area=6.00, Circumference=10.00, Diagonal=3.61, IsSquare=False]