圖解常見的九種設計模式

2021-01-11 歪碼行空

在軟體工程中,設計模式(Design Pattern)是對軟體設計中普遍存在(反覆出現)的各種問題,所提出的解決方案。根據模式的目的來劃分的話,GoF(Gang of Four)設計模式可以分為以下 3 種類型:

1、創建型模式:用來描述 「如何創建對象」,它的主要特點是 「將對象的創建和使用分離」。包括單例、原型、工廠方法、抽象工廠和建造者 5 種模式。

2、結構型模式:用來描述如何將類或對象按照某種布局組成更大的結構。包括代理、適配器、橋接、裝飾、外觀、享元和組合 7 種模式。

3、行為型模式:用來識別對象之間的常用交流模式以及如何分配職責。包括模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄和解釋器 11 種模式。

接下來阿寶哥將結合一些生活中的場景並通過精美的配圖,來向大家介紹 9 種常用的設計模式。

一、建造者模式

建造者模式(Builder Pattern)將一個複雜對象分解成多個相對簡單的部分,然後根據不同需要分別創建它們,最後構建成該複雜對象。

一輛小汽車 🚗 通常由 發動機、底盤、車身和電氣設備 四大部分組成。汽車電氣設備的內部構造很複雜,簡單起見,我們只考慮三個部分:引擎、底盤和車身。

在現實生活中,小汽車也是由不同的零部件組裝而成,比如上圖中我們把小汽車分成引擎、底盤和車身三大部分。下面我們來看一下如何使用建造者模式來造車子。

1.1 實現代碼
class Car {
  constructor(
    public engine: string,
    public chassis: string, 
    public body: string
  ) {}
}

class CarBuilder {
  engine!: string; // 引擎
  chassis!: string; // 底盤
  body!: string; // 車身

  addChassis(chassis: string) {
    this.chassis = chassis;
    return this;
  }

  addEngine(engine: string) {
    this.engine = engine;
    return this;
  }

  addBody(body: string) {
    this.body = body;
    return this;
  }

  build() {
    return new Car(this.engine, this.chassis, this.body);
  }
}

在以上代碼中,我們定義一個 CarBuilder 類,並提供了 addChassis、addEngine 和 addBody 3 個方法用於組裝車子的不同部位,當車子的 3 個部分都組裝完成後,調用 build 方法就可以開始造車。

1.2 使用示例
const car = new CarBuilder()
  .addEngine('v12')
  .addBody('鎂合金')
  .addChassis('複合材料')
  .build();

1.3 應用場景及案例需要生成的產品對象有複雜的內部結構,這些產品對象通常包含多個成員屬性。需要生成的產品對象的屬性相互依賴,需要指定其生成順序。隔離複雜對象的創建和使用,並使得相同的創建過程可以創建不同的產品。Github - node-sql-query:https://github.com/dresende/node-sql-query二、工廠模式

在現實生活中,工廠是負責生產產品的,比如牛奶、麵包或禮物等,這些產品滿足了我們日常的生理需求。

在眾多設計模式當中,有一種被稱為工廠模式的設計模式,它提供了創建對象的最佳方式。工廠模式可以分為:簡單工廠模式、工廠方法模式和抽象工廠模式

2.1 簡單工廠

簡單工廠模式又叫 靜態方法模式,因為工廠類中定義了一個靜態方法用於創建對象。簡單工廠讓使用者不用知道具體的參數就可以創建出所需的 」產品「 類,即使用者可以直接消費產品而不需要知道產品的具體生產細節。

在上圖中,阿寶哥模擬了用戶購車的流程,小王和小秦分別向 BMW 工廠訂購了 BMW730 和 BMW840 型號的車型,接著工廠會先判斷用戶選擇的車型,然後按照對應的模型進行生產並在生產完成後交付給用戶。

下面我們來看一下如何使用簡單工廠來描述 BMW 工廠生產指定型號車子的過程。

2.1.1 實現代碼
abstract class BMW {
  abstract run(): void;
}

class BMW730 extends BMW {
  run(): void {
    console.log("BMW730 發動咯");
  }
}

class BMW840 extends BMW {
  run(): void {
    console.log("BMW840 發動咯");
  }
}

