2
\$\begingroup\$

This is my original take on providing an interface for calculating the dimensions of a rectangle, given optional parameters. I also broke out perfect squares in the instance where only area or perimeter are supplied. I output the rectangle as a JSON string because I have designs on also building out a user interface.

This should essentially work just like the Google Search interface to define dimensions of a rectangle. Provide the length, width and get back area, perimeter, etc. Also, I created a base class of Shape as I have plans to add triangle and circle.

Class: Rectangle

using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;

namespace Shapes
{
    [DataContract]
    public class Rectangle : Shape
    {
        [DataMember] public double Length { get; private set; }
        [DataMember] public double Width { get; private set; }

        /// <summary>
        /// Enumeration denotes what is to be returned
        /// </summary>
        public enum RectangleReturns
        {
            WidthArea = 1,
            LengthArea = 2,
            WidthPerimeter = 3,
            LengthPerimeter = 4
        }

        /// <summary>
        /// Enumeration denotes what is to be returned
        /// </summary>
        public enum PerfectSquareReturns
        {
            Perimeter = 0,
            Area = 1
        }

        /// <summary>
        /// Provide the appropriate values for what you wish to return
        /// </summary>
        /// <param name="rectangleName">The name of your rectangle</param>
        /// <param name="firstDimension">Either the length or width</param>
        /// <param name="secondDimension">Either the width, perimeter or area</param>
        /// <param name="dimensions">What to return based on what was provided</param>
        public Rectangle(string rectangleName, double firstDimension, double secondDimension, RectangleReturns dimensions = 0)
        {
            this.ShapeName = rectangleName;
            if (firstDimension <= 0 || secondDimension <= 0)
            {
                this.ShapeException = "Parameters should be greater than zero";
                return;
            }

            switch (dimensions)
            {
                case RectangleReturns.LengthPerimeter:
                    this.Width = firstDimension;
                    this.Area = secondDimension;
                    this.Length = this.CalculateFromArea(this.Width, this.Area);
                    this.Perimeter = this.CalculatePerimeter(this.Length, this.Width);
                    break;

                case RectangleReturns.WidthPerimeter:
                    this.Length = firstDimension;
                    this.Area = secondDimension;
                    this.Width = this.CalculateFromArea(this.Length, this.Area);
                    this.Perimeter = this.CalculatePerimeter(this.Length, this.Width);
                    break;

                case RectangleReturns.LengthArea:
                    this.Width = firstDimension;
                    this.Perimeter = secondDimension;
                    if (secondDimension <= 2 * firstDimension)
                    {
                        this.ShapeException =
                            "Perimeter should be greater than two times the width";
                        break;
                    }
                    this.Length = this.CalculateFromPerimeter(this.Width, this.Perimeter);
                    this.Area = this.CalculateArea(this.Length, this.Width);
                    break;

                case RectangleReturns.WidthArea:
                    this.Length = firstDimension;
                    this.Perimeter = secondDimension;
                    if (secondDimension <= 2 * firstDimension)
                    {
                        this.ShapeException =
                            "Perimeter should be greater than two times the length";
                        break;
                    }
                    this.Width = this.CalculateFromPerimeter(this.Length, this.Perimeter);
                    this.Area = this.CalculateArea(this.Length, this.Width);
                    break;

                default:
                    this.Length = firstDimension;
                    this.Width = secondDimension;
                    this.Perimeter = this.CalculatePerimeter(this.Length, this.Width);
                    this.Area = this.CalculateArea(this.Length, this.Width);
                    break;
            }
        }

        /// <summary>
        /// Return the perfect square dimensions for a perimeter or area
        /// </summary>
        /// <param name="rectangleName">The name of your rectangle</param>
        /// <param name="firstDimension">Either the perimeter or the area</param>
        /// <param name="dimensions">Which dimension to return</param>
        public Rectangle(string rectangleName, double firstDimension, PerfectSquareReturns dimensions)
        {
            this.ShapeName = rectangleName;
            if (firstDimension <= 0)
            {
                this.ShapeException = "Parameter must be greater than zero";
                return;
            }

            double side = 0;

            // ReSharper disable once SwitchStatementMissingSomeCases
            switch (dimensions)
            {
                case PerfectSquareReturns.Perimeter:
                    side = this.FromPerfectArea(firstDimension);
                    this.Perimeter = this.CalculatePerimeter(side, side);
                    this.Area = firstDimension;
                    break;
                case PerfectSquareReturns.Area:
                    side = this.FromPerfectPerimeter(firstDimension);
                    this.Perimeter = firstDimension;
                    this.Area = this.CalculateArea(side, side);
                    break;
            }

            this.Length = side;
            this.Width = side;
        }

