© 2024 Merano Tu. All rights reserved.
Merano Tu
2024/12/20
就像我們生活中的吹風機、微波爐等家電,我們預設把插頭插上插座就可以使用了。
…
基本上操作只要按個按鈕就能運作,不會因為些微操作失誤就把電器用壞,製作者也不會提供損害物品的操作方式給使用者。
設計類別的思路也是同理,要設計成可以獨立運行,而不需繁瑣的初始設定也能直接使用。為了防止類別陷入異常狀態並產生 bug,只在外部提供外部能正確操作的函式。
構成要素包含(缺一不可):
如何做到第二點的函式呢?
要進入下一個重點
為了防止半熟物件(partially initialized objects)的產生,應該在建構函式內進行適當的初始化流程,確保在物件建立時所有必要的變數都已經被正確設定。
這樣可以避免物件處於不完整或不一致的狀態,從而減少潛在的錯誤和問題。
半熟物件(Partially Initialized Object)
是指在物件的建構過程中,物件的狀態尚未完全初始化或設定完成的情況。這種物件可能會導致不一致的行為或錯誤,因為它們的某些屬性或方法可能尚未正確設置或準備好使用。
建構函式(Constructor)
是一種特殊的函式,用於在創建物件時初始化該物件的狀態。建構函式通常在類別(class)中定義,並在創建類別的實例時自動調用。建構函式的主要目的是設置物件的初始屬性值或執行任何必要的初始化邏輯。
class Person {
// 在建構函式中檢查變數是否合法存在
constructor(name, age) {
if (!name || !age) {
throw new Error("Name and age are required");
}
this.name = name;
this.age = age;
}
// 之後才可以確保在函式中使用變數不會產生 error
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
class Person {
constructor(name, age) {
if (!name || !age) {
throw new Error("Name and age are required");
}
// 防衛子句:檢查 name 是否存在且為字串
if (typeof name !== 'string' || name.trim() === '') {
throw new Error("Name is required and must be a non-empty string");
}
// 防衛子句:檢查 age 是否存在且為數字
if (typeof age !== 'number' || isNaN(age) || age <= 0) {
throw new Error("Age is required and must be a positive number");
}
this.name = name;
this.age = age;
}
//...略
}
將傳入的變數設置為不可變(immutable)可以確保變數值在整個程式執行過程中不會被意外修改,從而避免潛在的錯誤和不一致性。這種做法有助於提高程式的穩定性和可預測性,特別是在多執行緒或複雜的應用程式中。
在 JavaScript 中,可以使用 const
關鍵字來宣告不可變的變數。
此外,對於物件,可以使用 Object.freeze
方法來防止物件的屬性被修改。
那萬一要修改怎麼辦?
class Person {
constructor(name, age) {
if (typeof name !== 'string' || name.trim() === '') {
throw new Error("Name is required and must be a non-empty string");
}
if (typeof age !== 'number' || isNaN(age) || age <= 0) {
throw new Error("Age is required and must be a positive number");
}
this._name = name;
this._age = age;
// 將物件設置為不可變
Object.freeze(this);
}
get name() {
return this._name;
}
get age() {
return this._age;
}
// 靜態方法來更新 Person 物件
static update(person, newName, newAge) {
return new Person(
newName !== undefined ? newName : person.name,
newAge !== undefined ? newAge : person.age
);
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
try {
const person1 = new Person('Alice', 30);
person1.greet(); // Hello, my name is Alice and I am 30 years old.
const person2 = Person.update(person1, 'Bob', 35);
person2.greet(); // Hello, my name is Bob and I am 35 years old.
class Person {
constructor(name, age) {
this._validateName(name);
this._validateAge(age);
this._name = name;
this._age = age;
// 將物件設置為不可變
Object.freeze(this);
}
get name() {
return this._name;
}
get age() {
return this._age;
}
// 靜態方法來只更新 age
static updateAge(person, newAge) {
// 在這裡也驗證 newAge 是否合法
person._validateAge(newAge);
return new Person(person.name, newAge);
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
// 驗證 name 的方法
_validateName(name) {
if (typeof name !== 'string' || name.trim() === '') {
throw new Error("Name is required and must be a non-empty string");
}
}
// 驗證 age 的方法
_validateAge(age) {
if (typeof age !== 'number' || isNaN(age) || age <= 0) {
throw new Error("Age is required and must be a positive number");
}
}
}
我們使用「值物件」+「完整建構函式」設計模式,創建一個不可變且完全初始化的物件。
值物件(Value Object)是一種設計模式,用於表示一個簡單的、不可變的物件,其等價性基於其屬性的值,而不是物件的身份。
值物件通常用於表示簡單的概念,如金額、日期、座標等。
值物件的特點包括:
完整建構函式是一個設計模式,用於__確保物件在創建時已經完全初始化,並且處於有效狀態。__
完整建構函式應該:
可以呈現物件的含義和相關限制,從而編寫出堅固的程式碼!