Hone logo
Hone
Problems

Designing a Shape Hierarchy with Abstract Base Classes

This challenge focuses on understanding and implementing Abstract Base Classes (ABCs) in Python. ABCs are crucial for defining interfaces and ensuring that subclasses adhere to a specific contract, promoting code reusability and maintainability in object-oriented design. You will create a system for geometric shapes where common behaviors are enforced through an abstract base class.

Problem Description

Your task is to design a system for different geometric shapes (e.g., Circle, Rectangle, Triangle) using Python's abc module. You need to create an abstract base class, Shape, that defines a common interface for all shapes. Specifically, Shape must declare abstract methods for calculating its area and perimeter.

Key Requirements:

  1. Abstract Base Class Shape:

    • Must inherit from abc.ABC.
    • Must define an abstract method area() that returns a floating-point number representing the area of the shape.
    • Must define an abstract method perimeter() that returns a floating-point number representing the perimeter of the shape.
    • Should not provide any implementation for area() or perimeter().
  2. Concrete Shape Classes:

    • Create at least three concrete shape classes: Circle, Rectangle, and Triangle.
    • Each concrete class must inherit from Shape.
    • Each concrete class must implement both the area() and perimeter() methods according to its specific geometric formulas.
    • Instantiating a concrete shape should be done by passing relevant dimensions (e.g., radius for Circle, width and height for Rectangle, three side lengths for Triangle).

Expected Behavior:

  • Attempting to instantiate Shape directly should raise a TypeError.
  • Attempting to instantiate a concrete shape class without implementing all abstract methods should raise a TypeError.
  • Instantiating a concrete shape class and calling its area() and perimeter() methods should return the correct calculated values.

Edge Cases to Consider:

  • For Triangle, consider the triangle inequality theorem (the sum of any two sides must be greater than the third side). While not strictly required to prevent invalid triangles in this challenge, be mindful of how your calculations might behave.
  • Ensure floating-point precision is handled appropriately for calculations.

Examples

Example 1:

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        if radius < 0:
            raise ValueError("Radius cannot be negative")
        self.radius = radius

    def area(self):
        return math.pi * self.radius**2

    def perimeter(self):
        return 2 * math.pi * self.radius

# --- Usage ---
my_circle = Circle(5)
print(f"Circle Area: {my_circle.area()}")
print(f"Circle Perimeter: {my_circle.perimeter()}")
Circle Area: 78.53981633974483
Circle Perimeter: 31.41592653589793

Explanation: A Circle object is created with a radius of 5. Its area() method calculates π * r², and its perimeter() method calculates 2 * π * r.

Example 2:

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        if width < 0 or height < 0:
            raise ValueError("Width and height cannot be negative")
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

# --- Usage ---
my_rectangle = Rectangle(10, 5)
print(f"Rectangle Area: {my_rectangle.area()}")
print(f"Rectangle Perimeter: {my_rectangle.perimeter()}")
Rectangle Area: 50
Rectangle Perimeter: 30

Explanation: A Rectangle object is created with a width of 10 and a height of 5. Its area() method calculates width * height, and its perimeter() method calculates 2 * (width + height).

Example 3:

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Triangle(Shape):
    def __init__(self, side1, side2, side3):
        if any(s <= 0 for s in [side1, side2, side3]):
            raise ValueError("Sides must be positive")
        # Basic check for triangle inequality
        if not (side1 + side2 > side3 and side1 + side3 > side2 and side2 + side3 > side1):
            print("Warning: These sides do not form a valid triangle.")
        self.a, self.b, self.c = side1, side2, side3

    def area(self):
        # Heron's formula
        s = (self.a + self.b + self.c) / 2
        return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))

    def perimeter(self):
        return self.a + self.b + self.c

# --- Usage ---
my_triangle = Triangle(3, 4, 5) # A right-angled triangle
print(f"Triangle Area: {my_triangle.area()}")
print(f"Triangle Perimeter: {my_triangle.perimeter()}")

# Demonstrating instantiation error for incomplete ABC
try:
    class IncompleteShape(Shape):
        def area(self):
            return 10
    IncompleteShape()
except TypeError as e:
    print(f"Error when creating incomplete shape: {e}")
Triangle Area: 6.0
Triangle Perimeter: 12
Error when creating incomplete shape: Can't instantiate abstract class IncompleteShape with abstract methods perimeter

Explanation: A Triangle object is created with sides 3, 4, and 5. Its area() uses Heron's formula, and its perimeter() sums the sides. The example also shows how Python enforces the abstract method contract by raising a TypeError if a subclass doesn't implement all abstract methods.

Constraints

  • All calculations for area and perimeter should be performed using floating-point numbers.
  • Input dimensions for shapes (radius, width, height, side lengths) will be non-negative numbers.
  • Your solution should not use any external libraries beyond Python's standard library, specifically the abc module and math for pi and sqrt.

Notes

  • Remember that abstract methods declared in an ABC cannot be called directly. They are meant to be implemented by concrete subclasses.
  • The abc module provides the @abstractmethod decorator to designate methods as abstract.
  • Consider how you would extend this system to include other shapes like Squares, Ellipses, etc., and how the ABC design facilitates this.
  • For the Triangle class, you can assume valid inputs for sides that form a triangle, or add basic validation as shown in Example 3. The core of the challenge is the ABC structure.
Loading editor...
python