owned this note
owned this note
Published
Linked with GitHub
---
title: 'Stragety 策略 模式'
disqus: kyleAlien
---
Stragety 策略 模式
===
## Overview of Content
如有引用參考請詳註出處,感謝 :smile:
> 定義一系列的演算法,並將每個一個**演算法封裝**,並 ==可**互相取代**==
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/)
:::
[TOC]
## Stragety 使用場景
1. 對同一類型的問題有多種處理方式,多個 `if/else`,`switch/case` 判斷時
2. 需要**安全封裝多種`同一類型`的操作**
### Stragety 定義 & UML
* **Stragety 定義**:
定義一組算法,並將其封裝起來,之後可以快速互換
* **Stragety UML**:
透過將共同方法定義在抽象,透過注入細節,實現相同方法 but 不同結果
| 角色 | 功能 |
| -------- | -------- |
| `Context` | 上下文角色,封裝 `IStragety`,屏蔽高層模塊對策略的直接訪問 |
| `IStragety` | 共同方法的抽象接口 |
| `ConstractStragetyA、B` | 實作類演算法類(細節實現) |
> 
### Stragety 設計 - 優缺點
* **Stragety 設計優點**
* 結構清晰,簡單直觀,擴充方便
* 透過注入相對而言耦合度較低
* 不同演算法出現問題時,可以**分開維護**,也符合開閉原則
* **Stragety 設計缺點**
* 隨著演算法的增加,實現子類會變多
* 另一個代價是,外部需要知道使用的策略!不符合迪米特原則(最少知識原則)
## Stragety 實現
:::info
假設去韓國旅行有三種方式,自助旅行、半自助旅行、旅行社,每種選擇方式所花費的金額也不一樣
:::
最簡單直覺的寫法是使用 `if / else`、`switch / case` 去分類,但 **這種寫法不符合開閉原則**,每次新增一個旅遊金額算法,都需要修改 TravelCal 方法
```java=
public class TravelCal {
public int getTravelCal(String type) {
int result;
if("Agency".equals(type)) {
result = 300;
} else if("SelfHelp".equals(type)) {
result = 50;
} else if("Backpacking".equals(type)) {
result = 100;
} else {
result = -1;
}
return result;
}
}
```
### Stragety 實現標準
1. **`IStragety` 介面**:宣告共同的抽象方法,以目前案例就是定義每一種旅行方式所需的花費
```java=
// IStragety.java
public interface TravelCast {
// 共同抽象
int calCast();
}
```
2. **`ConstractStragety` 類**:定義細節,讓每一個方案去實作計算花費金額
```java=
// 旅行社
public class Agency implements TravelCast {
@Override
public int calCast() {
return 300;
}
}
// 自助
public class SelfHelp implements TravelCast {
@Override
public int calCast() {
return 100;
}
}
// 背包客
public class Backpacking implements TravelCast {
@Override
public int calCast() {
return 50;
}
}
```
* User 使用:透過替換實作細節,來達到不同的金額
:::success
* 開閉原則:
如果有新的旅行方案,同樣透過繼承 `TravelCast` 抽象來拓展,並不會觸碰到其他類
:::
```java=
public class TestStrategy {
public static void main(String[] args) {
TravelCast travelCast;
travelCast = new Agency();
printCast(travelCast);
travelCast = new SelfHelp();
printCast(travelCast);
travelCast = new Backpacking();
printCast(travelCast);
}
public static void printCast(TravelCast travelCast) {
System.out.println(travelCast.getClass().getSimpleName() + " Cast: " + travelCast.calCast());
}
}
```
**--實作--**
> 
### 管理 Stragety 實作 - Context
* 在最原始的 Stragety 實作,User 類會了解到細節 (TravelCast 的實作),這就不符合 `最少知識`、`依賴導致` 設計原則
* 這邊我們透過一個 `Manager 管理類`,透過外部注入 Option 選項,來決定最終要產生的花費
> 上面重複,沒有修改的類 (IStragety、ConstractStragety),這邊不會列出
> 
1. **Manager**:新增一個管理類,聚合管理所有的 IStragety,並給予預設值
```java=
// Manager 類
public class TravelManager {
enum Option {
BACK_PACKING,
SELF_HELP,
AGENCY
}
// 聚合所有 TravelCast 抽象
private final Map<Option, TravelCast> map = new HashMap<>() {
{
put(Option.BACK_PACKING, new Backpacking());
put(Option.SELF_HELP, new SelfHelp());
put(Option.AGENCY, new Agency());
}
};
private Option option;
public TravelManager() {
// 設定 default
option = Option.AGENCY;
}
// 透過注入,來達到切換細節
public TravelManager setOption(Option option) {
this.option = option;
return this;
}
public void printCast() {
TravelCast travelCast = map.get(option);
if(travelCast == null) {
throw new IllegalAccessError("setTravelCast first");
}
System.out.println(travelCast.getClass().getSimpleName() + " Cast: " + travelCast.calCast());
}
}
```
:::success
* 這裡有另外一種方式叫做 **策略枚舉**,透過枚舉的封裝,可以加強可讀性
:::
2. **User 使用**:**透過 Manager 來 ++管理細節++**,而 User 只需要知道 Manager 即可
```java=
// User 使用
public class TestStrategy {
public static void main(String[] args) {
TravelManager travelManager = new TravelManager();
travelManager.setOption(TravelManager.Option.AGENCY).printCast();
travelManager.setOption(TravelManager.Option.SELF_HELP).printCast();
travelManager.setOption(TravelManager.Option.BACK_PACKING).printCast();
}
}
```
:::warning
* **Manager(Context) 不符合開閉原則**:
雖然這裡透過 Manager 來管理 IStragety 所有細節,但如果有新增類,就需要手動修改 Manager 類
:::
:::success
* 這裡還可以使用工廠、代理、享元... 等等模式來實做 Manager(依需求決定)
:::
## Android source 動畫
動畫透過人類的 **視覺暫留** 來達成,透過快速切換來達到動畫的效果
> 視覺暫留: 對於上個映像會暫時留在眼中
| 來源 | 每秒楨數 |
| -------- | -------- |
| 標準電影 | 24 楨/秒 |
| Android 動畫 | 60 楨/秒 |
### 動畫 - [TimeInterpolator](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/animation/TimeInterpolator.java) 插值器
* 透過 `TimeInterpolator` 插值器,可以讓我們接收動畫的運作時間,透過時間的百分比來調整動畫的速度
* 下圖代表給予相同的時間,但不同插值器會返回不同結果,從這裡其實就可以看出 **Stragety 設計,不同的插值器代表了不同的算法**
> 
* 常見 `Interpolator` 插值器如下
| 插值器 | 對動畫的功能 |
| -------- | -------- |
| [**LinearInterpolator**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/LinearInterpolator.java) | 線性加速 |
| [**AccelerateDecelerateInterpolator**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java) | 加速 & 減速 |
| [**DecelerateInterpolator**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/DecelerateInterpolator.java) | 減速 |
> 
### 動畫 - [TypeEvaluator](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/animation/TypeEvaluator.java) 類型估值器
* TypeEvaluator 的功能是,得到 TimeInterpolator 插值器的數值後,將得到的時間百分比、屬性起始值、目標值來運算最終結果,**將最終運算結果交給 View**
```java=
// TypeEvaluator.java
public interface TypeEvaluator<T> { // 泛型
public T evaluate(float fraction, T startValue, T endValue);
}
```
* 常見的 `Evaluator` 類型估值器如下
| 插值器 | 對動畫的功能 |
| -------- | -------- |
| FloatEvaluator | 浮點數運算 |
| IntEvaluator | 整數運算 |
| ArgbEvaluator | 顏色運算 |
```java=
// IntEvaluator.java
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* fraction 是時間插值器運算出的值 (0 ~ 1 之間)
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
```
> 
### Animation 基礎使用
1. 設定動畫 xml 檔案
```xml=
<!-- R.anim.click_btn -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300">
<scale android:fromXScale="0.5" android:fromYScale="0.5"
android:pivotX="50%" android:pivotY="50%"
android:toXScale="1.0" android:toYScale="1.0"/>
</set>
```
2. 使用 `AnimationUtils` 來讀取 xml 檔案
```java=
// 讀取 xml 資源檔
Animation animation = AnimationUtils.loadAnimation(this, R.anim.click_btn);
```
3. 將 Animation 設定給 View
```java=
Button btn = findViewById(R.id.btn);
// Button 加載動畫
btn.startAnimation(animation);
```
:::info
* startAnimation 是 View 的方法
:::
### [View](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java) - startAnimation 啟動動畫
```java=
// View.java
public void startAnimation(Animation animation) {
// 初始化動畫開始時間
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
// 對該 View 設定 Animation
setAnimation(animation);
// 刷新 Parent 緩存
invalidateParentCaches();
// 刷新 View 自身
invalidate(true);
}
// -------------------------------------------------------
// Animation.java
public static final int START_ON_FIRST_FRAME = -1;
```
* 刷新後 ViewGroup 會透過 `dispatchDraw` 方法,對 View 的部分區域重繪,ViewGroup 重繪時會呼叫 ViewGroup#**drawChild** 方法
```java=
// ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// @ 追蹤 draw 方法
return child.draw(canvas, this, drawingTime);
}
// -------------------------------------------------------------
// View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
... 省略部分
if (a != null) {
// @ 追蹤 applyLegacyAnimation 方法
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
...
} else {
... 省略部分
}
}
```
1. applyLegacyAnimation 方法:初始化、標記 View 動畫啟動
```java=
// View.java
@CallSuper
protected void onAnimationStart() {
// 標記該 View 已啟動 Animation
mPrivateFlags |= PFLAG_ANIMATION_STARTED;
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
// 檢查是否初始化
if (!initialized) {
// 尚未初始化則初始化
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
// 標記 View 啟動動畫
onAnimationStart();
}
// 儲存動畫訊息
final Transformation t = parent.getChildTransformation();
// @ 追蹤 getTransformation 方法
// 獲取動畫相關值
boolean more = a.getTransformation(drawingTime, t, 1f);
...
if (more) {
if (!a.willChangeBounds()) {
... 省略部分
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
// 獲取重繪區域
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
// child 繪製的動畫可能螢幕之外,需要確保該刷新不會被取消
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
// 計算有效區域
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
// ViewGroup 刷新指定區塊
parent.invalidate(left,
top,
left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
```
2. Animation#**getTransformation** 方法:**重點是計算動畫時間**,決定該動畫是否仍需要繪製,返回 true 代表要繪製動畫,如果
```java=
// Animation.java
Interpolator mInterpolator;
public boolean getTransformation(long currentTime,
Transformation outTransformation,
float scale) {
mScaleFactor = scale;
// @ 追蹤 getTransformation 方法
return getTransformation(currentTime, outTransformation);
}
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
// 設定動畫啟動時間
mStartTime = currentTime;
}
// 該 Animation 總時間
final long startOffset = getStartOffset();
final long duration = mDuration;
// 時間流逝的百分比
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// 動畫尚未啟動 (延遲)
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
// 動畫完成 or 取消,expired = true
// 1.0 代表動畫時間已經過
final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;
...
// 動畫時間正在運作中
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
... 省略部分
// 插值器
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
// @ 分析 applyTransformation 方法
applyTransformation(interpolatedTime, outTransformation);
}
...
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
```
* 從這裡可以看到 [**Interpolator**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/Interpolator.java) 的身影,同時也可以發現 **使用者如果設定 ++不同 Interpolator 也會也不同的效果++**
```java=
// Animation.java
public Animation() {
// 設定預設插值器
ensureInterpolator();
}
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}
protected void ensureInterpolator() {
if (mInterpolator == null) {
// 預設插值器為 AccelerateDecelerateInterpolator
mInterpolator = new AccelerateDecelerateInterpolator();
}
}
```
> 
* 預設插值器為 [**AccelerateDecelerateInterpolator**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java):這是加減速插值器
```java=
// AccelerateDecelerateInterpolator.java
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolator {
public AccelerateDecelerateInterpolator() { }
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) { }
public float getInterpolation(float input) {
// 加速插值器
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
// 減速插值器
return NativeInterpolatorFactory.createAccelerateDecelerateInterpolator();
}
}
```
### 里式原則應用 - applyTransformation
* [**Animation**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/Animation.java)#applyTransformation 是一個空實現,給每一個繼承於它的子類實現細節,這就是理式原則的典型應用,父類可以任意地被子類替換
```java=
// Animation.java
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
```
* 如過動畫 xml 有設定 alpha 值,則會對應到 [**AlphaAnimation 類**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/AlphaAnimation.java)、選轉則是對應到 [**RotateAnimation 類**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/animation/RotateAnimation.java)
```java=
// AlphaAnimation.java
public class AlphaAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
// 設定透明度
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
}
// -----------------------------------------------------
// RotateAnimation.java
public class RotateAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
}
```
## 更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
:::info
* [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/)
* [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/)
* [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/)
:::
### 創建模式 - Creation Patterns
* [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/)
* **創建模式 - `Creation Patterns`**:
創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建對象。這些模式可以隱藏創建對象的細節,並提供創建對象的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
:::success
* [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/)
* [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/)
* [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/)
* [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/)
* [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/)
* [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/)
* [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/)
:::
### 行為模式 - Behavioral Patterns
* [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/)
* **行為模式 - `Behavioral Patterns`**:
行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列對象如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
:::warning
* [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/)
* [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/)
* [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/)
* [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/)
* [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/)
* [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/)
* [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/)
* [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/)
* [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/)
* [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/)
* [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/)
:::
### 結構模式 - Structural Patterns
* [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/)
* **結構模式 - `Structural Patterns`**:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
:::danger
* [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/)
* [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/)
* [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/)
* [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/)
* [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/)
* [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/)
:::
## Appendix & FAQ
:::info
:::
###### tags: `Java 設計模式` `基礎進階`