Skip to content

Thực hành: OOP Design Patterns

🎯 Mục tiêu

🎯 Sau bài thực hành này, bạn sẽ:

  • Thiết kế class hierarchy hợp lý với kế thừa và đa hình
  • Triển khai các magic methods: __repr__, __eq__, __lt__
  • Sử dụng @property để kiểm soát truy cập thuộc tính

Yêu cầu

Bài 1: Thiết kế Class Hierarchy

Tạo class Shape làm base class, sau đó kế thừa thành Circle, Rectangle, Triangle.

python
from abc import ABC, abstractmethod
import math

class Shape(ABC):
    """Base class cho tất cả các hình."""
    @abstractmethod
    def area(self) -> float: pass
    @abstractmethod
    def perimeter(self) -> float: pass

class Circle(Shape):
    def __init__(self, radius: float):
        pass  # TODO: Implement constructor với validation

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        pass  # TODO: Implement constructor

class Triangle(Shape):
    def __init__(self, a: float, b: float, c: float):
        pass  # TODO: Implement, kiểm tra bất đẳng thức tam giác

Bài 2: Implement Magic Methods

Thêm __repr__, __eq__, __lt__ cho các class trên để có thể so sánh hình theo diện tích.

python
# Kết quả mong đợi:
c = Circle(5)
r = Rectangle(3, 4)
print(repr(c))       # Circle(radius=5)
print(c == Circle(5)) # True
print(c > r)          # True (so sánh theo area)
shapes = [Circle(3), Rectangle(2, 8), Circle(1)]
print(sorted(shapes)) # Sắp xếp theo diện tích tăng dần

Bài 3: Property và Validation

Sử dụng @property để đảm bảo các thuộc tính luôn hợp lệ.

python
class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius  # Sử dụng setter

    @property
    def radius(self) -> float:
        return self._radius

    @radius.setter
    def radius(self, value: float):
        # TODO: Validate radius > 0, raise ValueError nếu không hợp lệ
        pass

Gợi ý

Gợi ý Bài 1
  • Dùng ABC@abstractmethod để buộc subclass implement area()perimeter()
  • Diện tích tam giác dùng công thức Heron: s = (a+b+c)/2, area = sqrt(s*(s-a)*(s-b)*(s-c))
Gợi ý Bài 2
  • __repr__ nên trả về string có thể dùng eval() để tạo lại object
  • __lt__ so sánh theo area, kết hợp @functools.total_ordering
Gợi ý Bài 3
  • Property setter là nơi lý tưởng để validate dữ liệu
  • Constructor cũng đi qua setter nếu bạn dùng self.radius = value

Lời giải tham khảo

Xem lời giải
python
from abc import ABC, abstractmethod
from functools import total_ordering
import math

@total_ordering
class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass
    @abstractmethod
    def perimeter(self) -> float:
        pass
    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__dict__ == other.__dict__
    def __lt__(self, other):
        if not isinstance(other, Shape):
            return NotImplemented
        return self.area() < other.area()

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius
    @property
    def radius(self) -> float:
        return self._radius
    @radius.setter
    def radius(self, value: float):
        if value <= 0:
            raise ValueError("Radius phải lớn hơn 0")
        self._radius = value
    def area(self) -> float:
        return math.pi * self._radius ** 2
    def perimeter(self) -> float:
        return 2 * math.pi * self._radius
    def __repr__(self):
        return f"Circle(radius={self._radius})"