class BMWFactory {
  public static produceBMW(model: "730" | "840"): BMW {
    if (model === "730") {
      return new BMW730();
    } else {
      return new BMW840();
    }
  }
}

在以上代碼中,我們定義一個 BMWFactory 類,該類提供了一個靜態的 produceBMW() 方法,用於根據不同的模型參數來創建不同型號的車子。

2.1.2 使用示例
const bmw730 = BMWFactory.produceBMW("730");
const bmw840 = BMWFactory.produceBMW("840");

bmw730.run();
bmw840.run();

2.1.3 應用場景工廠類負責創建的對象比較少:由於創建的對象比較少,不會造成工廠方法中業務邏輯過於複雜。客戶端只需知道傳入工廠類靜態方法的參數,而不需要關心創建對象的細節。2.2 工廠方法

工廠方法模式(Factory Method Pattern)又稱為工廠模式,也叫多態工廠(Polymorphic Factory)模式,它屬於類創建型模式。

在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象, 這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。

在上圖中,阿寶哥模擬了用戶購車的流程,小王和小秦分別向 BMW 730 和 BMW 840 工廠訂購了 BMW730 和 BMW840 型號的車子,接著工廠按照對應的模型進行生產並在生產完成後交付給用戶。

同樣,我們來看一下如何使用工廠方法來描述 BMW 工廠生產指定型號車子的過程。

2.2.1 實現代碼
abstract class BMWFactory {
  abstract produceBMW(): BMW;
}

class BMW730Factory extends BMWFactory {
  produceBMW(): BMW {
    return new BMW730();
  }
}

class BMW840Factory extends BMWFactory {
  produceBMW(): BMW {
    return new BMW840();
  }
}

在以上代碼中,我們分別創建了 BMW730Factory 和 BMW840Factory 兩個工廠類,然後使用這兩個類的實例來生產不同型號的車子。

2.2.2 使用示例
const bmw730Factory = new BMW730Factory();
const bmw840Factory = new BMW840Factory();

const bmw730 = bmw730Factory.produceBMW();
const bmw840 = bmw840Factory.produceBMW();

bmw730.run();
bmw840.run();

2.2.3 應用場景一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和裡氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。2.3 抽象工廠

抽象工廠模式(Abstract Factory Pattern),提供一個創建一系列相關或相互依賴對象的接口,而無須指定它們具體的類。

在工廠方法模式中具體工廠負責生產具體的產品,每一個具體工廠對應一種具體產品,工廠方法也具有唯一性,一般情況下,一個具體工廠中只有一個工廠方法或者一組重載的工廠方法。但是有時候我們需要一個工廠可以提供多個產品對象,而不是單一的產品對象。

在上圖中,阿寶哥模擬了用戶購車的流程,小王向 BMW 工廠訂購了 BMW730,工廠按照 730 對應的模型進行生產並在生產完成後交付給小王。而小秦向同一個 BMW 工廠訂購了 BMW840,工廠按照 840 對應的模型進行生產並在生產完成後交付給小秦。

下面我們來看一下如何使用抽象工廠來描述上述的購車過程。

2.3.1 實現代碼
abstract class BMWFactory {
  abstract produce730BMW(): BMW730;
  abstract produce840BMW(): BMW840;
}

class ConcreteBMWFactory extends BMWFactory {
  produce730BMW(): BMW730 {
    return new BMW730();
  }

  produce840BMW(): BMW840 {
    return new BMW840();
  }
}

2.3.2 使用示例
const bmwFactory = new ConcreteBMWFactory();

const bmw730 = bmwFactory.produce730BMW();
const bmw840 = bmwFactory.produce840BMW();

bmw730.run();
bmw840.run();

2.3.3 應用場景一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有類型的工廠模式都是重要的。系統中有多於一個的產品族,而每次只使用其中某一產品族。系統提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現。三、單例模式

單例模式(Singleton Pattern)是一種常用的模式,有一些對象我們往往只需要一個,比如全局緩存、瀏覽器中的 window 對象等。單例模式用於保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

