(7)结构型模式——桥接

结构型模式——桥接(Bridge)

问题背景

当对象有两个可能的扩展维度时,应该考虑使用桥接。现在我们对动物进行建模,将它们从两个维度分为陆生生物和海洋生物、植食动物和肉食动物。根据语义,这四种动物和动物这个抽象概念之间均为is-a的关系,因此我们直接提取一个动物接口IAnimal,并让四个实现类都直接实现IAnimal。这种设计的类结构是这样的:
程序结构
这种设计在稳定的系统中是没问题的,但如果系统需要迭代新版本,就要考虑有没有扩展的可能。比如:在某次更新中,我们要将肉食动物和腐食动物区分开。

害!不就加两个类的事吗,简单的鸭皮。你很快完成了任务,然后愉悦地打开了LOL。

过了几天,又来新的需求,要求将寄生生物从陆生和海洋动物中区分出来。

害!不就加三个类的事吗!

又过了几天,来了个吃土的……又过了几天,来了个外星人……现在的系统已经不成样子了,因为两个维度的交替变化使得类的数量成平方式增长,完全Hold不住了……

解决方案

为什么会出现平方式增长?以前从来没遇到过这种情况。我们透过现象看其本质,原来是因为在这个系统中,动物的特性会在两个维度(栖息地、食性)独立变化。由于这两种变化的正交性,它们放在一起就会是乘的关系,因此是平方式增长。但现实并不仅仅是两个维度,很有可能是三个、四个……
...
既然继承是如此的辣鸡,那干脆就不要用啦,我们不是有一句秘诀叫“组合优于继承”吗?通常情况下,组合和继承可以实现的功能是相同的,但继承是静态的,组合是动态的,因此组合会比继承更加灵活。

我们将栖息地或食性中的至少一个抽象成属性(书中的标准做法是抽象一个,但我个人更喜欢全部抽象成属性),使用组合的方式实现以上需求。按照面向接口编程的惯例,我们抽象出两个接口IHabitat和IFoodHabit,分别表示栖息地和食性,并让Ocean和Land、Plant和Meat分别实现两个接口。改进后的程序结构是这样的:
程序结构
类图中的动物类像一座桥梁,将栖息地和食性两个独立的类联系了起来,因此这个模式被称为桥接模式。

使用桥接后,扩展系统带来的成本被降低到了线性级,我们可以毫无顾虑地扩展各个维度而不用担心类爆炸了。

效果

  1. 提高了系统的可扩展性,降低了扩展所需的成本。
  2. 可以在运行时对每个独立维度进行动态绑定,灵活性更高。
  3. 对用户隐藏了实现细节,增强了系统的可用性。

缺陷

“组合优于继承”这个道理并非绝对,而是针对复杂系统而言的。在类比较少或系统相对稳定时,桥接反而会增加系统的复杂度,不推荐使用。我们不应该将某个说法当成万金油,奉为圣经,而应该认识到其适用范围,理性接受。在某些情况下,继承确实有着组合无可替代的优势。

相关模式

  1. 抽象工厂:使用了桥接的对象一般比较复杂,可以考虑使用抽象工厂来创建。

实现

using System;

namespace Bridge
{
    class Client
    {
        public interface IHabitat { }
        public interface IFoodHabit { }

        public class Animal
        {
            public IHabitat Habitat { get; set; }
            public IFoodHabit FoodHabit { get; set; }
            public Animal(IHabitat habitat, IFoodHabit foodHabit)
            {
                Habitat = habitat;
                FoodHabit = foodHabit;
            }
            public void Show()
            {
                Console.WriteLine($"我是{Habitat.ToString()}{FoodHabit.ToString()}动物");
            }
        }

        public class Ocean : IHabitat
        {
            public override string ToString()
            {
                return "海洋";
            }
        }

        public class Land : IHabitat
        {
            public override string ToString()
            {
                return "陆生";
            }
        }

        public class Plant : IFoodHabit
        {
            public override string ToString()
            {
                return "植食性";
            }
        }

        public class Meat : IFoodHabit
        {
            public override string ToString()
            {
                return "肉食性";
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("创建海洋植食性动物...");
            var ocean_plant = new Animal(new Ocean(), new Plant());
            ocean_plant.Show();

            Console.WriteLine("创建海洋肉食性动物...");
            var ocean_meat = new Animal(new Ocean(), new Meat());
            ocean_meat.Show();

            Console.WriteLine("创建陆生植食性动物...");
            var land_plant = new Animal(new Land(), new Plant());
            land_plant.Show();

            Console.WriteLine("创建陆生肉食性动物...");
            var land_meat = new Animal(new Land(), new Meat());
            land_meat.Show();
        }
    }
}

运行结果

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值