設計模式:命令模式(Command Pattern)及例項

养肥胖虎發表於2024-06-08

好傢伙,

0.什麼是命令模式

在軟體系統中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。

但在某些場合,比如要對行為進行“記錄、撤銷/重做、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。

在這種情況下,如何將“行為請求者”與“行為實現者”解耦?將一組行為抽象為物件實現二者之間的松耦合。這就是命令模式(Command Pattern)。

0.1.角色解釋

Command(定義介面): 定義命令的介面,宣告執行的方法。

ConcreteCommand(實現介面):命令介面實現物件,是“虛”的實現;通常會持有接收者,並呼叫接收者的功能來完成命令要執行的操作。

Receiver(接收者):接收者,真正執行命令的物件。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。

Invoker(呼叫者):要求命令物件執行請求,通常會持有命令物件,可以持有很多的命令物件。

    這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令物件的入口。

Client(具體命令):建立具體的命令物件,並且設定命令物件的接收者。注意這個不是我們常規意義上的客戶端,而是在組裝命令物件和接收者,或許,

    把這個Client稱為裝配者會更好理解,因為真正使用命令的客戶端是從Invoker來觸發執行

0.2.模式分析

1.命令模式的本質是對命令進行封裝,將發出命令的責任和執行命令的責任分割開
2.每一個命令都是一個操作:請求的一方發出請求,要求執行一個操作;接收的一方收到請求,並執行操作
3.命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求是怎麼被接收,以及操作是否被執行、何時被執行,以及是怎麼被執行的。
4.命令模式使請求本身成為一個物件,這個物件和其他物件一樣可以被儲存和傳遞。
5.命令模式的關鍵在於引入了抽象命令介面,且傳送者針對抽象命令介面程式設計,只有實現了抽象命令介面的具體命令才能與接收者相關聯

0.3.模式優點

1.降低物件之間的耦合度。
2.新的命令可以很容易地加入到系統中。
3.可以比較容易地設計一個組合命令。
4.呼叫同一方法實現不同的功能

0.4.缺點

使用命令模式可能會導致某些系統有過多的具體命令類。因為針對每一個命令都需要設計一個具體命令類,因此某些系統可能需要大量具體命令類,這將影響命令模式的使用。
                                                          ----以上內容均來自“百度百科”

1.例項

想象一個例項

餐廳點餐案例

現在我走進一家餐廳,向一名服務員點餐,點了一份牛肉漢堡,

服務員將這份”牛肉漢堡"訂單交給廚師,廚師製作這份牛肉牛肉漢堡

來個程式碼實現

// 廚師類
class Chef {
  makeBurger() {
    console.log("This is your burger.");
  }
}

// 服務員類
class Waitress {
  constructor(chef) {
    this.chef = chef;
  }

  takeOrder(order) {
    // 服務員接收訂單並處理
    console.log("Waitress is taking the order for a " + order);
    this.chef.makeBurger(); // 直接通知廚師製作漢堡
  }
}

// 客戶端程式碼
(() => {
  const chef = new Chef();
  const waitress = new Waitress(chef);
  
  // 客戶點餐 服務員通知
  waitress.takeOrder("burger");
})();

那麼如果我使用命令模式去實現呢?

// 命令介面(抽象概念,使用ES6的類來表示)
class Command {
  execute() {
    // 抽象方法execute,將在具體命令中實現
    throw new Error('You should implement the execute method');
  }
}

// 具體命令類
class OrderBurgerCommand extends Command {
  constructor(receiver) {
    super();
    this.receiver = receiver;
  }

  execute() {
    this.receiver.makeBurger();
  }
}

// 接收者類
class Chef {
  makeBurger() {
    console.log("Chef is making a burger.");
  }
}

// 請求者類
class Waitress {
  constructor() {
    this.commands = [];
  }

  takeOrder(command) {
    if (!(command instanceof Command)) {
      throw new Error('You can only take order of Command instances');
    }
    this.commands.push(command);
  }

  notify() {
    this.commands.forEach(command => command.execute());
  }
}

// 客戶類
class Customer {
  constructor() {
    this.waitress = new Waitress();
    this.chef = new Chef();
    this.burgerCommand = new OrderBurgerCommand(this.chef);
  }

  orderBurger() {
    this.waitress.takeOrder(this.burgerCommand);
  }

  serveOrder() {
    this.waitress.notify();
  }
}

// 客戶端程式碼
(() => {
  const customer = new Customer();
  customer.orderBurger();  // 客戶點餐
  customer.serveOrder();   // 服務員通知廚師製作漢堡
})();

2.增加需求

同樣的,如果我們新下單一條“薯條”

使用命令模式

// 命令模式實現

// 命令介面
class Command {
    execute() {
        // 抽象方法,需要在子類中實現
    }
}

// 漢堡命令
class BurgerCommand extends Command {
    constructor(chef) {
        super();
        this.chef = chef;
    }

    execute() {
        this.chef.makeBurger();
    }
}

// 薯條命令
class FrenchFriesCommand extends Command {
    constructor(chef) {
        super();
        this.chef = chef;
    }

    execute() {
        this.chef.makeFrenchFries();
    }
}

// 廚師類
class Chef {
    makeBurger() {
        console.log('製作漢堡');
    }

    makeFrenchFries() {
        console.log('製作薯條');
    }
}

// 服務員類
class Waiter {
    constructor() {
        this.commands = [];
    }

    order(command) {
        this.commands.push(command);
        console.log('訂單已接收');
    }

    serve() {
        this.commands.forEach(command => command.execute());
    }
}

// 客戶類
class Customer {
    constructor(waiter) {
        this.waiter = waiter;
    }

    orderBurger() {
        const chef = new Chef();
        const burgerCommand = new BurgerCommand(chef);
        this.waiter.order(burgerCommand);
    }

    orderFrenchFries() {
        const chef = new Chef();
        const friesCommand = new FrenchFriesCommand(chef);
        this.waiter.order(friesCommand);
    }
}

// 客戶端程式碼
(() => {
    const waiter = new Waiter();
    const customer = new Customer(waiter);

    // 客戶點一份漢堡
    customer.orderBurger();

    // 客戶再點一份薯條
    customer.orderFrenchFries();

    // 服務員開始服務
    waiter.serve();
})();

再結合以上程式碼來看,命令模式的優勢有

1.真正實現瞭解耦:客戶不需要知道漢堡製作的細節,服務員也不需要知道漢堡製作的細節,客戶僅僅是下單,服務員僅僅是通知

2.易於擴充套件:如果需要新增新的操作(如包裝、加熱),可以建立新的命令類,而無需修改現有的類結構。

透過漢堡和薯條的例子,我們可以看到命令模式如何使得程式碼更加靈活、可維護,並且更容易進行擴充套件。

相關文章