8
votes

I am trying to learn better programming practices using SOLID principles. Here I am working on a sample application of Shapes. I just want to know, am I breaking the principle anywhere.Below are classes and its code.

1. Base Class - Shape

public abstract class Shape
{
    public abstract double Area();
    public virtual double Volume() 
    { 
        throw new NotImplementedException("You cannot determine volume from here...Method not implemented."); 
    }
}

2. Classes for Shapes like Rectangle, Triangle etc implementing base class Shape.

public class Circle : Shape
{
    public int Radius { get; set; }
    public override double Area() { return 3.14 * Radius * Radius; }
}

public class Triangle : Shape
{
    public int Height { get; set; }
    public int Base { get; set; }
    public override double Area()
    {
        return 0.5 * Base * Height;
    }
}

public class Rectangle : Shape
{
   public int Length { get; set; }
   public int Breadth { get; set; }
   public override double Area()
   {
        return Length * Breadth;
   }
}

public class Square : Shape
{
    public Square() { }
    public int Side { get; set; }
    public override double Area()
    {
        return Side * Side;
    }
}

3. A factory class that returns Shape.

internal class ShapeFactory<K, T> where T : class, K, new()
{
    static K k;
    private ShapeFactory() { }

    public static K Create()
    {
        k = new T();
        return k;
    }
}

Till here everything seems fine and looks good, but problem occurs when I implemented it. I am little confused here. Lets see the front end code first:

internal class Program
{
    private static void Main(string[] args)
    {
        try
        {

            var c = ShapeFactory<Shape, Circle>.Create();
            // this part is not clear to me. See the questions below
            if(c is Circle)
            {
                var circle = c as Circle;
                circle.Radius = 5;
                Console.WriteLine(string.Format("{0}", circle.Area()));
            }


        }

        catch (Exception ex)
        {

            Console.WriteLine("Error: {0}", ex.Message);
        }
        Console.Read();
    }
}

QUESTIONS

  1. Different shapes has got different properties like circle has Radius, triangle has base and height and so on , so i decided to keep my properties in child class. I knew, I can have that as virtual member in my base class. So Is there any way other than coded above.

  2. If not, then what is the use of abstract class, if still I am typecasting my Shape object to circle object? I can simple use Circle c = new Circle(). I don't want unwanted checks like (if c is circle) and all.

  3. What If , I am asked to implement a new method to get Circumference of a circle. Do I need to create a new Abstract class or put it in Circle class. But if I put it Circle, I think it will break very first principle of SOLID i.e. SRP . Kindly note, I don't my abstract class as a fat class having unnecessary or repeated properties.

Thanks in advance

4
codereview.stackexchange.com would be better suited for this question - Papa

4 Answers

6
votes

What I usually do in this case is to pass constructor parameters in concrete classes. So i'd change your concrete shapes to something like:

public class Circle : Shape
{
    public int Radius { get; set; }

    public Circle(int radius) {
        this.Radius = radius;
    }

    public override double Area() { return 3.14 * this.Radius * this.Radius; }
}

public class Rectangle : Shape
{
   public int Length { get; set; }
   public int Breadth { get; set; }

   public Rectangle(int lenght, int breadth) {
        this.Length = lenght;
        this.Breadth = breadth;
   }

   public override double Area()
   {
        return Length * Breadth;
   }
}

and so on

Now, I would use a factory method, so your fabric will now be like:

public abstract class ShapeFactory
{
    abstract Create();
}

public class CircleFactory : ShapeFactory
{
    private int radius;

    public CircleFactory(int radius){
        this.radius = radius;
    }

    protected override Shape Create()
    {
        return new Circle(this.radius);
    }
}

public class RectangleFactory : ShapeFactory
{
    private int length;
    private int breadth;

    public RectangleFactory(int length, int breadth){
        this.lenght = length;
        this.breadth = breadth;     
}

    protected override Shape Create()
    {
        return new Rectangle(this.length, this.breadth);
    }
}

Notice that, now a factory know how to build a shape with constructor passed in its own constructor.

So, each time you want a diferent shape you will instantiate a new factory.

ShapeFactory factory = new CircleFactory(5);
Shape shape = factory.Create();
Console.WriteLine(shape.Area()));

I think this answer your 1st and 2nd question.

So, 3: What you can do to dont modify your class is use the strategy pattern in order to pass at runtime how to implement this method:

public interface IPerimeter
{
    int calculatePerimeter();
}

public class Circunference : IPerimeter 
{
    public int calculatePerimeter(Circle circle) {
        return 2*pi*circle.radius;
    } 
}

public class Circle : Shape
{
    public int Radius { get; set; }
    private IPerimeter perimeter;

    public Circle(int radius, IPerimeter perimeter) {
        this.Radius = radius;
        this.perimeter = perimeter;
    }

    public Circunference() {
        perimeter.calculatePerimeter(this);
    }

    public override double Area() { return 3.14 * this.Radius * this.Radius; }
}

Hope this helps with your training.

0
votes
  1. Different child classes will have different properties, that's expected and ok. Normally not all derived classes have the exact same properties as their base class. There's no reason to force Shape to have a Radius. What advantage would you have? That's just opening the door for trouble. What's your ultimate goal with this? Having something like myShape.Dimension = value and not care if it's a radius, a side, etc.? Anything can be done, depending on your needs.

  2. With your abstract class you can, for example, loop through a list of Shape and call Area() or Volume(), knowing that you will get your result (despite your still not implemented Volume). Also your base class could have some common code, which in this case you are not using. You could have for example a Unit property which could be cm, inches, meters, etc. and then have a method like this (silly example):

    public string GetAreaString()
    {
        return string.Format("{0} {1}", this.Area().ToString(), this.Unit);
    }
    
  3. Just implement it in Circle, of course. Why would it break Circle's single responsibility? Your class is dealing with the calculation of its related values, just like a string tells you if it's null or its length.

0
votes

For me your example seems really over engineered. I think you should always implement the simplest thing that works nothing more nothing less. I know that this is an example code, because you want to learn the SOLID principles, but I think is important to be aware of how horribly wrong can go these principles in the wrong context. In your specific code: do you need to group all your shapes using the Shape class? I mean, do you ever plan to iterate through a list of Shapes and calculate the area and volume for them? If not, the inheritance has absolutely no point. In fact I would say that inheritance is overused these days, and when it is overused you end up with ugly inheritance dependency graphs. Regarding the factory class: Is construction of any of your "shape" objects particularly difficult, time consuming, tricky. Does your factory class provide some value or it is completely useless? In case it has no real reason to exist, I wouldn't use it, the new operator is far more clear.

I hope you don't mind my reply but I just wanted you to be aware of the fact that some SOLID principles applies in very specific scenarios. Forcing them in the wrong places may cause ugly and over complicated code. In some real world situations, if the questions above are answered with yes, your pattern seems OK. Otherwise the exact same pattern can over-complicate things without any real benefits. I guess my point is: just be aware, not every SOLID principle is good in any situation:).

0
votes

This is extremely common problem. While learning SOLID is nice, it requires understanding of basic design principles like abstraction and indirection. The reason why you are confused is because there is no abstraction in your code.

Imagine you have code that wants to know shape's area, but it doesn't care what shape it is nor how to calculate that shape's area. Something like :

public void PrintArea(Shape shape)
{
    Console.WriteLine(shape.Area());
}

This is THE CRITICAL PART of OOP design. Your example has absolutely nothing of this. Your example is just contrived piece of code that has no logic to it, let alone being SOLID.