Lab: Replace conditional with polymorphism#
Objective#
To refactor the provided Shape
class to remove conditionals and implement polymorphism, thereby enhancing code maintainability.
Provided code#
Study the starting code given to you. It consists of a single Shape
class which draws different shapes based on the ShapeType
using conditionals.
enum ShapeType { HorizontalLine, Square, RightTriangle }
class Shape
{
ShapeType type;
int size;
string symbol;
public Shape(ShapeType type, int size, string symbol)
{
this.symbol = symbol;
this.type = type;
this.size = size;
}
public string Draw()
{
if (type == ShapeType.HorizontalLine)
return draw(MakeHorizontalLine());
else if (type == ShapeType.Square)
return draw(MakeSquare());
else if (type == ShapeType.RightTriangle)
return draw(MakeRightTriangle());
else
return "";
}
private string draw(string[][] symbols)
{
string lines = "";
for (int i = 0; i < symbols.Length; i++)
{
string line = "";
for (int j = 0; j < symbols[i].Length; j++)
{
line += symbols[i][j];
}
lines += line + "\n";
}
return lines;
}
private string[][] MakeHorizontalLine()
{
string[][] symbols = new string[1][];
symbols[0] = new string[size];
for (int i = 0; i < size; i++)
symbols[0][i] = symbol;
return symbols;
}
private string[][] MakeSquare()
{
string[][] symbols = new string[size][];
for (int i = 0; i < size; i++)
{
symbols[i] = new string[size];
for (int j = 0; j < size; j++)
symbols[i][j] = symbol;
}
return symbols;
}
private string[][] MakeRightTriangle()
{
string[][] symbols = new string[size][];
for (int i = 0; i < size; i++)
{
symbols[i] = new string[i + 1];
for (int j = 0; j < i + 1; j++)
symbols[i][j] = symbol;
}
return symbols;
}
}
Shape shape1 = new Shape(ShapeType.HorizontalLine, 12, "=");
Console.WriteLine(shape1.Draw());
Shape shape2 = new Shape(ShapeType.Square, 4, "■ ");
Console.WriteLine(shape2.Draw());
Shape shape3 = new Shape(ShapeType.RightTriangle, 6, "* ");
Console.WriteLine(shape3.Draw());
============
■ ■ ■ ■
■ ■ ■ ■
■ ■ ■ ■
■ ■ ■ ■
*
* *
* * *
* * * *
* * * * *
* * * * * *
Instructions#
Step 1: Replace conditionals in Shape
with polymorphism#
Define an interface, IShape
, with a method signature for string Draw()
.
Create individual classes for each shape type, namely HorizontalLine
, Square
, and RightTriangle
, implementing the IShape
interface.
Note
It’s ok if you end up duplicating the private
method string draw(string[][] symbols)
. We’ll get rid of that duplication later.
Step 2: Rewrite the main class#
Rewrite the Main
method so that it uses subtypes of IShape
instead of the Shape
class.
It should look like this and should both compile and still print the same output as before.
You should now be able to delete the old Shape
class.
IShape shape1 = new HorizontalLine(12, "=");
Console.WriteLine(shape1.Draw());
// Do the same for Square.
// Do the same for RightTriangle
============
Step 3: Eliminate duplication using inheritance#
When we split the shapes into their own classes that implement the interface IShape
we ended up duplicating the logic of the private
method string draw(string[][] symbols)
.
Let’s eliminate that duplication using inheritance.
Make the classes HorizontalLine
, Square
, and RightTriangle
inherit from an abstract class called Shape
which in turn implements the interface IShape
.
The abstract class Shape
should allow you to eliminate the duplicated method private
method string draw(string[][] symbols)
.
One way is to add an abstract
method to Shape
with the signature string[][] MakeLines()
:
abstract class Shape : IShape
{
protected abstract string[][] GetLines();
public string Draw()
=> ""; // Replace with your implementation.
}
Another way is to make Draw
abstract
in Shape
and instead offer its subclasses a protected
method with the signature string Flatten(string[][] symbols)
.
abstract class Shape : IShape
{
public abstract string Draw();
protected string Flatten(string[][] lines)
=> ""; // Replace with your implementation.
}
should provide a protected method with the signature string Draw(string[][] symbols
. This allows us to remove the duplicated code in the three shape classes.
🤔 Reflection
How does creating specific classes for each shape, adhering to the same interface, make the code more organized and maintainable?
Step 4: Add more shapes#
Challenge: Create some new classes, such as VerticalLine
or Cross
, that implement IShape
and integrate it with your existing code without modifying the existing shape classes.
🤔 Reflection
How does your design enable the addition of new shapes with minimal changes to existing code?