在上圖中,阿寶哥模擬了借車的流程,小王臨時有急事找阿寶哥借車子,阿寶哥家的車子剛好沒用,就借給小王了。當天,小秦也需要用車子,也找阿寶哥借車,因為阿寶哥家裡只有一輛車子,所以就沒有車可借了。

對於車子來說,它雖然給生活帶來了很大的便利,但養車也需要一筆不小的費用(車位費、油費和保養費等),所以阿寶哥家裡只有一輛車子。

在開發軟體系統時,如果遇到創建對象時耗時過多或耗資源過多,但又經常用到的對象,我們就可以考慮使用單例模式。

下面我們來看一下如何使用 TypeScript 來實現單例模式。

3.1 實現代碼
class Singleton {
  // 定義私有的靜態屬性,來保存對象實例
  private static singleton: Singleton;
  private constructor() {}

  // 提供一個靜態的方法來獲取對象實例
  public static getInstance(): Singleton {
    if (!Singleton.singleton) {
      Singleton.singleton = new Singleton();
    }
    return Singleton.singleton;
  }
}

3.2 使用示例
let instance1 = Singleton.getInstance();
let instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // true

3.3 應用場景創建對象時耗時過多或耗資源過多,但又經常用到的對象。系統只需要一個實例對象,如系統要求提供一個唯一的序列號生成器或資源管理器,或者需要考慮資源消耗太大而只允許創建一個對象。四、適配器模式

在實際生活中,也存在適配器的使用場景,比如:港式插頭轉換器、電源適配器和 USB 轉接口。而在軟體工程中,適配器模式的作用是解決兩個軟體實體間的接口不兼容的問題使用適配器模式之後,原本由於接口不兼容而不能工作的兩個軟體實體就可以一起工作。

4.1 實現代碼
interface Logger {
  info(message: string): Promise<void>;
}

interface CloudLogger {
  sendToServer(message: string, type: string): Promise<void>;
}

class AliLogger implements CloudLogger {
  public async sendToServer(message: string, type: string): Promise<void> {
    console.info(message);
    console.info('This Message was saved with AliLogger');
  }
}

class CloudLoggerAdapter implements Logger {
  protected cloudLogger: CloudLogger;

  constructor (cloudLogger: CloudLogger) {
    this.cloudLogger = cloudLogger;
  }

  public async info(message: string): Promise<void> {
    await this.cloudLogger.sendToServer(message, 'info');
  }
}

class NotificationService {
  protected logger: Logger;
  
  constructor (logger: Logger) {    
    this.logger = logger;
  }

  public async send(message: string): Promise<void> {
    await this.logger.info(`Notification sended: ${message}`);
  }
}

在以上代碼中,因為 Logger 和 CloudLogger 這兩個接口不匹配,所以我們引入了 CloudLoggerAdapter 適配器來解決兼容性問題。

4.2 使用示例
(async () => {
  const aliLogger = new AliLogger();
  const cloudLoggerAdapter = new CloudLoggerAdapter(aliLogger);
  const notificationService = new NotificationService(cloudLoggerAdapter);
  await notificationService.send('Hello semlinker, To Cloud');
})();

4.3 應用場景及案例以前開發的系統存在滿足新系統功能需求的類,但其接口同新系統的接口不一致。使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同。Github - axios-mock-adapter:https://github.com/ctimmerm/axios-mock-adapter五、觀察者模式 & 發布訂閱模式5.1 觀察者模式

觀察者模式,它定義了一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知所有的觀察者對象,使得它們能夠自動更新自己。

在觀察者模式中有兩個主要角色:Subject(主題)和 Observer(觀察者)。

在上圖中,Subject(主題)就是阿寶哥的 TS 專題文章,而觀察者就是小秦和小王。由於觀察者模式支持簡單的廣播通信,當消息更新時,會自動通知所有的觀察者。

下面我們來看一下如何使用 TypeScript 來實現觀察者模式。

5.1.1 實現代碼
interface Observer {
  notify: Function;
}

class ConcreteObserver implements Observer{
  constructor(private name: string) {}

  notify() {
    console.log(`${this.name} has been notified.`);
  }
}

class Subject { 
  private observers: Observer[] = [];

  public addObserver(observer: Observer): void {
    console.log(observer, "is pushed!");
    this.observers.push(observer);
  }

