设计模式(三)-结构型模式(2)-桥接模式

2023-12-14 13:42:38

一、为何需要桥接模式(Bridge)?

在软件设计中,存在有两个或多个不同维度的模块时,我们需要将这些模块使用到在一起,来实现一个完整的功能。所谓不同维度的意思就是这些模块所负责的职责是不同的,并且它们之间没有多大的关联性。

根据设计原则中的单一职责原则:各自模块应当保持各自的分工任务。比如在画图系统中存在形状和画笔的两个维度。形状负责的是它的模样(矩形、圆形、三角形等),而画笔负责的是铅笔、毛笔、圆珠笔之类的选型。

倘若在某个维度的类里对另一维度的类有改写的痕迹,就会对这个功能的扩展性以及维护性受到影响。

比如设计一个抽奖系统。员工、员工家属都参与抽奖,奖金分为一二三等奖,最后公布抽奖名单。其中中奖者和奖金是两个不同维度的模块。
以下例子就是个错误的设计,暴露了对代码的扩展和维护困难的弊端。

    // 方式一:将中奖者类和奖金类作为继承关系,互相受到影响
    public abstract class Bounds
    {
        public abstract string getOneGift();//一等奖礼品
        public abstract string getTwoGift();//二等奖礼品
        //扩展定义三等奖、安慰奖礼品等...
    }
    
    public class LuckyStaff:Bounds
    {
        public override string getOneGift()
        {
            return "iPhone 15 Pro";
        }

        public override string getTwoGift()
        {
            return "HUAWEI MatePad Pro 13.2";
        }
        //扩展实现 n 个 getXXXGift...
    }
//扩展其他中奖者:public class LuckyFamilyMember:Bounds...
//方式一:违反了单一职责原则:
//LuckyStaff 类重写或实现了 Bounds 类的方法,不应该直接处理 Bounds 的事情。

    //方式二:将中奖者和奖金具体类直接作为对象组合关系,互相受到影响
    public class LuckyStaff
    {
        private Bounds onebounds;
        private Bounds twobounds;
        //...
        public string getOneGift()
        {
            return onebounds.getOneGift();
        }

        public string getTwoGift()
        {
            return twobounds.getTwoGift();
        }
        //...
    }
//扩展其他中奖者:public class LuckyFamilyMember...
//方式二违反了依赖倒置原则和迪米特法则:
//违反依赖倒置原则:当修改 Bounds 类的方法(如移除方法),就会影响 LuckyStaff
//违反迪米特法则:Bounds 和 LuckyStaff 没有多大关联性,却在代码中 LuckyStaff 过于依赖 Bounds

从以上代码可看出,如果以后再扩展不同身份的中奖者和不同的奖金类别,就得需要在原来的代码结构中进行添加和修改,或者再创建一个类重新实现一遍。如果扩展的东西越多,后果将不堪设想。于是为了解决这个问题,这时候就需要用到桥接模式了。

所谓桥接模式就是,把不同维度的模块之间进行分离,保持各自的独立性,然后再把这些模块进行桥接。这样子即使更改了哪个模块,也不会受到影响。

不同维度模块的主要分离为:抽象化和实现化进行分离:
抽象化:抽象化和修正抽象化。(抽象化派生给修正抽象化)
实现化:实现化和具体实现化。(实现化派生给具体实现化)
关系:实现化服务于抽象化。
以下将会解释这个概念。

特点:
将抽象化与实现化进行脱耦(也就是分离),使得他们两个互相独立,不会影响到彼此。

结构:

抽象化接口(Abstraction):定义高层模块的接口。(比如有设置形状大小等的抽象方法。作为形状的基类接口)

修正抽象化类(Refined Abstraction):扩展抽象化,改变或实现抽象化的接口。(比如扩展为矩形类、三角形类,并实现大小的接口;作为 Abstraction 的派生类)

实现化接口(Implementor):定义底层模块的接口。(比如画笔粗细等的抽象方法;作为画笔的基类接口)

具体实现化类(Concrete Implementor):扩展实现化,对实现化接口进行实现。(比如扩展为铅笔类、毛笔类,并实现粗细的接口;作为 Implementor 的派生类)

适合应用场景特点:

  • 可扩展性:不同维度的模块都潜在可扩展性,需要进行抽象化和实现化的分离。(比如抽象化为形状接口、实现化为画笔接口)
  • 服务与运用关系:抽象化属于高层模块,运用底层模块。实现化属于底层模块,服务高层模块。(比如画笔是为形状服务的)
  • 桥接作用:主要针对于不同维度的模块。修正抽象化通常包装一个实现化接口的派生实例。(派生指的是具体实现化)注重对象结构模型。(把形状和画笔进行桥接)

