如何管理和維護算法族?只需知道策略模式

同樣是排序算法,你可以選擇冒泡排序、選擇排序、插入排序、快速排序等等,也即是說,爲了實現排序這一個目的,有很多種算法可以選擇。這些不同的排序算法構成了一個算法族,你可以在需要的時候,根據需求或者條件限制(內存、複雜度等)適時選擇具體的算法。

在面向對象的設計裏,該如何設計這樣一個算法族呢?它包含了多種算法,在使用的時候又會根據條件來選擇具體的算法?這就會用到軟件設計模式中的——策略模式。

1.策略模式簡介

策略模式用於算法的自由切換和擴展,對應於解決某一問題的一個算法族,允許用戶從該算法族中任意選擇一個算法解決問題,同時還可以方便地更換算法或者增加新的算法。策略模式將算法族中的每一個算法都封裝成一個類,每一個類稱爲一個策略(Strategy)。

策略模式:

定義一系列算法,將每一個算法封裝起來,並讓它們可以相互替換。策略模式讓算法可以獨立於使用它的客戶而變化。

2.策略模式結構

爲了方便算法族中的不同算法在使用中具有一致性,在策略模式中會提供一個抽象層來聲明公共接口,在具體的策略類中實現各個算法。策略模式由上下文類和策略類組成,其UML結構如下圖:

  • Context(上下文類) :上下文類是使用算法的角色,可以在解決不同具體的問題時實例化不同的具體策略類對象;
  • Strategy(抽象策略類):聲明算法的方法,抽象層的設計使上下文類可以無差別的調用不同的具體策略的方法;
  • ConcreteStrategy(具體策略類):實現具體的算法。

3.策略模式代碼實例

某系統提供了一個用於對數組進行操作的類,該類封裝了對數組的常見操作,現以排序操作爲例,使用策略模式設計該數組操作類,使得客戶端可以動態更換排序算法,可以根據需要選擇冒泡排序或者選擇排序或者插入排序,也能夠靈活增加新的排序算法

 顯然,在該實例中,可以冒泡排序、選擇排序和插入排序分別封裝爲3個具體策略類,它們有共同的基類SortStrategy。還需要一個上下文類Context,Context中維護了一個SortStrategy的指針,在客戶端需要的時候,通過Context的setSortStrategy()方法來實例化具體的排序類對象。該實例的UML結構圖如下:

3.1.排序策略類

3.1.1.抽象排序策略類

// 抽象策略類
class Strategy
{
public:
	Strategy(){}
	virtual void sort(int arr[], int N) = 0;
};

3.1.2.具體策略類:冒泡排序類

// 具體策略:冒泡排序
class BubbleSort :public Strategy
{
public:
	BubbleSort(){
		printf("冒泡排序\n");
	}
	void sort(int arr[], int N){
		for (int i = 0; i<N; i++)
		{
			for (int j = 0; j<N - i - 1; j++)
			{
				if (arr[j]>arr[j + 1]){
					int tmp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = tmp;
				}
			}
		}
	}
};

3.1.3.具體策略類:選擇排序類

// 具體策略:選擇排序
class SelectionSort :public Strategy
{
public:
	SelectionSort(){
		printf("選擇排序\n");
	}
	void sort(int arr[], int N){
		int i, j, k;
		for (i = 0; i<N; i++)
		{
			k = i;
			for (j = i + 1; j<N; j++)
			{
				if (arr[j] < arr[k]){
					k = j;
				}
			}
			int temp = arr[i];
			arr[i] = arr[k];
			arr[k] = temp;
		}
	}
};

3.1.4.具體策略類:插入排序類

// 具體策略:插入排序
class InsertSort :public Strategy
{
public:
	InsertSort(){
		printf("插入排序\n");
	}
	void sort(int arr[], int N){
		int i, j;
		for (i = 1; i<N; i++)
		{
			for (j = i - 1; j >= 0; j--)
			{
				if (arr[i]>arr[j]){
					break;
				}
			}
			int temp = arr[i];
			for (int k = i - 1; k > j; k--){
				arr[k + 1] = arr[k];
			}
			arr[j + 1] = temp;
		}
	}
};

3.2.上下文類

#ifndef __CONTEXT_H__
#define __CONTEXT_H__

#include "Strategy.h"
#include <stdio.h>

// 上下文類
class Context
{
public:
	Context(){
		arr = NULL;
		N = 0;
	}
	Context(int iArr[], int iN){
		this->arr = iArr;
		this->N = iN;
	}
	void setSortStrategy(Strategy* iSortStrategy){
		this->sortStrategy = iSortStrategy;
	}
	void sort(){
		this->sortStrategy->sort(arr, N);
		printf("輸出: ");
		this->print();
	}
	void setInput(int iArr[], int iN){
		this->arr = iArr;
		this->N = iN;
	}
	void print(){
		for (int i = 0; i < N; i++){
			printf("%3d ", arr[i]);
		}
		printf("\n");
	}

private:
	Strategy* sortStrategy;
	int* arr;
	int N;
};

#endif // __CONTEXT_H__

3.3.客戶端代碼示例及結果

#include "Context.h"
#include <stdio.h>
#include <stdlib.h>

int main()
{
	Context* ctx = new Context();
	int arr[] = { 10, 23, -1, 0, 300, 87, 28, 77, -32, 2 };
	ctx->setInput(arr, sizeof(arr)/sizeof(int));
	printf("輸入:");
	ctx->print();

	// 冒泡排序
	ctx->setSortStrategy(new BubbleSort());
	ctx->sort();

	// 選擇排序
	ctx->setSortStrategy(new SelectionSort());
	ctx->sort();

	// 插入排序
	ctx->setSortStrategy(new InsertSort());
	ctx->sort();

	printf("\n\n");
	system("pause");
	return 0;
}

代碼運行結果如下:

從客戶端代碼可以看到,客戶端無需關心具體排序算法的細節,都是統一的調用上下文的sort()接口。另外,如果要增加新的排序算法,比如快速排序QuickSort,只需要從基類SortStrategy在派生一個類QuickSort,在QuickSort類中實現具體的sort()算法即可,擴展起來非常方便。

4.總結

優點:

  • 符合開閉原則,策略模式易於擴展,增加新的算法時只需繼承抽象策略類,新設計實現一個具體策略類即可;
  • 客戶端可以無差別地通過公共接口調用,利用裏式替換原則,靈活使用不同的算法策略;
  • 提供了一個算法族管理機制和維護機制。

缺點:

  • 客戶端必須要知道所有的策略,以便在使用時按需實例化具體策略;
  • 系統會產生很多單獨的類,增加系統中類的數量;
  • 客戶端在同一時間只能使用一種策略。

適用環境:

  • 系統需要在一個算法族中動態選擇一種算法,可以將這些算法封裝到多個具體算法類中,這些算法類都有共同的基類,即可以通過一個統一的接口調用任意一個算法,客戶端可以使用任意一個算法;
  • 不希望客戶端知道複雜的、與算法相關的數據結構,在具體策略類中封裝與算法相關的數據結構,可以提高算法的安全性。

歡迎關注知乎專欄:Jungle是一個用Qt的工業Robot

歡迎關注Jungle的微信公衆號:Jungle筆記

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章