  public deleteObserver(observer: Observer): void {
    console.log("remove", observer);
    const n: number = this.observers.indexOf(observer);
    n != -1 && this.observers.splice(n, 1);
  }

  public notifyObservers(): void {
    console.log("notify all the observers", this.observers);
    this.observers.forEach(observer => observer.notify());
  }
}

5.1.2 使用示例
const subject: Subject = new Subject();
const xiaoQin = new ConcreteObserver("小秦");
const xiaoWang = new ConcreteObserver("小王");
subject.addObserver(xiaoQin);
subject.addObserver(xiaoWang);
subject.notifyObservers();

subject.deleteObserver(xiaoQin);
subject.notifyObservers();

5.1.3 應用場景及案例一個對象的行為依賴於另一個對象的狀態。或者換一種說法,當被觀察對象(目標對象)的狀態發生改變時 ,會直接影響到觀察對象的行為。RxJS Subject:https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subject.tsRxJS Subject 文檔:https://rxjs.dev/guide/subject5.2 發布訂閱模式

在軟體架構中,發布/訂閱是一種消息範式,消息的發送者(稱為發布者)不會將消息直接發送給特定的接收者(稱為訂閱者)。而是將發布的消息分為不同的類別,然後分別發送給不同的訂閱者。 同樣的,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的消息,無需了解哪些發布者存在。

在發布訂閱模式中有三個主要角色:Publisher(發布者)、 Channels(通道)和 Subscriber(訂閱者)。

在上圖中,Publisher(發布者)是阿寶哥,Channels(通道)中 Topic A 和 Topic B 分別對應於 TS 專題和 Deno 專題,而 Subscriber(訂閱者)就是小秦、小王和小池。

下面我們來看一下如何使用 TypeScript 來實現發布訂閱模式。

5.2.1 實現代碼
type EventHandler = (...args: any[]) => any;

class EventEmitter {
  private c = new Map<string, EventHandler[]>();

  // 訂閱指定的主題
  subscribe(topic: string, ...handlers: EventHandler[]) {
    let topics = this.c.get(topic);
    if (!topics) {
      this.c.set(topic, topics = []);
    }
    topics.push(...handlers);
  }

  // 取消訂閱指定的主題
  unsubscribe(topic: string, handler?: EventHandler): boolean {
    if (!handler) {
      return this.c.delete(topic);
    }

    const topics = this.c.get(topic);
    if (!topics) {
      return false;
    }
    
    const index = topics.indexOf(handler);

    if (index < 0) {
      return false;
    }
    topics.splice(index, 1);
    if (topics.length === 0) {
      this.c.delete(topic);
    }
    return true;
  }

  // 為指定的主題發布消息
  publish(topic: string, ...args: any[]): any[] | null {
    const topics = this.c.get(topic);
    if (!topics) {
      return null;
    }
    return topics.map(handler => {
      try {
        return handler(...args);
      } catch (e) {
        console.error(e);
        return null;
      }
    });
  }
}

5.2.2 使用示例
const eventEmitter = new EventEmitter();
eventEmitter.subscribe("ts", (msg) => console.log(`收到訂閱的消息:${msg}`) );

eventEmitter.publish("ts", "TypeScript發布訂閱模式");
eventEmitter.unsubscribe("ts");
eventEmitter.publish("ts", "TypeScript發布訂閱模式");

5.2.3 應用場景對象間存在一對多關係,一個對象的狀態發生改變會影響其他對象。BetterScroll - EventEmitter:https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/events.ts六、策略模式

策略模式(Strategy Pattern)定義了一系列的算法,把它們一個個封裝起來,並且使它們可以互相替換。策略模式的重心不是如何實現算法,而是如何組織、調用這些算法,從而讓程序結構更靈活、可維護、可擴展。

目前在一些主流的 Web 站點中,都提供了多種不同的登錄方式。比如帳號密碼登錄、手機驗證碼登錄和第三方登錄。為了方便維護不同的登錄方式,我們可以把不同的登錄方式封裝成不同的登錄策略。

下面我們來看一下如何使用策略模式來封裝不同的登錄方式。

6.1 實現代碼

