Giao diện
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ácBà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ầnBà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ệ
passGợi ý
Gợi ý Bài 1
- Dùng
ABCvà@abstractmethodđể buộc subclass implementarea()và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ùngeval()để 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})"