        private double CalculateArea(double length, double width) => length * width;
        private double CalculatePerimeter(double length, double width) => 2 * (length + width);
        private double CalculateFromPerimeter(double side, double perimeter) => perimeter / 2 - side;
        private double CalculateFromArea(double side, double area) => area / side;
        private double FromPerfectPerimeter(double side) => side / 4;
        private double FromPerfectArea(double side) => Math.Sqrt(side);

        public string SerializedRectangle() => JsonConvert.SerializeObject(this, Formatting.Indented);

    }
}

Class: Shape

using System.Runtime.Serialization;

namespace Shapes
{
    [DataContract]
    public class Shape
    {
        [DataMember] public double Perimeter { get; set; }
        [DataMember] public double Area { get; set; }
        [DataMember] public string ShapeName { get; set; }
        [DataMember] public string ShapeException { get; set; }

    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I think you should subclass Square from Rectangle, because a Square actually is a Rectangle, and is a perfect candidate for polymorphism. The Shape class should in my opinion force implementations onto derived classes: protected abstract double GetArea(). Then in this example the Area property on Shape would simply be public double Area => GetArea();It doesn't care where it gets it, it just knows derived classes have to provide the means to calculate it. For serialization purposes, the getter only example may not be what you need, however the approach should still work. \$\endgroup\$ – Parrish Husband Sep 9 '18 at 23:19
  • \$\begingroup\$ @ParrishHusband thanks for the suggestions and I'll look into it more. Would you say though not terrible over all as submitted? \$\endgroup\$ – Steve Sep 10 '18 at 11:58
  • 1
    \$\begingroup\$ It's certainly not bad, and I can see you had put some thought into it. It sometimes gets tricky when trying to fit all the desired functionality into classes. Generally I'm not a fan of parameters having multiple meanings, and I would say that your rectangle constructors are violating open-closed principal. God job though, you made intent very clear. \$\endgroup\$ – Parrish Husband Sep 10 '18 at 14:04
2
\$\begingroup\$

Here is a very rudimentary example of how you can set this up via inheritance. Obviously you'd add in the error checking on the static constructors to sanitize the input, but the intent should be clear.

public interface IShape
{
    double Area { get; }
    double Perimeter { get; }
    string Name { get; set; }
    int Sides { get; }
}

public abstract class Shape : IShape
{
    protected double? _area;
    protected double? _perimeter;

    public double Area => (_area ?? (_area = GetArea())).Value;
    public double Perimeter => (_perimeter ?? (_perimeter = GetPerimeter())).Value;
    public string Name { get; set; }
    public int Sides { get; }

    public Shape() { }

    protected Shape(string name, int sides)
    {
        Name = name;
        Sides = sides;
    }

    protected abstract double GetArea();
    protected abstract double GetPerimeter();

    public override string ToString()
    {
        return $"{this.GetType().Name} {Name}: A={Area}, P={Perimeter}, S={Sides}";
    }
}

public class Rectangle : Shape
{
    public double Length { get; }
    public double Width { get; }

    protected Rectangle(string name, double length, double width)
        : base(name, 4)
    {
        Length = length;
        Width = width;
    }

    public static Rectangle FromLengthWidth(string name, double length, double width)
    {
        return new Rectangle(name, length, width);
    }
    public static Rectangle FromLengthPerimeter(string name, double length, double perimeter)
    {
        // w = (p / 2) - l
        double width = (perimiter / 2) - length;
        return new Rectangle(name, length, width) {_perimeter = perimeter };
    }
    public static Rectangle FromWidthPerimeter(string name, double width, double perimeter)
    {
        // l = (p / 2) - w
        double length = (perimiter / 2) - width;
        return new Rectangle(name, length, width) { _perimeter = perimeter };
    }
    public static Rectangle FromLengthArea(string name, double length, double area)
    {
        // w = a / l
        double width = area / length;
        return new Rectangle(name, length, width) { _area = area };
    }
    public static Rectangle FromWidthArea(string name, double width, double area)
    {
        // l = a / w
        double length = area / width;
        return new Rectangle(name, length, width) { _area = area };
    }

