Abstract Shape Hierarchy
This challenge focuses on understanding and implementing abstract classes in TypeScript. Abstract classes are crucial for establishing common interfaces and behaviors for a group of related classes, promoting code reusability and enforcing a consistent structure. You will create a hierarchy of shapes, starting with an abstract base class.
Problem Description
Your task is to create an abstract class Shape that defines a common interface for geometric shapes. This abstract class should have:
- An abstract method
getArea()that must be implemented by any concrete subclass to calculate the area of the specific shape. - An abstract method
getPerimeter()that must be implemented by any concrete subclass to calculate the perimeter of the specific shape. - A concrete method
describe()that returns a string describing the shape, using the information provided by concrete subclasses.
You will then create two concrete subclasses of Shape: Circle and Rectangle.
Circleshould have aradiusproperty. ItsgetArea()should calculate π * r², and itsgetPerimeter()should calculate 2 * π * r. Thedescribe()method should also include the radius.Rectangleshould havewidthandheightproperties. ItsgetArea()should calculate width * height, and itsgetPerimeter()should calculate 2 * (width + height). Thedescribe()method should also include the width and height.
The expected behavior is that you can instantiate Circle and Rectangle objects, and call getArea(), getPerimeter(), and describe() on them. You should not be able to instantiate the Shape class directly.
Examples
Example 1:
// Assume Shape, Circle, and Rectangle classes are defined as per the problem description.
const myCircle = new Circle(5);
console.log(myCircle.getArea()); // Expected Output: ~78.53981633974483
console.log(myCircle.getPerimeter()); // Expected Output: ~31.41592653589793
console.log(myCircle.describe()); // Expected Output: "This is a Circle with radius 5."
Explanation: A Circle object is created with a radius of 5. Its area and perimeter are calculated using the appropriate formulas, and its description includes the radius.
Example 2:
// Assume Shape, Circle, and Rectangle classes are defined as per the problem description.
const myRectangle = new Rectangle(4, 6);
console.log(myRectangle.getArea()); // Expected Output: 24
console.log(myRectangle.getPerimeter()); // Expected Output: 20
console.log(myRectangle.describe()); // Expected Output: "This is a Rectangle with width 4 and height 6."
Explanation: A Rectangle object is created with a width of 4 and a height of 6. Its area and perimeter are calculated correctly, and its description includes its dimensions.
Example 3: (Edge Case/Scenario)
// Assume Shape, Circle, and Rectangle classes are defined as per the problem description.
// Attempting to instantiate the abstract class should result in a TypeScript error.
// const genericShape = new Shape(); // This line should cause a compile-time error.
// You can, however, use the abstract class type for variable declarations.
let shapeVariable: Shape;
shapeVariable = new Circle(10);
console.log(shapeVariable.describe()); // Expected Output: "This is a Circle with radius 10."
shapeVariable = new Rectangle(3, 7);
console.log(shapeVariable.describe()); // Expected Output: "This is a Rectangle with width 3 and height 7."
Explanation: This example demonstrates that the abstract Shape class cannot be instantiated directly. However, it can be used as a type to hold instances of its concrete subclasses, showcasing polymorphism.
Constraints
- All calculations should use standard JavaScript
Math.PIfor π. - Method names and property names must match those specified in the problem description.
- No external libraries are permitted for mathematical operations.
- The solution must be written entirely in TypeScript.
Notes
- Remember that
abstractmethods in TypeScript do not have an implementation in the abstract class. They are declared but left empty, to be implemented by concrete subclasses. - Consider how you will pass dimensions (radius, width, height) to your concrete shape classes. Constructors are the standard way to do this.
- Think about how the
describe()method will access the specific properties of its subclasses. You'll likely need to cast or use type guards if you were to implement a singledescribein the abstract class that needs subclass-specific properties, but in this case, the abstract class'sdescribewill rely on subclasses to provide the specific details.