Here's the concept as a UML representation.
Let's take a look at a concrete example with shapes.
Traditionally when you learn about Object Oriented Programming you may come across something like the following.
Now the above is perfectly valid you create an abstract base Shape class which contains width and height properties, and abstract methods that are overridden in subclasses. makes sense right? but the question is do we really need this hierarchy? well let's take a look at the following
as you can see this looks far more complex than the "Simple" inheritance model, but let's investigate a bit further, firstly let's take a look at our inheritance solution.
using System;
namespace pav.shapesOOP
{
abstract class Shape
{
protected int[] args;
public abstract double
GetArea();
public abstract double
GetPerimeter();
}
class Rectangle : Shape
{
public Rectangle(int[] args)
=> base.args = args;
public override double GetArea()
=> args[0] * args[1];
public override double
GetPerimeter() => 2 * args[0] + 2 * args[1];
}
class Circle : Shape
{
public Circle(int[] args)
=> base.args = args;
public override double
GetArea() => Math.Round(Math.PI * Math.Pow((args[0] / 2), 2), 2);
public override double
GetPerimeter() => Math.Round(Math.PI * args[0], 2);
}
class RightTriangle : Shape
{
public RightTriangle(int[] args) => base.args = args;
public override double
GetArea() => (args[0] * args[1]) / 2;
public override double
GetPerimeter()
=> Math.Sqrt(Math.Pow(args[0],
2) + Math.Pow(args[1], 2)) + args[0] + args[1];
}
class Program
{
static void Main(string[] args)
{
var c = new Circle(new[] { 10 });
Console.WriteLine($"Circle:\t\t A:{c.GetArea()}\t P:{c.GetPerimeter()}");
var r = new
Rectangle(new[] { 5, 4 });
Console.WriteLine($"Rectangle:\t A:{r.GetArea()}\t\t P:{r.GetPerimeter()}");
var t = new
RightTriangle(new[] { 3, 4
});
Console.WriteLine($"RightTriangle:\t A:{t.GetArea()}\t\t P:{t.GetPerimeter()}");
}
}
}
Now we can see that every time we are going to introduce a new shape we have to inherit from our Shape base class and inherit our abstract methods.
Next let's take a look at our Composite Solution
using System;
namespace pav.StrategyPattern
{
class Program
{
static void Main(string[] args)
{
var c = new Shape(new[] { 10,10 }, new CircleAreaStrategy() , new CirclePerimeterStrategy() );
Console.WriteLine($"Circle:\t\t A:{c.getArea()}\t P:{c.getPerimeter()}");
var r = new Shape(new[] { 5, 4 }, new RectanlgeAreaStrategy(), new RectanglePerimeterStrategy());
Console.WriteLine($"Rectangle:\t A:{r.getArea()}\t\t P:{r.getPerimeter()}");
var t = new Shape(new[] { 3, 4 }, new TriangleAreaStrategy(), new RightTrianglePerimeterStrategy());
Console.WriteLine($"RightTriangle:\t A:{t.getArea()}\t\t P:{t.getPerimeter()}");
}
}
class Shape
{
IAreaStrategy areaStrategy;
IPerimeterStrategy perimeterStrategy;
protected int[] args;
public Shape(int[] args,
IAreaStrategy areaStrategy, IPerimeterStrategy perimeterStrategy)
{
this.args = args;
this.areaStrategy = areaStrategy;
this.perimeterStrategy = perimeterStrategy;
}
public double
getArea() => areaStrategy.GetArea(args);
public double
getPerimeter() => perimeterStrategy.GetPerimeter(args);
}
interface IAreaStrategy { double GetArea(int[] args); }
public class TriangleAreaStrategy :
IAreaStrategy {
public double GetArea(int[] args) =>args[0] * args[1] / 2;
}
public class RectanlgeAreaStrategy :
IAreaStrategy {
public double GetArea(int[] args)
=> args[0] * args[1];
}
public class CircleAreaStrategy :
IAreaStrategy {
public double GetArea(int[] args) => Math.Round(args[0]/2
* Math.PI,2);
}
interface IPerimeterStrategy { double
GetPerimeter(int[] args);
}
public class CirclePerimeterStrategy :
IPerimeterStrategy {
public double
GetPerimeter(int[] args)
=> Math.Round(Math.PI * args[0], 2);
}
public class RectanglePerimeterStrategy :
IPerimeterStrategy {
public double
GetPerimeter(int[] args)
=> 2 * args[0] + 2 * args[1];
}
public class RightTrianglePerimeterStrategy :
IPerimeterStrategy {
public double
GetPerimeter(int[] args)
=> Math.Sqrt(Math.Pow(args[0], 2)
+ Math.Pow(args[1], 2)) + args[0] + args[1];
}
}
One Caveat before the next section, it has been a long time since i've done highschool so my trigonometry might be off, however it's not the point of this entry.
Now instead of a square let's say we have a non right angle triangle, and we have the Base, the Height and one angle like so
well using our composition strategy we could reuse our Area of a triangle strategy and just create a new one for perimeter.
using System;
namespace pav.StrategyPattern
{
class Program
{
static void Main(string[] args)
{
//Diameter
var c = new Shape(new[] { 10 }, new CircleAreaStrategy() , new CirclePerimeterStrategy() );
Console.WriteLine($"Circle:\t\t A:{c.getArea()}\t P:{c.getPerimeter()}");
//Base, Height
var r = new Shape(new[] { 5, 4 }, new RectanlgeAreaStrategy(), new RectanglePerimeterStrategy());
Console.WriteLine($"Rectangle:\t A:{r.getArea()}\t\t P:{r.getPerimeter()}");
//Base, Height
var rt = new Shape(new[] { 3, 4 }, new TriangleAreaStrategy(), new RightTrianglePerimeterStrategy());
Console.WriteLine($"RightTriangle:\t A:{rt.getArea()}\t\t P:{rt.getPerimeter()}");
//Base, Height,
Angle
var t= new Shape(new[] { 4, 2, 55 }, new TriangleAreaStrategy(), new TrianglePerimeterStrategy());
Console.WriteLine($"RightTriangle:\t A:{t.getArea()}\t\t P:{t.getPerimeter()}");
}
}
class Shape
{
IAreaStrategy areaStrategy;
IPerimeterStrategy perimeterStrategy;
protected int[] args;
public Shape(int[] args,
IAreaStrategy areaStrategy, IPerimeterStrategy perimeterStrategy)
{
this.args = args;
this.areaStrategy = areaStrategy;
this.perimeterStrategy = perimeterStrategy;
}
public double
getArea() => areaStrategy.GetArea(args);
public double
getPerimeter() => perimeterStrategy.GetPerimeter(args);
}
interface IAreaStrategy { double GetArea(int[] args); }
public class TriangleAreaStrategy :
IAreaStrategy {
public double GetArea(int[] args)
=>args[0] * args[1] / 2;
}
public class RectanlgeAreaStrategy :
IAreaStrategy {
public double GetArea(int[] args) => args[0] * args[1];
}
public class CircleAreaStrategy :
IAreaStrategy {
public double GetArea(int[] args)
=> Math.Round(args[0]/2 * Math.PI,2);
}
interface IPerimeterStrategy { double
GetPerimeter(int[] args);
}
public class CirclePerimeterStrategy :
IPerimeterStrategy {
public double
GetPerimeter(int[] args)
=> Math.Round(Math.PI * args[0], 2);
}
public class RectanglePerimeterStrategy :
IPerimeterStrategy {
public double
GetPerimeter(int[] args)
=> 2 * args[0] + 2 * args[1];
}
public class RightTrianglePerimeterStrategy :
IPerimeterStrategy {
public double
GetPerimeter(int[] args)
=> Math.Sqrt(Math.Pow(args[0], 2)
+ Math.Pow(args[1], 2)) + args[0] + args[1];
}
class TrianglePerimeterStrategy : IPerimeterStrategy
{
public double GetPerimeter(int[] args)
{
var Base = args[0];
var Height = args[1];
var Theta = (Math.PI / 180) * args[2];
var SideA = Height / Math.Sin(Theta);
var SideB = Math.Sqrt(Math.Pow(SideA, 2) + Math.Pow(Base, 2) - 2 *
Base * SideA * Math.Cos(Theta));
return Math.Round(Base + SideA + SideB);
}
}
}