為了更好地理解以下代碼,我們先來看一下對應的 UML 類圖:

interface Strategy {
  authenticate(...args: any): any;
}

class Authenticator {
  strategy: any;
  constructor() {
    this.strategy = null;
  }

  setStrategy(strategy: any) {
    this.strategy = strategy;
  }

  authenticate(...args: any) {
    if (!this.strategy) {
      console.log('尚未設置認證策略');
      return;
    }
    return this.strategy.authenticate(...args);
  }
}

class WechatStrategy implements Strategy {
  authenticate(wechatToken: string) {
    if (wechatToken !== '123') {
      console.log('無效的微信用戶');
      return;
    }
    console.log('微信認證成功');
  }
}

class LocalStrategy implements Strategy {
  authenticate(username: string, password: string) {
    if (username !== 'abao' && password !== '123') {
      console.log('帳號或密碼錯誤');
      return;
    }
    console.log('帳號和密碼認證成功');
  }
}

6.2 使用示例
const auth = new Authenticator();

auth.setStrategy(new WechatStrategy());
auth.authenticate('123456');

auth.setStrategy(new LocalStrategy());
auth.authenticate('abao', '123');

6.3 應用場景及案例一個系統需要動態地在幾種算法中選擇一種時,可將每個算法封裝到策略類中。多個類只區別在表現行為不同,可以使用策略模式,在運行時動態選擇具體要執行的行為。一個類定義了多種行為,並且這些行為在這個類的操作中以多個條件語句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。Github - passport-local:https://github.com/jaredhanson/passport-localGithub - passport-oauth2:https://github.com/jaredhanson/passport-oauth2Github - zod:https://github.com/vriad/zod/blob/master/src/types/string.ts七、職責鏈模式

職責鏈模式是使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關係。在職責鏈模式裡,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。

在公司中不同的崗位擁有不同的職責與權限。以上述的請假流程為例,當阿寶哥請 1 天假時,只要組長審批就可以了,不需要流轉到主管和總監。如果職責鏈上的某個環節無法處理當前的請求,若含有下個環節,則會把請求轉交給下個環節來處理。

在日常的軟體開發過程中,對於職責鏈來說,一種常見的應用場景是中間件,下面我們來看一下如何利用職責鏈來處理請求。

7.1 實現代碼

為了更好地理解以下代碼,我們先來看一下對應的 UML 類圖:

interface IHandler {
  addMiddleware(h: IHandler): IHandler;
  get(url: string, callback: (data: any) => void): void;
}

abstract class AbstractHandler implements IHandler {
  next!: IHandler;
  addMiddleware(h: IHandler) {
    this.next = h;
    return this.next;
  }

  get(url: string, callback: (data: any) => void) {
    if (this.next) {
      return this.next.get(url, callback);
    }
  }
}

// 定義Auth中間件
class Auth extends AbstractHandler {
  isAuthenticated: boolean;
  constructor(username: string, password: string) {
    super();

    this.isAuthenticated = false;
    if (username === 'abao' && password === '123') {
      this.isAuthenticated = true;
    }
  }

  get(url: string, callback: (data: any) => void) {
    if (this.isAuthenticated) {
      return super.get(url, callback);
    } else {
      throw new Error('Not Authorized');
    }
  }
}

// 定義Logger中間件
class Logger extends AbstractHandler {
  get(url: string, callback: (data: any) => void) {
    console.log('/GET Request to: ', url);
    return super.get(url, callback);
  }
}

class Route extends AbstractHandler {
  URLMaps: {[key: string]: any};
  constructor() {
    super();
    this.URLMaps = {
      '/api/todos': [{ title: 'learn ts' }, { title: 'learn react' }],
      '/api/random': Math.random(),
    };
  }

  get(url: string, callback: (data: any) => void) {
    super.get(url, callback);

    if (this.URLMaps.hasOwnProperty(url)) {
      callback(this.URLMaps[url]);
    }
  }
}

7.2 使用示例
const route = new Route();
route.addMiddleware(new Auth('abao', '123')).addMiddleware(new Logger());

route.get('/api/todos', data => {
  console.log(JSON.stringify({ data }, null, 2));
});

