用類別打造智慧養殖場域與感測器物件
把場域資料與操作規則放在一起,讓程式更接近真實世界的設備與管理工作。
完成本篇後,你能建立類別與物件,理解
self、建構子、實例屬性、類別屬性與方法;能使用屬性驗證、類別方法、組合及繼承,建立多場域感測資料模型。一、只用字典有什麼問題?
字典很適合保存一筆資料,但資料與規則分散後,容易寫錯鍵名,也難以確保每座場域都具有相同欄位。
site = {
"name": "示範池 A",
"temperature": 27.2,
"ph": 7.4,
}
if 24.0 <= site["temperature"] <= 30.0:
print(site["name"], "水溫在示意範圍內")若每次檢查都重新寫判斷式,規則很可能不一致。類別可以定義共同藍圖,物件則是依藍圖建立的個別場域。
| 概念 | 智慧養殖比喻 | Python 表示 |
|---|---|---|
| 類別 class | 場域資料的設計規格 | AquacultureSite |
| 物件 object | 依規格建立的某座場域 | site_a |
| 屬性 attribute | 名稱、水溫、pH | site_a.temperature |
| 方法 method | 更新讀值、產生摘要 | site_a.summary() |
二、第一個場域類別
__init__ 是建立物件時自動執行的建構方法;self 代表目前正在操作的物件。
class AquacultureSite:
def __init__(self, name, temperature, ph):
self.name = name
self.temperature = temperature
self.ph = ph
def summary(self):
return (
f"{self.name}|水溫 {self.temperature}°C"
f"|pH {self.ph}"
)
site_a = AquacultureSite("示範池 A", 27.2, 7.4)
site_b = AquacultureSite("示範池 B", 28.1, 7.1)
print(site_a.summary())
print(site_b.summary())site_a 與 site_b 各自保存一份實例屬性。呼叫 site_a.summary() 時,Python 會把 site_a 自動傳給 self。
三、讓物件負責自己的資料更新
不要任由外部程式到處修改屬性,可建立方法統一驗證流程。
class SensorReading:
def __init__(self, sensor_id, value, unit):
self.sensor_id = sensor_id
self.unit = unit
self.update(value)
def update(self, value):
if not isinstance(value, (int, float)):
raise TypeError("感測值必須是數字")
self.value = float(value)
def display(self):
return f"{self.sensor_id}: {self.value}{self.unit}"
temperature = SensorReading("TEMP-01", 27.2, "°C")
temperature.update(27.6)
print(temperature.display())raise 會主動產生例外,阻止不合理資料進入物件。下一篇將完整學習如何捕捉與處理例外。
四、使用 property 驗證屬性
property 讓外部仍以屬性方式讀寫,但實際上會經過驗證方法。前置底線 _value 表示這是內部使用的慣例名稱,並非真正禁止存取。
class PHSensor:
def __init__(self, sensor_id, value):
self.sensor_id = sensor_id
self.value = value
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
new_value = float(new_value)
if not 0.0 <= new_value <= 14.0:
raise ValueError("pH 必須介於 0 到 14")
self._value = new_value
sensor = PHSensor("PH-01", 7.4)
sensor.value = 7.2
print(sensor.value)五、實例屬性與類別屬性
實例屬性屬於個別物件;類別屬性由所有物件共享,適合記錄共同版本或物件數量。
class Device:
platform_name = "水井村智慧巡查教學平台"
device_count = 0
def __init__(self, device_id):
self.device_id = device_id
Device.device_count += 1
device_a = Device("TEMP-01")
device_b = Device("PH-01")
print(Device.platform_name)
print("裝置數量:", Device.device_count)self;整個類別共同使用的資料才放成類別屬性。可變的串列或字典若設成類別屬性,所有物件會共用同一份,須特別小心。六、類別方法:從字典建立物件
@classmethod 接收類別本身 cls,常用於提供另一種建立物件的方式。
class AquacultureSite:
def __init__(self, name, temperature, ph):
self.name = name
self.temperature = float(temperature)
self.ph = float(ph)
@classmethod
def from_dict(cls, data):
return cls(
data["場域"],
data["水溫"],
data["pH"],
)
def summary(self):
return f"{self.name}|{self.temperature}°C|pH {self.ph}"
data = {"場域": "示範池 C", "水溫": 26.8, "pH": 7.3}
site_c = AquacultureSite.from_dict(data)
print(site_c.summary())七、組合:一座場域擁有多個感測器
「場域擁有感測器」適合使用組合,而不是繼承。場域物件內保存感測器物件,讓兩種責任保持清楚。
class Sensor:
def __init__(self, sensor_id, kind, value, unit):
self.sensor_id = sensor_id
self.kind = kind
self.value = float(value)
self.unit = unit
def display(self):
return f"{self.kind}: {self.value}{self.unit}"
class Site:
def __init__(self, name):
self.name = name
self.sensors = []
def add_sensor(self, sensor):
if not isinstance(sensor, Sensor):
raise TypeError("只能加入 Sensor 物件")
self.sensors.append(sensor)
def report(self):
lines = [f"【{self.name}】"]
lines.extend(sensor.display() for sensor in self.sensors)
return "\n".join(lines)
site = Site("示範池 A")
site.add_sensor(Sensor("TEMP-01", "水溫", 27.2, "°C"))
site.add_sensor(Sensor("PH-01", "pH", 7.4, ""))
print(site.report())八、繼承:建立不同種類的感測器
若多種感測器都具有編號、數值與顯示功能,可把共同部分放在父類別,再由子類別補充差異。
class Sensor:
def __init__(self, sensor_id, value):
self.sensor_id = sensor_id
self.value = float(value)
def status(self):
return "尚未定義判斷規則"
class TemperatureSensor(Sensor):
def status(self):
return "讀值已收到,請依場域標準判讀"
class PHSensor(Sensor):
def status(self):
if 0.0 <= self.value <= 14.0:
return "讀值格式合理,仍須依場域標準判讀"
return "讀值超出 pH 理論範圍,請檢查設備"
sensors = [
TemperatureSensor("TEMP-01", 27.2),
PHSensor("PH-01", 7.4),
]
for sensor in sensors:
print(sensor.sensor_id, sensor.status())兩個子類別都能呼叫 status(),卻可有不同實作,這稱為多型。外部程式不必用大量 if 判斷感測器種類。
九、組合還是繼承?
| 關係 | 設計方式 | 例子 |
|---|---|---|
| 「是一種」is-a | 可考慮繼承 | pH 感測器是一種感測器 |
| 「擁有」has-a | 優先考慮組合 | 養殖場域擁有多個感測器 |
不要為了使用類別而建立很深的繼承層次。若兩個物件只是合作關係,組合通常更容易替換與測試。
十、物件如何轉回可儲存資料?
物件不能直接假設可以存進 JSON。先提供 to_dict(),把需要公開的欄位轉成基本資料型別。
class Sensor:
def __init__(self, sensor_id, kind, value, unit):
self.sensor_id = sensor_id
self.kind = kind
self.value = float(value)
self.unit = unit
def to_dict(self):
return {
"sensor_id": self.sensor_id,
"kind": self.kind,
"value": self.value,
"unit": self.unit,
}
sensor = Sensor("TEMP-01", "水溫", 27.2, "°C")
print(sensor.to_dict())十一、常見錯誤除錯表
| 現象 | 可能原因 | 修正方式 |
|---|---|---|
| 方法缺少參數錯誤 | 忘記在實例方法第一個參數寫 self | 補上 self,並由物件呼叫 |
| 所有物件共用同一串列 | 把可變串列設成類別屬性 | 在 __init__ 建立實例串列 |
| 建立物件時立即出錯 | 建構子參數數量或型別不符 | 檢查類別介面並加入清楚驗證 |
| 屬性名稱打錯 | AttributeError | 統一命名,必要時提供 property |
| 繼承後父類別資料不存在 | 子類別未呼叫父類別建構子 | 使用 super().__init__(...) |
十二、USR 實作挑戰
建立七個匿名教學場域物件,每座加入水溫與 pH 感測器,再輸出巡查摘要。公開展示時不要放入養殖戶姓名或精確位置。
替 Sensor 加入最後更新時間與連線狀態,建立
is_stale() 方法判斷資料是否過期。與養殖戶共同討論規則應放在哪個物件,區分「感測器讀值是否合理」與「養殖管理是否需要處理」。
十三、與生成式 AI 協作
請擔任 Python 物件導向設計助教。
我要建立智慧養殖教學系統,包含場域、感測器與巡查報告。
請先用「是一種」與「擁有」分析類別關係,再提出簡潔的類別設計。
要求:
1. 不建立過深的繼承;
2. 感測資料必須驗證型別;
3. 不自行設定真實養殖安全門檻;
4. 提供 to_dict() 供後續存檔;
5. 說明哪些資料不宜公開。類別是資料與操作規則的共同藍圖,物件則代表個別場域或設備。先釐清責任,再選擇組合或繼承,才能讓程式隨場域擴充而不失去可讀性。
沒有留言:
張貼留言