    protected override double GetArea() => Length * Width;
    protected override double GetPerimeter() => 2 * (Length + Width);

}

public class Square : Rectangle
{
    protected Square(string name, double size)
        : base(name, size, size) { }

    public static Square FromSize(string name, double size)
    {
        return new Square(name, size);
    }
    public static Square FromPerimeter(string name, double perimeter)
    {
        // s = p / 4
        double size = perimiter / 4;
        return new Square(name, size) { _perimeter = perimeter };
    }
    public static Square FromArea(string name, double area)
    {
        // s = a^0.5
        double size = Math.Sqrt(area);
        return new Square(name, size) { _area = area };
    }
}

I'm not addressing serialization here, so that will need to be changed in regards to constructors and property accessors to fit your needs.

A simple proof of concept calling example showing the polymorphism:

static void Main(string[] args)
{
    List<Shape> shapes = new List<Shape>();

    shapes.Add(Rectangle.FromWidthArea("WidthArea", 12.5, 120));
    shapes.Add(Square.FromArea("Area", 121.0));

    foreach (IShape shape in shapes)
        Console.WriteLine(shape);

    Console.ReadLine();
} 

Outputs:

Rectangle WidthArea: A=120, P=44.2, S=4

Square Area: A=121, P=44, S=4

\$\endgroup\$
  • \$\begingroup\$ Also I obviously can't do math. \$\endgroup\$ – Parrish Husband Sep 10 '18 at 0:27
1
\$\begingroup\$

I can only add to what Parrish Husband has written:

Consider to rename the sides from "Width" and "Length" to "A" and "B". It will save you a lot of trouble and confusion about which side is Length and which Width (is Length longer than Width or is Width Wider than Length?).


Consider to expand Parrish Husbands class hierarchy further:

// base class
public abstract class Shape
{
  public string Name { get; }
  public double Area { get; }
  public double Perimeter { get; }
}

public class Ellipse : Shape
{
}

public class Circle : Ellipse
{
}

public class Polygon : Shape
{
  public int Sides { get; }
}

public class Rectangle : Polygon 
{
  public double A { get; }
  public double B { get; }
}

public class Square : Rectangle 
{
}
\$\endgroup\$
  • 1
    \$\begingroup\$ It should be width& height but never a & b :-] \$\endgroup\$ – t3chb0t Sep 10 '18 at 9:37
  • 2
    \$\begingroup\$ We could argue about it and both of us would be correct because OP didn't tell us the exact context he needs this for. If it's a pure math application I could live with a&b but if it would be anything graphical then width&height and optional depth (if it was a cube) is a must ;-) \$\endgroup\$ – t3chb0t Sep 10 '18 at 9:57
  • 1
    \$\begingroup\$ Funny, I never gave it a second thought until now. I guess I ran with what Google has and never looked back. The context is only to learn. I will end up wiring it up to a web page though. @HenrikHansen \$\endgroup\$ – Steve Sep 10 '18 at 11:09
  • 1
    \$\begingroup\$ @ParrishHusband: My polymorphism is merely a suggestion for inspiration - I added Polygon because - for instance - a circle doesn't have Sides. Funny enough I've programmed in AutoCAD for many years, and I know the rectangle isn't a defined object in its object model. If you want a rigid object, you could create a region - but from a users perspective it isn't the best solution. Alternatively a dynamic block may be a solution, if you want it to be modifiable. I didn't mean a concrete object but the geometrical figure - a rectangle. \$\endgroup\$ – Henrik Hansen Sep 10 '18 at 15:10
  • 1
    \$\begingroup\$ @HenrikHansen nice, I actually got started in C# from reading Kean Walmsley when I was a drafter/designer. I've heard it argued that a circle has one continuous side, but I think that is an oversimplification/assumption so I tend to agree with you. \$\endgroup\$ – Parrish Husband Sep 10 '18 at 15:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.