route.get('/api/random', data => {
  console.log(data);
});

7.3 應用場景想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。有多個對象可以處理一個請求,哪個對象處理該請求運行時自動確定,客戶端只需要把請求提交到鏈上即可。八、模板方法模式

模板方法模式由兩部分結構組成:抽象父類和具體的實現子類。通常在抽象父類中封裝了子類的算法框架,也包括實現一些公共方法以及封裝子類中所有方法的執行順序。子類通過繼承這個抽象類,也繼承了整個算法結構,並且可以選擇重寫父類的方法。

在上圖中,阿寶哥通過使用不同的解析器來分別解析 CSV 和 Markup 文件。雖然解析的是不同的類型的文件,但文件的處理流程是一樣的。這裡主要包含讀取文件、解析文件和列印數據三個步驟。針對這個場景,我們就可以引入模板方法來封裝以上三個步驟的處理順序。

下面我們來看一下如何使用模板方法來實現上述的解析流程。

8.1 實現代碼

為了更好地理解以下代碼,我們先來看一下對應的 UML 類圖:

import fs from 'fs';

abstract class DataParser {
  data: string = '';
  out: any = null;

  // 這就是所謂的模板方法
  parse(pathUrl: string) {
    this.readFile(pathUrl);
    this.doParsing();
    this.printData();
  }

  readFile(pathUrl: string) {
    this.data = fs.readFileSync(pathUrl, 'utf8');
  }

  abstract doParsing(): void;
  
  printData() {
    console.log(this.out);
  }
}

class CSVParser extends DataParser {
  doParsing() {
    this.out = this.data.split(',');
  }
}

class MarkupParser extends DataParser {
  doParsing() {
    this.out = this.data.match(/<\w+>.*<\/\w+>/gim);
  }
}

8.2 使用示例
const csvPath = './data.csv';
const mdPath = './design-pattern.md';

new CSVParser().parse(csvPath);
new MarkupParser().parse(mdPath);

8.3 應用場景算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現。當需要控制子類的擴展時,模板方法只在特定點調用鉤子操作,這樣就只允許在這些點進行擴展。九、參考資源

