前言:
目的:
如果有兩大類模組是多對多的組合,如本次Smaple Code. Nick和Addidas 包包都有紅、藍、黃….或其他顏色
就可能呈現下面6種組合
- Nick(紅)
- Nick(藍)
- Nick(黃)
- Addidas(紅)
- Addidas(藍)
- Addidas(黃)
如果此建立類別的話 可能情況如下面的UNL圖
這樣會有兩個問題
- 隨著品牌和顏色增多,包包類別數量急速增長 (X = m*n)個
- 顏色我們可看做一個抽象 不應當和包包合在一起
其中的第二點我覺得最重要
這時候就很適合帶入我們的主角BridgePattern
範例介紹
關係變成下圖UML

我們可以看到BagBsae去引用ColorBase 可以看到我們所需的子類別變成五個而已,重點是類別關係變得更有條理了,顏色和包包分開可調整性更大。
OOP有一個很重要的設計觀念
盡量用組合取代繼承,因為繼承耦合性遠大於組合!
因為子類別繼承父類別,子類別無條件都擁有protect已上的方法或成員資料.這就會造成一個耦合性(使用繼承須看情況),而A類別對於B類別進行組合就可達到繼承效果但不造成像繼承般的強耦合.
我們的背包一樣可擁有多種顏色,但耦合度跟類別關係變得更清晰了。
程式碼解說
建立 BagBase 類別並將 ColorBase 當建構傳入(因為Bag需要上顏色)
public abstract class BagBase
{
    protected ColorBase color{ get; set; }
    public BagBase(ColorBase color) {
        this.color = color;
    }
    public abstract void GetBag();
}
public abstract class ColorBase
{
    public abstract string Color();
}
這邊我只介紹一種顏色和包包來當作範例,因為其他概念都一樣
public class AdidasBag : BagBase
{
    public AdidasBag(ColorBase color) : base(color)
    {
    }
    public override void GetBag()
    {
        Console.WriteLine($"It is Addidas Bag,Color is {color.Color()}");
    }
}
class ColorBlue : ColorBase
{
    public override string Color()
    {
        return "Blue";
    }
}
建立
- AdidasBag類別重載- GetBag方法
- ColorBlue類別重載- Color方法
因為BagBase要傳入顏色GetBag就可幫包包上色.
使用如下外面看起來很合理乾淨.
class Program
{
    static void Main(string[] args)
    {
        AdidasBag nick = new AdidasBag(new ColorBlue());
        nick.GetBag();
        Console.ReadKey();
    }
}
實際案例
前陣子在做一個Unity2D遊戲,遇到一組遊戲邏輯
一個人物要移動有分兩種移動方式
- 自動移動
- 玩家手動點擊移動
因為是2D遊戲 有 上下左右 四個方位移動,四個方位配上兩個移動方式,人物會有不一樣的移動邏輯.
這邊我貼上部分程式碼
建立一個 RoadActionBase裡面有三個必要屬性需要給 上下左右 實現
- ArrowType2D人物移動箭頭方向
- OffSetPos移動距離
- PlayerDirction這是一個Unity2D座標屬性- public abstract class RoadActionBase 
 {- protected int _level; public RoadActionBase() { _level = SenceParamter.RoadCount; } public abstract ArrowType ArrowType { get; } public abstract int OffSetPos { get; } public abstract Vector2 PlayerDirction { get; }- } 