请添加图片描述

二、例子

需求:

年底快到了,公司将要组织一场年会活动,以感谢大家这一年来为公司做出了贡献。老板做出决定,需要在这场活动中有抽奖环节。于是领导把开发抽奖系统的任务交给你去做。具体需求是:奖金设置一等奖有1名、二等奖有两名、三等奖有三名。第一轮被抽到的幸运者获得一等奖,第二轮是二等奖,第三轮是三等奖。后续可能还有安慰奖啥的,领导和老板还在策划中。抽奖结束后,需要公布中奖结果。目前参加成员有员工和员工家属。至于老板是否也参加抽奖环节还未定,暂时先补个空,到时候再做决定。

需求分析:

  • 中奖者(员工、家属、老板(未定))和奖金(一等奖、二等奖、三等奖、安慰奖啥的(未定))是属于不同维度的模块。
  • 奖金服务于中奖者,作为底层模块;而中奖者作为高层模块,运用底层模块。但两者的定义互不影响。即修改中奖者不影响奖金的代码逻辑,反之亦然。
  • 本次抽奖活动在于老板是否参与还未定,以及奖金的类别后续在规划中,都属于可扩展的功能需求。

1、定义实现化接口和具体实现化类:

底层模块:奖金


    //Implementor:实现化接口(奖金)
    interface IBonus
    {
        string getGift();
    }

    //ConcreteImplementor:具体实现化类(一等奖)
    class OneBonus : IBonus
    {
        public string getGift()
        {
            return "iPhone 15 Pro";
        }
    }

    //ConcreteImplementor:具体实现化类(二等奖)
    class TwoBonus : IBonus
    {
        public string getGift()
        {
            return "HUAWEI MatePad Pro 13.2";
        }
    }

    //ConcreteImplementor:具体实现化类(三等奖)
    class ThreeBonus : IBonus
    {
        public string getGift()
        {
            return "Xiaomi 14 Pro";
        }
    }
    //可能还存在安慰奖、参与奖啥的...

2、定义抽象化接口和修正抽象化类:

高层模块:中奖者


    //Abstraction:抽象化接口(中奖者)
    interface ILucker
    {
        string Name { get; }
        void showResult();
    }

     //Refined Abstraction:修正抽象化类(员工中奖者)
    class LuckyStaff : ILucker
    {
        private IBonus iBonus;
        private string _name;
        public string Name { get { return _name; } }
        
        public LuckyStaff(string name,IBonus iBonus)
        {
            this.iBonus = iBonus;
            _name = name;
        }

        public void showResult()
        {
            Console.WriteLine("恭喜 {0}\t 获得一部 {1} 手机 !", Name, iBonus.getGift());
        }
    }

    //Refined Abstraction:修正抽象化类(可扩展员工家属中奖者)
    class LuckyFamilyMember :ILucker
    {
        private IBonus iBonus;
        private string _name;
        public string Name { get { return _name; } }

        public LuckyFamilyMember(string name, IBonus iBonus)
        {
            this.iBonus = iBonus;
            _name = name;
        }

        public void showResult()
        {
            
            Console.WriteLine("恭喜 {0}家属\t 获得一部 {1} 手机 !", Name, iBonus.getGift());
        }
    }

3、主程序(抽奖名单,公布结果):


    class Program
    {
        static void Main(string[] args)
        {
            OneBonus oneBounds = new OneBonus();
            TwoBonus twoBonus = new TwoBonus();
            ThreeBonus threeBnus = new ThreeBonus();

            //抽奖名单...
            List<ILucker> lucker = new List<ILucker>(){
                //第一轮抽奖
                new LuckyFamilyMember("CRongQ",oneBounds),
                //第二轮抽奖
                new LuckyStaff("张三",twoBonus),
                new LuckyStaff("小蓝",twoBonus),
                //第三轮抽奖
                new LuckyStaff("小红",threeBnus),
                new LuckyFamilyMember("李四",threeBnus),
                new LuckyStaff("赵五",threeBnus)
            };

            Console.WriteLine("抽奖结果:\n");
            foreach (var item in lucker)
            {
                item.showResult();
            }

            Console.ReadLine();
        }
    }

文章来源:https://blog.csdn.net/chen1083376511/article/details/134992819
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。