相關焦點

  • 黑暗模式大勢所趨,盤點常見APP黑暗模式設計
    前段時候,一則「蘋果稱:微信若堅持不開發夜間模式,或面臨下架」的消息傳播網際網路,由於展開對黑暗模式的討論與探究不絕於耳,本文也以此為話題聊聊常見的APP黑暗模式設計。
  • 九種電池儲能的優缺點
    到目前為止,中國沒有達到類似美國、日本將儲能當作一個獨立產業加以看待並出臺專門扶持政策的程度,尤其在缺乏為儲能付費機制的前提下,儲能產業的商業化模式尚未成形。電池儲能大功率場合一般採用鉛酸蓄電池,主要用於應急電源、電瓶車、電廠富餘能量的儲存。小功率場合也可以採用可反覆充電的乾電池:如鎳氫電池,鋰離子電池等。本文跟隨小編一起來了解一下九種電池儲能的優缺點。
  • UI設計模式大閱兵
    互動設計師在設計線框圖原型時,熟知常見的Web設計模式很有幫助,做到「心中有數」才能創造出符合需求,用戶易學易用的界面來。常見的設計模式有哪些呢?在商業中有哪些案例呢?某公司互動設計師張雅秋寫了一篇博文對此進行了總結,現轉載於此,全文如下:互動設計師在設計線框圖原型時,熟知常見的Web設計模式很有幫助,做到「心中有數」才能創造出符合需求,用戶易學易用的界面來。所謂「沒有必要重複發明輪子」,模式往往容易解決常見問題,正確的模式能幫用戶熟悉界面、提高效率。常見的UI設計模式如下圖:
  • 這九種 「常見」動物竟瀕臨滅絕
    在國際自然保護聯盟(IUCN)的紅色名錄中,上面的名單裡有九種動物被列為脆弱,瀕危或嚴重瀕危物種,除了狼。
  • 哈弗h6儀錶盤圖解圖解,哈弗h6儀錶盤指示燈圖解,哈弗h6故障燈
    哈弗h6儀錶盤圖解圖解A 發動機轉速表車速表C 水溫表D 燃油表哈弗h6儀錶盤圖解圖解1、發動機故障警告燈:點火開關處於 ON 模式時,此警告燈點亮;發動機啟動後,此警告燈熄滅。5、充電警告燈:點火開關處於 ON 模式時,此警告燈點亮;發動機啟動後,此警告燈熄滅。當發動機啟動後,此警告燈仍然點亮,表明充電系統出現故障。此時,請聯繫長城汽車服務商進行檢修。
  • 男人稱呼女人的九種叫法,看看你是哪一種?
    而一個男人如果能在激情流逝以後還能保持對你曖昧的稱呼,絕對是一個真愛你的男人,這樣的男人也是真的少見,其實一段感情溫度有多高,好不好,從男人對女人的稱呼就可以看得出來,一般男人稱呼自己的女人都有這九種叫法,看看你是哪一種?1、老婆。
  • 常見公差定義的圖解—尺寸誤差、表面形狀誤差和相對位置誤差
    〖新朋友〗點擊上面藍色字關注「機械產品檢驗」常見公差定義的圖解—尺寸誤差、表面形狀誤差和相對位置誤差誤差在檢驗工作中是常見的、不可避免的存在著,有些概念必須清楚,否則會造成產品檢驗結果的誤判。今天介紹「常見公差定義的圖解」—機械產品的幾何誤差分類:尺寸誤差(工件實際尺寸與圖紙規定尺寸的偏差)、表面形狀誤差和相對位置誤差;表面形狀誤差包括:微觀(表面粗糙度)、中間(波度)和宏觀(形狀誤差)三種;形狀誤差和相對位置誤差統稱為幾何誤差(過去人們常說的形位公差)。
  • 蝴蝶蘭微景觀常見模式
    蝴蝶蘭微景觀常見模式蔡紅海等1 蝴蝶蘭的概念蝴蝶蘭為蘭科蝴蝶蘭屬,原產於亞熱帶雨林地區,為附生性蘭花。蝴蝶蘭白色粗大的氣根露在葉片周圍,除了具有吸收空氣中養分的作用外,還具有光合作用。2 蝴蝶蘭微景觀常見模式2.1 自然式形式多採用盆景的造型,布局可採用自然式,可根據需要點綴奇石或其他配件來突出大自然的野趣。可選擇多種性狀的盆器,材質以陶質、瓷盆為佳,色彩和形狀應不過於花哨,表現出盆栽自然典雅的特點為宜。2.2 趣味式主要突出植物自身的特點,彰顯其獨特的形態和色彩。
  • 螺紋緊固件常見失效模式分析
    緊固件的失效是常見機械類產品質量問題產生的原因,因此螺紋緊固件失效模式的潛在原因分析,對產品的質量提升有重要的意義。 1 螺紋緊固件常見的失效模式 在我們工作中遇到的螺紋緊固件主要的失效模式看分為: ①裝配擰拉斷裂; ②螺紋受剪切力擰斷; ③應力集中部位使用後斷裂; ④疲勞斷裂; ⑤延時斷裂; ⑥零件扭矩報警; ⑦螺紋滑牙。
  • 汽車內部按鈕圖解 教您識別和使用汽車內部按鈕
    雖然現在很多網友對車輛已經頭頭是道,不過還是有很多人對於汽車內部按鈕、旋鈕或者標識並不一定了解,今天我們將通過汽車內部按鈕圖解來教您識別和使用汽車內部按鈕1、汽車內部按鈕圖解:對於多數車輛,按動方向盤帶喇叭符號這個位置就可以鳴笛了,不過為了文明駕駛,還是建議少按喇叭。
  • 也許這是冬季最進補的美食,用九種方法烹飪海膽
    東北第一家海膽料理專門店近日開門納客,據悉膽道是瀋陽第一家專門吃海膽的地方,中國也是很少見的,跟著小編一起看看他們如何用九種方法烹飪海膽。世界上已發現的海膽有850多種,只有十幾種可以食用,主要有蝦夷馬糞海膽、光棘球海膽、馬糞海膽、紫海膽、白棘三列海膽、海刺蝟等。我國常見的海膽主要有:光棘球海膽、紫海膽和馬糞海膽。
  • 如何設計商業模式?——《商業模式新生代》讀書筆記之七
    讀書筆記分享——《商業模式畫布》之一、幾種常見的商業模式之後,接下來幾期我們將了解如何進行商業模式的設計#設計的方法。為了確保大家有耐心讀完所有的內容,對於這幾種商業模式設計的方法我也將分為幾期來為大家進行詳細解讀。
  • 周巍:《圖解時間簡史》引人入勝
    在ZAKER哈爾濱「領讀冰城·禮讚中國」領讀人、黑龍江電視臺著名節目主持人周巍看來,只要讀過《圖解時間簡史》這本書,就能更好地理解黑洞理論。  1993年,讀大學二年級的周巍第一次看到《時間簡史》,就被書中的宇宙學理論所吸引。雖然書中晦澀難懂的物理學概念沒能完全理解,但對宇宙的探尋與好奇一直埋藏心底。
  • 九種激情性愛姿勢詳解(圖)
    嘗試以下九種小技巧,有助於使你們雙方共享這種人間歡樂,以鞏固和發展愛情。  第一種叫龍翻。  女子面向上躺臥,男子伏趴在對方身上,男股在女子兩腿中間。女子陰戶上迎陽具,陽具刺激陰蒂,攻擊陰戶上部。  第九種名叫鶴交頸。  男子跪姿,雙膝打開,女子跨騎在他身上,兩腳分置在男子左右兩側,雙手環抱男頸。陽具插入陰道的同時,磨擦女子陰唇,刺激陰核。男子雙手捧抱女臀協助她搖晃,上下刺插。女子幾經交合後,津液淋漓,達到高潮後便停止。如此,可使各種心理鬱結病症不治自愈。
  • 工程測量員應該掌握的知識,最全測量員工作全能圖解,通俗易懂!
    今天給大家整理了一份測量員日常工程全能圖解,主要闡述了建築工程測量與施工放線的理論知識以及使用方法。共330頁,分為16個章節。主要內容包括了建築工程識圖、建築工程測量概論、測量誤差基本知識,水準測量、地形圖的測量與運用等。圖解+實操,更易理解和掌握,適合測量人員使用。【領取方式在文末!!】
  • 我的世界中的九種海洋種類,老玩家只知道五種!你知道幾種?
    但是大家知道海洋的種類是有九種嗎?今天小編找到了一位大神總結的關於九種海洋群系的總結,大家一起來看看吧!普通海洋群系:裡面大部分有海草、海帶、在底部生長,各種魚類在水中。海床為沙子、泥土、砂礫較為常見。第一種:冷水海洋群系,海床由砂礫及少量泥土組成,鮭魚、鱈魚、魷魚、海豚、溺屍以及沉船。
  • 適合大功率的CCM模式APFC電路設計
    本文引用地址:http://www.eepw.com.cn/article/227454.htm目前在功率因數校正電路中,最常用的就是由BOOST變換器構成的主電路,而按照輸入電流的連續與否,又分為DCM、CRM、CCM模式。DCM模式,因為控制簡單,但輸入電流不連續,峰值較高,所以常用在小功率場合。
  • ​密度計算的九種類型
    杜鴻蛟老師初中物理網課:密度計算的九種類型 密度類計算題的解答,具有一定的探究性和靈活性
  • MVC設計模式(模型-視圖-控制器)設計模式介紹
    控制器與視圖的分離,支持了視圖與不同控制器結合的靈活性,以實現不同的操作模式,例如對普通用戶、專業用戶、或不使用控制器建立的只讀視圖。這種分離還為在應用中集成新的I/O設備提供了途徑。   4 MVC的變化   把模型、視圖、控制器實行分離,使設計和使用有了很大靈活性。但是,在現實中,視圖和控制器的功能通常是緊密地聯繫在一起的。
  • MVC設計模式的總結
    設計模式起源: 1994 年,由 Erich Gamma,Richard Helm,Ralph Johnson 和 John Vlissides(即所謂的「四人幫』,GoF:Gang of Four)合作的以《設計模式:可復用的面向對象軟體的基本原理》為題目的書藉出版了。