UpRoadAction類別對於往上時的狀態做給值
public class UpRoadAction : RoadActionBase
{
    public override ArrowType ArrowType
    {
        get
        {
            return ArrowType.Up;
        }
    }
    public override int OffSetPos
    {
        get
        {
            return -_level;
        }
    }
    public override Vector2 PlayerDirction
    {
        get
        {
            return Vector2.up;
        }
    }
}
DownRoadAction類別對於往下時的狀態做給值
public class DownRoadAction : RoadActionBase
{
    public override ArrowType ArrowType
    {
        get
        {
            return ArrowType.Down;
        }
    }
    public override int OffSetPos
    {
        get
        {
            return _level;
        }
    }
    public override Vector2 PlayerDirction
    {
        get
        {
            return Vector2.down;
        }
    }
}
RightRoadAction類別對於往右時的狀態做給值
public class RightRoadAction : RoadActionBase
{
    public override ArrowType ArrowType
    {
        get
        {
            return ArrowType.Right;
        }
    }
    public override int OffSetPos
    {
        get
        {
            return 1;
        }
    }
    public override Vector2 PlayerDirction
    {
        get
        {
            return Vector2.right;
        }
    }
}
LeftRoadAction類別對於往左時的狀態做給值
public class LeftRoadAction : RoadActionBase
{
    public override ArrowType ArrowType
    {
        get
        {
            return ArrowType.Left;
        }
    }
    public override int OffSetPos
    {
        get
        {
            return -1;
        }
    }
    public override Vector2 PlayerDirction
    {
        get
        {
            return Vector2.left;
        }
    }
}
建立一個 MoveBase 並將 RoadActionBase當作建構子傳入(內部邏輯有寫注解).
/// <summary>
/// 橋接模式
/// </summary>
public abstract class MoveBase
{
    protected PlayerController _player;
    protected int _level;
    protected float _Scape;
    public RoadActionBase RoadAction { get; protected set; }
    public MoveBase(RoadActionBase roadAction)
    {
        _player = PlayerController.Instance;
        _level = SenceParamter.RoadCount;
        _Scape = SenceParamter.Scape + SenceParamter.RoadHeigh;
        RoadAction = roadAction;
    }
    public virtual void Move(RoadContext currentRoad, RoadContext nextRoad)
    {
        //取得下一個位置
        Vector2 nextPos = nextRoad.transform.localPosition;
        if (IsWalkNext(currentRoad, nextRoad, _player.targetPos, nextPos))
        {
            //將下一個資料塞給當前玩家
            _player.targetPos = nextPos;
            _player.RoadContext = nextRoad;
            _player.moveDirction = RoadAction.PlayerDirction;
            currentRoad.SetIsWalk(true);
            //加入等待轉換的地方
            ReloadRoadController.Instance.AddRoadContext(currentRoad);
        }
    }
    protected abstract bool IsWalkNext(RoadContext currentRoad, RoadContext nextRoad, Vector3 targetPos, Vector3 nextPos);
}
TouchMove 類別重載 IsWalkNext實現自己的邏輯
public class TouchMove : MoveBase
{
    public TouchMove(RoadActionBase roadAction) : base(roadAction)
    {
    }
    /// <summary>
    /// 判斷是否可以 前往下一個目標
    /// </summary>
    /// <param name="currentRoad"></param>
    /// <param name="nextRoad"></param>
    /// <param name="targetPos"></param>
    /// <param name="nextPos"></param>
    /// <returns></returns>
    protected override bool IsWalkNext(RoadContext currentRoad, RoadContext nextRoad, Vector3 targetPos, Vector3 nextPos)
    {
        ArrowType arrowType = RoadAction.ArrowType;
        //1.下一個道路要可以進去
        //2.當前道路要可以出來
        //3.必須為四周的道路
        return
            arrowType.CanWalk(currentRoad.CanWalkOut) &&
            arrowType.CanWalk(nextRoad.CanWalkIn) &&
            CanMoveNextPos(targetPos, nextPos);
    }
    private bool CanMoveNextPos(Vector3 targetPos, Vector3 nextPos)
    {
        return ((int)Vector2.Distance(targetPos, nextPos)) % 
            ((int)_Scape) == 0;
    }
}
AutoMove 類別重載 IsWalkNext實現自己的邏輯
public class AutoMove : MoveBase
{
    public AutoMove(RoadActionBase roadAction) : base(roadAction)
    {
    }
    /// <summary>
    /// 判斷是否可以 前往下一個目標
    /// </summary>
    /// <param name="currentRoad"></param>
    /// <param name="nextRoad"></param>
    /// <param name="targetPos"></param>
    /// <param name="nextPos"></param>
    /// <returns></returns>
    protected override bool IsWalkNext(RoadContext currentRoad, RoadContext nextRoad, Vector3 targetPos, Vector3 nextPos)
    {
        //1.下一個道路要可以進去
        //2.當前道路要可以出來
        //3.必須為四周的道路
        //4.步數必須大於0
        return
                currentRoad.CurrentArrow.CanWalk(nextRoad.CanWalkIn) &&
                currentRoad.CurrentArrow.CanWalk(currentRoad.CanWalkOut) &&
                CanMoveNextPos(targetPos, nextPos) &&
                !nextRoad.IsChangeState && 
                GameModel.Step >0;
    }
    private bool CanMoveNextPos(Vector3 targetPos, Vector3 nextPos)
    {
        return ((int)Vector2.Distance(targetPos, nextPos)) % 
            ((int)_Scape) == 0;
    }
}
上面程式碼最主要是跟大家分享移動方式和方位的關係,上下左右值和方位式固定,將此配上不同的移動方式有不一樣的邏輯.
__此文作者__:Daniel Shih(石頭)
__此文地址__: https://isdaniel.github.io/bridge-pattern/ 
__版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!
