C--设计模式-三-

51 阅读37分钟

C# 设计模式(三)

原文:Design Patterns in C#

协议:CC BY-NC-SA 4.0

九、外观模式

本章涵盖了外观模式。

GoF 定义

为子系统中的一组接口提供统一的接口。Facade 定义了一个更高级的接口,使得子系统更容易使用。

概念

这种模式支持松耦合。使用这种模式,您可以通过公开一个简单的接口来强调抽象并隐藏复杂的细节。

考虑一个简单的例子。假设在一个应用中,有多个类,每个类由多个方法组成。客户可以使用这些类中的方法组合来制作产品,但是他需要记住选择哪些类,哪些方法用于这些构造的调用序列。这没什么,但是如果这些产品之间有很多差异,客户的日子就不好过了。

为了克服这一点,Facade 模式很有用。它为客户提供了一个用户友好的界面,因为所有内在的复杂性都被隐藏起来了。因此,客户可以简单地专注于他需要做的事情。

真实世界的例子

假设你要举办一个有 300 名客人的生日聚会。现在,你可以雇佣一个聚会组织者,让他们知道聚会的类型、日期和时间、参加人数等关键信息。组织者会为您完成剩下的工作。你不需要考虑他们如何装饰聚会房间,他们如何管理食物,等等。

考虑另一个例子。假设客户向银行申请贷款。在这种情况下,客户只对贷款能不能批下来感兴趣;他不关心在后端进行的内部背景验证过程。

计算机世界的例子

想想当你使用一个库中的方法时(在编程语言的上下文中)。该方法在库中是如何实现的并不重要,您只需调用该方法以便于使用。下面的例子更清楚地说明了这一点。

履行

在这个例子中,一个客户可以请求得到不同种类的机器人和他喜欢的颜色。为了达到这个目的,只有两个类。第一个是RobotBody,制作机器人的身体。第二类是RobotColor,给机器人上色。

RobotBody有一个参数化的构造函数,有两个方法叫做MakeRobotBodyDestroyRobotBody。这些方法负责制造一个机器人和摧毁一个机器人。我用一个计数器来记录机器人的数量。如果系统中没有机器人,销毁请求将被忽略。如果您愿意,可以忽略计数器,将注意力完全集中在描述该模式重要方面的部分。现在我们来看看RobotBody级。

  class RobotBody
    {
        string robotType;
        /*
        * To keep a count of number of robots.
        * This operation is optional for you.
       */
        static int count = 0;
        public RobotBody(string robotType)
        {
            this.robotType = robotType;
        }
        public void MakeRobotBody()
        {
          Console.WriteLine($"Constructing one {robotType} robot.");
          Console.WriteLine("Robot creation finished.");
          Console.WriteLine($"Total number of robot created at this moment={++count}");
        }
        public void DestroyRobotBody()
        {
            if (count > 0)
            {
                --count;
                Console.WriteLine("Robot's destruction process is over.");
            }
            else
            {
                Console.WriteLine("All robots are destroyed.");
                Console.WriteLine("Color removal operation will not continue.");
            }
        }
    }

RobotColor很容易理解。它有一个参数化的构造函数和两个方法——SetColor()RemoveColor()——来给机器人上色或从机器人身上移除颜料。下面的代码段是针对RobotColor的。

    public class RobotColor
    {
        string color;
        public RobotColor(string color)
        {
            this.color = color;
        }
        public void SetColor()
        {
            if (color == "steel")
            {
                Console.WriteLine($"The default color {color} is set for the robot.");
            }
            else
            {
                Console.WriteLine($"Painting the robot with your favourite {color} color.");
            }
        }
        public void RemoveColor()
        {
            Console.WriteLine("Attempting to remove the colors from the robot.");
        }
    }

现在是最重要的部分。您可以看到,客户端可以通过向对象RobotBody提供所需的字符串参数来创建机器人,调用MakeRobotBody() ,,然后使用RobotColor类的SetColor()来绘制机器人。因此,可以使用下面几行。

// Without Facade pattern
RobotBody robotBody = new RobotBody("Milano");
robotBody.MakeRobotBody();
RobotColor robotColor = new RobotColor("green");
robotColor.SetColor();

但是,如果一个客户端有一个名为RobotFacade的类,并且像下面这样调用,会发生什么呢?

RobotFacade facade = new RobotFacade("Milano","green");
facade.ConstructRobot();

或者,你允许他像下面这样打电话(通过提供默认颜色)?

// Making a robonaut robot with default steel color.
facade = new RobotFacade("Robonaut");
facade.ConstructRobot();

你知道答案:客户会很高兴;在这些情况下,他不需要记住创建机器人的步骤。为了简单起见,示例中只使用了两个类,但是在现实世界中,您可能需要使用大量的类和方法来制作这样的产品。在这种情况下,Facade 模式更加强大。你可以告诉你的客户使用RobotFacade类来创建和销毁机器人,而不是像RobotBodyRobotColor那样调用每个类。

现在我们来看看RobotFacade。当我使用这个类的ConstructRobot()DestroyRobot()方法时,我将RobotBodyRobotColor组合到其中,并将任务委托给相应的组件。从现在开始,RobotBodyRobotColor在这个例子中可以称为子系统类

这里是门面类。

class RobotFacade
    {
        RobotBody robotBody;
        RobotColor robotColor;
        public RobotFacade(string robotType, string color = "steel")
        {
            robotBody = new RobotBody(robotType);
            robotColor = new RobotColor(color);
        }
        public void ConstructRobot()
        {
            Console.WriteLine("Robot creation through facade starts...");
            robotBody.MakeRobotBody();
            robotColor.SetColor();
            Console.WriteLine();
        }

        public void DestroyRobot()
        {
            Console.WriteLine("Making an attempt to destroy one robot using  the facade now.");
            robotColor.RemoveColor();
            robotBody.DestroyRobotBody();
            Console.WriteLine();
        }
    }

类图

图 9-1 显示了类图。

img/463942_2_En_9_Fig1_HTML.jpg

图 9-1

类图

解决方案资源管理器视图

图 9-2 显示了程序的高层结构。从 Solution Explorer 中,您可以看到,在较高的层次上,我将子系统类与外观类和客户端代码分离开来。子系统类放在 RobotParts 文件夹中。

img/463942_2_En_9_Fig2_HTML.jpg

图 9-2

解决方案资源管理器视图

示范

下面是完整的实现。

// RobotBody.cs

using System;

namespace FacadePattern.RobotParts
{
    class RobotBody
    {
        string robotType;
        /*
        * To keep a count of number of robots.
        * This operation is optional for you.
       */
        static int count = 0;
        public RobotBody(string robotType)
        {
            this.robotType = robotType;
        }
        public void MakeRobotBody()
        {
          Console.WriteLine($"Constructing one {robotType} robot.");
          Console.WriteLine("Robot creation finished.");
          Console.WriteLine($"Total number of robot created at this moment={++count}");
        }
        public void DestroyRobotBody()
        {
            if (count > 0)
            {
                --count;
                Console.WriteLine("Robot's destruction process is over.");
            }
            else
            {
                Console.WriteLine("All robots are destroyed.");
                Console.WriteLine("Color removal operation will not continue.");
            }
        }
    }
}

// RobotColor.cs

using System;

namespace FacadePattern.RobotParts
{
    public class RobotColor
    {
        string color;
        public RobotColor(string color)
        {
            this.color = color;
        }
        public void SetColor()
        {
            if (color == "steel")
            {
                Console.WriteLine($"The default color {color} is set for the robot.");
            }
            else
            {
                Console.WriteLine($"Painting the robot with your favourite {color} color.");
            }
        }
        public void RemoveColor()
        {
            Console.WriteLine("Attempting to remove the colors from the robot.");
        }
    }
}

 // RobotFacade.cs

using System;

namespace FacadePattern.RobotParts
{
    class RobotFacade
    {
        RobotBody robotBody;
        RobotColor robotColor;
        public RobotFacade(string robotType, string color = "steel")
        {
            robotBody = new RobotBody(robotType);
            robotColor = new RobotColor(color);
        }
        public void ConstructRobot()
        {
            Console.WriteLine("Robot creation through facade starts...");
            robotBody.MakeRobotBody();
            robotColor.SetColor();
            Console.WriteLine();
        }

        public void DestroyRobot()
        {
            Console.WriteLine("Making an attempt to destroy one robot using the facade now.");
            robotColor.RemoveColor();
            robotBody.DestroyRobotBody();
            Console.WriteLine();
        }
    }
}

 // Program.cs

using System;
using FacadePattern.RobotParts;

namespace FacadePattern
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Facade Pattern Demo.***\n");
            // Making a Milano robot with green color.
            RobotFacade facade = new RobotFacade("Milano","green");
            facade.ConstructRobot();
            // Making a robonaut robot with default steel color.
            facade = new RobotFacade("Robonaut");
            facade.ConstructRobot();
            // Destroying one robot
            facade.DestroyRobot();
            // Destroying another robot
            facade.DestroyRobot();
            // This destrcution attempt should fail.
            facade.DestroyRobot();
            Console.ReadLine();

        }
    }
}

输出

这是输出。

***Facade Pattern Demo.***

Robot creation through facade starts...
Constructing one Milano robot.
Robot creation finished.
Total number of robot created at this moment=1
Painting the robot with your favourite green color.

Robot creation through facade starts...
Constructing one Robonaut robot.
Robot creation finished.
Total number of robot created at this moment=2
The default color steel is set for the robot.

Making an attempt to destroy one robot using the facade now.
Attempting to remove the colors from the robot.
Robot's destruction process is over.

Making an attempt to destroy one robot using the facade now.
Attempting to remove the colors from the robot.
Robot's destruction process is over.

Making an attempt to destroy one robot using the facade now.
Attempting to remove the colors from the robot.
All robots are destroyed.
Color removal operation will not continue.

问答环节

9.1 使用 Facade 模式的关键 优势 有哪些?

以下是一些优点。

  • 如果您的系统由许多子系统组成,那么管理这些子系统就变得很困难,并且客户端发现很难与这些子系统中的每一个单独通信。在这种情况下,外观模式非常方便。您向客户呈现了一个简化的界面,而不是复杂的子系统。这种方法还通过将客户端代码与子系统分离来支持弱耦合。

  • 它还可以帮助减少客户端需要处理的对象数量。

在这个例子中,facade 类使用了 组合 。有这个必要吗?

是的。使用这种方法,您可以访问每个子系统中的预期方法。当我使用这个类的ConstructRobot()DestroyRobot()方法时,我将任务委托给了相应的组件。

9.3 您现在可以直接访问每个子系统了吗?

是的,你可以。Facade 模式并不限制您这样做。在介绍 facade 类之前,我已经向您展示了这一点。但是在这种情况下,代码可能看起来很脏,并且您可能会失去与 Facade 模式相关的好处。在这个上下文中,您可以注意到,由于客户端可以直接访问子系统,所以它被称为透明外观。但是,当您限制这种使用并强制他们只能通过 RobotFacade 创建机器人时,您可以将 Facade 称为不透明 facade。

9.4 Facade 与适配器设计模式 有何不同?

在适配器模式中,您试图改变一个接口,以便您的客户机看不到接口之间的任何差异。相比之下,Facade 模式简化了界面。它为客户端提供了一个简单的交互界面(而不是复杂的子系统)。

9.5 一个 复杂子系统 应该只有一个门面。这是正确的吗?

一点也不。您可以为特定子系统创建任意数量的外观。

9.6 可以用 facade 添加新的东西或者附加代码吗?

是的,你可以。您可以看到,在将调用委托给实际组件之前,我在RobotFacade类的ConstructRobot()中使用了下面一行代码。

Console.WriteLine("Robot creation through facade starts...");

同样的,DestroyRobot()在试图摧毁一个机器人之前,有如下一行。

Console.WriteLine("Making an attempt to destroy one robot using  the facade now.");

9.7 与门面格局相关的 挑战 有哪些?

这里有一些挑战。

  • 子系统连接到外观层。因此,您需要关注额外的编码层(增加您的代码库)。

  • 当子系统的内部结构发生变化时,您也需要将变化合并到外观层中。

  • 一些开发人员可能需要了解这个新层,但是他们中的一些人知道如何有效地使用子系统/API。

9.8 我能让 facade 类成为静态的吗?

在许多例子中,只有一个 facade,您可能不需要初始化 facade 类。在这些情况下,如果让 facade 类成为静态的,就很有意义。

十、享元模式

这一章涵盖了 Flyweight 模式。

GoF 定义

使用共享来有效地支持大量细粒度的对象。

概念

这种模式可能看起来很简单,但是如果您没有确定核心概念,实现可能会显得很复杂。在实现这个模式之前,让我们从一个基本但详细的解释开始。

有时你需要处理许多非常相似但又不完全相同的对象。限制是您不能创建所有的文件来减少资源和内存的使用。Flyweight 模式就是用来处理这些场景的。

现在的问题是如何去做?为了理解这一点,让我们快速回顾一下面向对象编程的基础。一个类是一个模板或蓝图,一个对象是它的一个实例。一个对象可以有状态和行为。例如,如果你熟悉足球(或在美国被称为足球,你可以说 Ronaldo 或 Beckham 是Footballer类的对象。您可能会注意到,它们有“播放状态”或“非播放状态”这样的状态在玩耍状态下,他们可以展示不同的技能(或行为)——他们可以跑,可以踢,可以传球,等等。从面向对象编程开始,您可以提出以下问题。

  • 我的对象可能有哪些状态?

  • 在这些状态下,它们可以执行哪些不同的功能(行为)?

一旦你得到了这些问题的答案,你就可以继续了。现在回到享元模式。在这里你的工作是识别。

  • 我的对象的状态是什么?

  • 这些状态的哪一部分是可以改变的?

一旦你确定了答案,你就把状态分成两部分,称为内在的(不变的)和外在的(会变的)。现在你明白了,如果你制造的对象具有所有对象都可以共享的内在状态。对于外在部分,用户或客户需要传递信息。所以,无论何时你需要一个对象,你都可以得到具有内在状态的对象,然后你可以通过传递外在状态来动态地配置对象。遵循这种技术,您可以减少不必要的对象创建和内存使用。

现在让我们在下面的段落中验证你的知识,这是极其重要的。让我们看看 GoF 对享元是怎么说的。

flyweight 是一个可以同时在多个上下文中使用的共享对象。在每个上下文中,flyweight 充当一个独立的对象——它与未共享的对象实例没有区别。Flyweights 不能对它们运行的环境做出假设。这里的关键概念是内在和外在状态之间的区别。内在状态存储在 flyweight 中;它由独立于 flyweight 上下文的信息组成,因此可以共享。外在状态依赖于 flyweight 的上下文并随其变化,因此不能共享。客户端对象负责在需要时将外部状态传递给 flyweight。

真实世界的例子

假设你有一支笔。你可以用不同的墨水笔芯写不同的颜色。因此,在这个例子中,没有笔芯的笔可以被认为是具有内在数据的享元,而笔芯可以被认为是外在数据。

计算机世界的例子

假设在一个电脑游戏中,你有大量的参与者,他们的核心结构是相同的,但他们的外观各不相同(例如,他们可能有不同的状态、颜色、武器等等)。因此,如果你想存储所有具有所有变化/状态的对象,内存需求将是巨大的。因此,不需要存储所有的对象,您可以用这样一种方式设计应用,即您创建这些实例中的一个,这些实例的状态在对象之间没有变化,并且您的客户端可以维护剩余的变化/状态。如果您能够在设计阶段成功地实现这个概念,那么您已经在应用中遵循了 Flyweight 模式。

考虑另一个例子。假设一家公司需要为员工打印名片。在这种情况下,出发点是什么?企业可以创建一个通用模板,在该模板上已经打印了公司徽标、地址等(内部),然后公司将特定员工的信息(外部)放在卡片上。

这种模式的另一个常见用途是在文字处理程序中用图形表示字符,或者在应用中处理字符串时使用。

履行

下面的例子展示了三种不同类型车辆的用法:CarBus,FutureVehicle(我假设 2050 年使用)。在这个应用中,我假设客户可能想要使用这些类中的大量对象,这些对象具有他们喜欢的不同颜色。我还假设汽车(或公共汽车等)的基本结构。)不变。

当客户端请求特定的车辆时,如果应用先前创建了该类型车辆的实例,则它不会从头开始创建对象;相反,它会准备好现有的(没有color)来满足他的需求。就在交付产品之前,它会给车辆涂上客户喜欢的color。现在让我们看看实现策略。

首先,为 flyweights 创建一个接口。这个接口提供了接受 flyweights 的外部状态的通用方法。在我们的例子中,color是由客户提供的;因此,这被视为一种外在状态,这就是为什么你会看到下面的代码段。

/// <summary>
/// The 'Flyweight' interface
/// </summary>
interface IVehicle
    {
        /*
         * Client will supply the color.
         * It is extrinsic state.
         */
        void AboutMe(string color);
    }

最常见的情况是,你看到一个工厂为客户提供飞锤。这个工厂缓存 flyweights 并提供获取它们的方法。在一个共享的 flyweight 对象中,如果需要的话,可以添加内部状态并实现方法。你也可以有不共享的飞锤。在这些情况下,您可以忽略客户端传递的外部状态。

在接下来的例子中,VehicleFactory是为 flyweights 提供内在状态的工厂。一个Dictionary对象存储key/value对以存储特定类型的车辆。最初,工厂内部没有对象,但是一旦它开始接收车辆请求,它就创建车辆并缓存这些车辆以备将来使用。请注意,“创建一辆汽车”、“创建一辆公共汽车”和“创建 2050 辆汽车”是在对象创建阶段由工厂在 flyweight 对象中提供的。这些是这些车辆的固有状态,不会因产品而异。下面的代码段显示了这个工厂类。

/// <summary>
/// The factory class for flyweights.
/// </summary>
class VehicleFactory
{
    private Dictionary<string, IVehicle> vehicles = new Dictionary<string, IVehicle>();
    public int TotalObjectsCreated
    {
        get { return vehicles.Count; }
    }

    public IVehicle GetVehicleFromVehicleFactory(string vehicleType)
    {
        IVehicle vehicleCategory = null;
        if (vehicles.ContainsKey(vehicleType))
        {
               vehicleCategory = vehicles[vehicleType];

            }
            else
            {
                switch (vehicleType)
                {
                    case "car":
                        vehicleCategory = new Car("One car is created");
                        vehicles.Add("car", vehicleCategory);
                        break;
                    case "bus":
                        vehicleCategory = new Bus("One bus is created");
                        vehicles.Add("bus", vehicleCategory);
                        break;
                    case "future":
                        vehicleCategory = new FutureVehicle("Vehicle 2050 is created");
                        vehicles.Add("future", vehicleCategory);
                        break;
                    default:
                        throw new Exception("Vehicle Factory can give you cars and buses only.");
                }

            }
            return vehicleCategory;
        }
    }

现在让我们来看一个具体的享元类。下面是其中的一个类(其他的都差不多)。相关注释帮助您理解AboutMe()方法如何包含车辆的内在状态和外在状态。

    /// <summary>
    /// A 'ConcreteFlyweight' class called Car
    /// </summary>
    class Car : IVehicle
    {
        /*
         * It is intrinsic state and
         * it is independent of flyweight context.
         * this can be shared.So, our factory method will supply
         * this value inside the flyweight object.
         */
        private string description;
        /*
         * Flyweight factory will supply this
         * inside the flyweight object.
         */
        public Car(string description)
        {
            this.description = description;
        }
        // Client will supply the color
        public void AboutMe(string color)
        {
            Console.WriteLine($"{description} with {color} color.");
        }
    }

从这段代码中,您可以看到,description是在对象创建过程中提供的(Flyweight 工厂会这样做),但是color是由客户端提供的。在这个例子中,我使用一种叫做GetRandomColor()的方法随机绘制颜色。因此,在Main()中,您会看到下面的代码:

vehicle.AboutMe(GetRandomColor());

只读属性TotalObjectsCreated计算任意给定时刻不同类型的车辆;在工厂类中理解下面的代码是非常容易的。

public int TotalObjectsCreated
{
        get
        {
            return vehicles.Count;
        }
}

最后,在这个例子中,FutureVehicle被认为是一个非共享的 flyweight。所以,在这个类中,AboutMe(...)方法忽略了string参数。因此,它总是生产蓝色的车辆,并忽略客户的喜好。

// Client cannot choose color for FutureVehicle
//since it's unshared flyweight,ignoring client's input
     public void AboutMe(string color)
     {
         Console.WriteLine($"{description} with blue color.");
     }

类图

图 10-1 为类图。

img/463942_2_En_10_Fig1_HTML.jpg

图 10-1

类图

解决方案资源管理器视图

图 10-2 显示了程序各部分的高层结构。

img/463942_2_En_10_Fig2_HTML.jpg

图 10-2

解决方案资源管理器视图

演示 1

下面是完整的实现。参考评论帮助你更好的理解。

using System;
using System.Collections.Generic;//Dictionary is used here

namespace FlyweightPattern
{
    /// <summary>
    /// The 'Flyweight' interface
    /// </summary>
    interface IVehicle
    {
        /*
         * Client will supply the color.
         * It is extrinsic state.
         */
        void AboutMe(string color);
    }
    /// <summary>
    /// A 'ConcreteFlyweight' class called Car
    /// </summary>
    class Car : IVehicle
    {
        /*
         * It is intrinsic state and
         * it is independent of flyweight context.
         * this can be shared.So, our factory method will supply
         * this value inside the flyweight object.
         */
        private string description;
        /*
         * Flyweight factory will supply this
         * inside the flyweight object.
         */
        public Car(string description)
        {
            this.description = description;
        }
        // Client will supply the color
        public void AboutMe(string color)
        {
            Console.WriteLine($"{description} with {color} color.");
        }
    }
    /// <summary>
    /// A 'ConcreteFlyweight' class called Bus
    /// </summary>
    class Bus : IVehicle
    {
        /*
         * It is intrinsic state and
         * it is independent of flyweight context.
         * this can be shared.So, our factory method will supply
         * this value inside the flyweight object.
         */
        private string description;
        public Bus(string description)
        {
            this.description = description;
        }
        // Client will supply the color
        public void AboutMe(string color)
        {
            Console.WriteLine($"{description} with {color} color.");
        }
    }
    /// <summary>
    /// A 'ConcreteFlyweight' class called FutureVehicle
    /// </summary>
    class FutureVehicle : IVehicle
    {
        /*
         * It is intrinsic state and
         * it is independent of flyweight context.
         * this can be shared.So, our factory method will supply
         * this value inside the flyweight object.
         */
        private string description;
        public FutureVehicle(string description)
        {
            this.description = description;
        }
        // Client cannot choose color for FutureVehicle
        // since it's unshared flyweight,ignoring client's input
        public void AboutMe(string color)
        {
            Console.WriteLine($"{description} with blue color.");
        }
    }

    /// <summary>
    /// The factory class for flyweights.
    /// </summary>
    class VehicleFactory
    {
        private Dictionary<string, IVehicle> vehicles = new Dictionary<string, IVehicle>();
         /*
          * To count different types of vehicles
          * in a given moment.
          */
        public int TotalObjectsCreated
        {
            get
            {
                return vehicles.Count;
            }
        }
        public IVehicle GetVehicleFromVehicleFactory(string vehicleType)
        {
            IVehicle vehicleCategory = null;
            if (vehicles.ContainsKey(vehicleType))
            {
                vehicleCategory = vehicles[vehicleType];
            }
            else
            {
                switch (vehicleType)
                {
                    case "car":
                        vehicleCategory = new Car("One car is created");
                        vehicles.Add("car", vehicleCategory);
                        break;
                    case "bus":
                        vehicleCategory = new Bus("One bus is created");
                        vehicles.Add("bus", vehicleCategory);
                        break;
                    case "future":
                        vehicleCategory = new FutureVehicle("Vehicle 2050 is created");
                        vehicles.Add("future", vehicleCategory);
                        break;
                    default:
                        throw new Exception("Vehicle Factory can give you cars and buses only.");
                }
            }
            return vehicleCategory;
        }
    }

    class Client
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Flyweight Pattern Demo.***\n");
            VehicleFactory vehiclefactory = new VehicleFactory();
            IVehicle vehicle;
            /*
            * Now we are trying to get the 3 cars. Note that:we need not create additional cars if we have already created one of this category.
            */
            for (int i = 0; i < 3; i++)
            {
                vehicle = vehiclefactory.GetVehicleFromVehicleFactory("car");
                vehicle.AboutMe(GetRandomColor());
            }
            int numOfDistinctRobots = vehiclefactory.TotalObjectsCreated;
            Console.WriteLine($"\n Now, total numbers of distinct vehicle object(s) is = {numOfDistinctRobots}\n");
            /*
            Here we are trying to get the 5 more buses.Note that: we need not create additional buses if we have already created one of this category.
            */

            for (int i = 0; i < 5; i++)
            {
                vehicle = vehiclefactory.GetVehicleFromVehicleFactory("bus");
                vehicle.AboutMe(GetRandomColor());
            }
            numOfDistinctRobots = vehiclefactory.TotalObjectsCreated;
            Console.WriteLine($"\n Now, total numbers of distinct vehicle object(s) is = {numOfDistinctRobots}\n");
            /*
             Here we are trying to get the 2 future vehicles.Note that: we need not create additional future vehicle if we have already created one of this category.
             */
            for (int i = 0; i < 2; i++)
            {
                vehicle = vehiclefactory.GetVehicleFromVehicleFactory("future");
                vehicle.AboutMe(GetRandomColor());
            }
            numOfDistinctRobots = vehiclefactory.TotalObjectsCreated;
            Console.WriteLine($"\n Now, total numbers of distinct vehicle object(s) is = {numOfDistinctRobots}\n");
            Console.ReadKey();
        }

        private static string GetRandomColor()
        {
            Random r = new Random();
            /*
             You can supply any number of your choice in nextInt argument.we are simply checking the random number generated is an even number or an odd number. And based on that we are choosing the color. For simplicity, we'll use only two colors-red and green.
             */
            int random = r.Next(100);
            if (random % 2 == 0)
            {
                return "red";
            }
            else
            {
                return "green";
            }
        }
    }
}

输出

下面是一个可能的输出(因为color是随机生成的)。这是在我的机器上第一次运行的时候。

***Flyweight Pattern Demo.***

One car is created with green color.
One car is created with red color.
One car is created with green color.

 Now, total numbers of distinct vehicle object(s) is = 1

One bus is created with green color.
One bus is created with red color.
One bus is created with green color.
One bus is created with red color.
One bus is created with red color.

 Now, total numbers of distinct vehicle object(s) is = 2

Vehicle 2050 is created with blue color.
Vehicle 2050 is created with blue color.

 Now, total numbers of distinct vehicle object(s) is = 3

这是另一个可能的输出。这是我的机器第二次运行的结果。

***Flyweight Pattern Demo.***

One car is created with red color.
One car is created with red color.
One car is created with red color.

 Now, total numbers of distinct vehicle object(s) is = 1

One bus is created with red color.
One bus is created with green color.
One bus is created with red color.
One bus is created with green color.
One bus is created with red color.

 Now, total numbers of distinct vehicle object(s) is = 2

Vehicle 2050 is created with blue color.
Vehicle 2050 is created with blue color.

 Now, total numbers of distinct vehicle object(s) is = 3

Note

输出会有变化,因为在这个例子中我随机选择了颜色。

分析

当且仅当对象当时不可用时,应用才创建对象。此后,它将缓存该对象以供将来重用。

问答环节

你能指出单例模式 和享元模式的主要区别吗?

Singleton 帮助你最多维护一个系统中需要的对象。换句话说,一旦创建了所需的对象,就不能再创建更多的对象了。您需要重用现有的对象。

Flyweight 模式通常与重但相似的对象(其中状态不相同)有关,因为它们可能会占用大量内存。因此,您尝试创建一个较小的模板对象集,可以动态地配置这些模板对象来制作这些重对象。这些更小的可配置对象被称为享元对象。当您处理许多大型对象时,可以在应用中重用它们。这种方法有助于减少大块内存的消耗。基本上,flyweight 使一个看起来像许多,这就是为什么 GoF 声明:“一个 flyweight 是一个共享对象,可以同时在多个上下文中使用。flyweight 在每个上下文中都是一个独立的对象——它与未共享的对象实例没有什么区别。”

图 10-3 向你展示了在使用 Flyweight 之前,如何可视化 Flyweight 模式的核心概念。

img/463942_2_En_10_Fig3_HTML.jpg

图 10-3

在使用飞锤之前

图 10-4 显示了使用飞锤后的设计。

img/463942_2_En_10_Fig4_HTML.jpg

图 10-4

使用飞锤后

因此,从图 10-4 中,你可以看到当我们将配置-1 应用于 Flyweight 对象时,创建了 Heavy-Object1,同样,当我们将配置-2 应用于 Flyweight 对象时,创建了 Heavy-Object2。您可以看到,特定于实例的内容(如我们的演示 1 中的颜色)可以传递给 flyweights 来生成这些重对象。在这个例子中,flyweight 对象就像一个公共模板,可以根据需要进行配置。

10.2****多线程 有什么影响?

如果在多线程环境中使用 new 运算符创建对象,最终可能会创建多个不需要的对象。这类似于单例模式,补救措施也类似。

10.3 使用 Flyweight 设计模式有什么好处?

以下是一些优点。

  • 您可以减少可同等控制的重物的内存消耗。

  • 您可以减少系统中的对象总数。

  • 您可以维护许多“虚拟”对象的集中状态。

10.4 使用 Flyweight 设计模式的相关挑战是什么?

这里有一些挑战。

  • 在这种模式中,您需要花一些时间来配置这些 flyweights。这些配置时间会影响应用的整体性能。

  • 要创建 flyweights,您需要从现有对象中提取一个公共模板类。这个额外的编程层可能很棘手,有时很难调试和维护。

10.5 能不能有一个 不可共享的 flyweight 接口

是的,flyweight 接口并不强制要求它总是可共享的。因此,在某些情况下,您可能有不可共享的 flyweight,具体的 flyweight 对象作为子对象。在演示 1 中,FutureVehicle 就是为此而生的。你可以看到它总是由蓝色组成,对于这辆车,无论客户提供什么颜色(红色或绿色)作为外在状态都没有关系。

10.6 既然 flyweights 的 内在数据 相同,可以试着分享一下。这是正确的吗?

是的。请注意,“创建一辆汽车”、“创建一辆公共汽车”和“创建 2050 辆汽车”是由工厂在 flyweight(具有内在状态)对象创建阶段在 flyweight 内部提供的。

10.7 客户如何处理这些蝇量级的 外来数据

当他们需要使用这个概念时,他们需要将这些信息(状态)传递给 flyweights。

10.8 外部数据不可共享。这是正确的吗?

是的。在实现这个模式之前,理解它是非常重要的。

10.9****车辆厂 在此次实施中的作用是什么?

它缓存了 flyweights,并提供了获取它们的方法。在本例中,有多个具有内在状态的对象可以共享。因此,将它们存放在一个中心位置总是一个好主意。

10.10 我可以将工厂类作为单例实现吗?

是的,你可以。事实上,在很多应用中,你可能会看到这一点。演示 2 描述了它。

演示 2

在这个例子中,VehicleFactory工厂类是作为单例实现的。因此,您可以用下面的代码替换演示 1 中的工厂类。

/// <summary>
/// The factory class for flyweights implemented as singleton.
/// </summary>
class VehicleFactory
{
    private static readonly VehicleFactory Instance = new VehicleFactory();
        private Dictionary<string, IVehicle> vehicles = new Dictionary<string, IVehicle>();

        private VehicleFactory()
        {
          vehicles.Add("car", new Car("One car is created"));
          vehicles.Add("bus", new Bus("One bus is created"));
          vehicles.Add("future", new FutureVehicle("Vehicle 2050 is created"));
        }
        public static VehicleFactory GetInstance
        {
            get
            {
                return Instance;
            }
        }
        /*
        * To count different types of vehicles
        * in a given moment.
        */
        public int TotalObjectsCreated
        {
            get
            {
                return vehicles.Count;
            }
        }

        public IVehicle GetVehicleFromVehicleFactory(string vehicleType)
        {
            IVehicle vehicleCategory = null;
            if (vehicles.ContainsKey(vehicleType))
            {
                vehicleCategory = vehicles[vehicleType];
                return vehicleCategory;
            }
            else
            {
               throw new Exception("Currently, the vehicle factory can have cars and buses only.");
            }
        }
    }

现在,在客户端代码中,您需要使用新的代码行来适应前面的更改,而不是使用下面的代码行(它被注释掉了)。

//VehicleFactory vehiclefactory = new VehicleFactory();
VehicleFactory vehiclefactory = VehicleFactory.GetInstance;

输出

当您使用这些新代码段运行应用时,您可能会得到(因为颜色是随机生成的)如下所示的输出。

***Flyweight Pattern Demo.***

One car is created with red color.
One car is created  with red color.
One car is created with red color.

 Now, total numbers of distinct vehicle object(s) is = 3

One bus is created with green color.
One bus is created with green color.
One bus is created with green color.
One bus is created with red color.
One bus is created with red color.

 Now, total numbers of distinct vehicle object(s) is = 3

Vehicle 2050 is created with blue color.
Vehicle 2050 is created with blue color.

 Now, total numbers of distinct vehicle object(s) is = 3

分析

注意,在这个实现中,我在开始时在构造函数中初始化了所有不同类型的车辆。因此,我一开始就使用了三个不同的车辆对象。因此,如果 2050 年我不需要任何公共汽车、汽车或交通工具,我就为这些对象浪费了内存。相反,在演示 1 中,如果这些对象中有任何一个不可用,那么工厂类会创建它并缓存它以备将来使用。所以,我投票支持演示 1,除非你修改演示 2,记住这个潜在的缺点。简而言之,无论何时使用这种模式,都要创建一个对象,填充所有必需的状态信息,并将其提供给客户机。每次客户端请求一个对象时,你的应用应该检查它是否可以重用一个现有的对象(填充了所需的状态);从而减少不必要的对象创建并节省内存消耗。

微软表示, Intern 方法使用 intern 池来搜索与字符串值相等的字符串。如果存在这样的字符串,则返回它在实习生池中的引用;否则,将对该字符串的引用添加到实习生池,然后返回该引用。英寸 NET Core 3.1,我执行下面这段代码的时候,firstStringthirdString都是指同一个字符串。结果,这段代码的最后一行返回 True,而当您比较firstStringsecondString时,情况并非如此,因为它们引用的是不同的对象。

#region test for in-built flyweight pattern
string firstString = "A simple string";
string secondString = new StringBuilder().Append("A").Append(" simple").Append(" string").ToString();
string thirdString = String.Intern(secondString);
// Different references.
Console.WriteLine((Object)secondString == (Object)firstString);
// Same reference.
Console.WriteLine((Object)thirdString == (Object)firstString);
#endregion

所以,你可以说。NET Core 3.1 遵循 Flyweight 模式。

十一、组合模式

本章涵盖了组合模式。

GoF 定义

将对象组成树结构来表示部分-整体层次结构。Composite 允许客户端统一处理单个对象和对象的组合。

概念

考虑一家出售不同种类干果的商店,如腰果、枣和核桃。这些物品都有一定的价格。让我们假设你可以购买这些单独的物品中的任何一种,或者你可以购买由不同的干果物品组成的“礼品包”(或盒装物品)。在这种情况下,数据包的开销是其组成部分的总和。组合模式在类似的情况下很有用,在这种情况下,您以相同的方式处理单个部分和部分的组合,以便可以统一处理它们。

这种模式对于表示对象的部分-整体层次结构很有用。在面向对象编程中,组合对象是由一个或多个相似对象组成的对象,其中每个对象都具有相似的功能。(这也称为对象之间的“有-有”关系。)这种模式在树结构数据中很常见,当您在这样的数据结构中实现这种模式时,您不需要区分树的分支和叶节点。因此,您可以使用该模式实现这两个关键目标。

  • 您可以将对象组合成一个树形结构,以代表部分整体层次结构。

  • 您可以统一访问组合对象(分支)和单个对象(叶节点)。因此,您可以降低代码的复杂性,并使应用不容易出错。

真实世界的例子

除了我们前面的例子,你还可以想到一个由许多部门组成的组织。一般来说,一个组织有很多员工。这些雇员中的一些被分组以形成一个部门,这些部门可以被进一步分组以构建组织的高层结构。

计算机世界的例子

我提到过树数据结构可以遵循这个概念,其中客户端可以以相同的方式处理树叶和非树叶(或树枝)。所以,当你看到一个分层的数据时,你可以得到一个线索,组合模式可能是有用的。XML 文件是这种树结构的常见例子。

Note

当你遍历树时,你经常会用到迭代器设计模式的概念,这将在第十八章中介绍。

履行

在这个例子中,我代表一个大学组织。假设有一个校长和两个系主任(hod),一个是计算机科学与工程(CSE),一个是数学(Math)。假设在数学系,目前有两个讲师(或老师),在计算机科学与工程系,有三个讲师(老师)。该组织的树形结构如图 11-1 所示。

img/463942_2_En_11_Fig1_HTML.jpg

图 11-1

一个大学组织,有一名校长、两名主任和五名讲师/教师

我们还假设在年底,CSE 部门的一名讲师提交了辞呈。以下示例考虑了提到的所有场景。

类图

图 11-2 显示了类图。

img/463942_2_En_11_Fig2_HTML.jpg

图 11-2

类图

解决方案资源管理器视图

图 11-3 显示了程序的高层结构。

img/463942_2_En_11_Fig3_HTML.jpg

图 11-3

解决方案资源管理器视图

示范

该演示以树形结构为特色。IEmployee是一个接口,有三个读写属性和一个叫做DisplayDetails()的方法。看起来是这样的。

interface IEmployee
    {
        // To set an employee name
        string Name { get; set; }
        // To set an employee department
        string Dept { get; set; }
        // To set an employee designation
        string Designation { get; set; }
        // To display an employee details
        void DisplayDetails();
    }

从相关的注释中,很容易理解这三个属性设置了雇员的姓名、对应的部门和职务。EmployeeCompositeEmployee具体类实现了这个接口。Employee班级(讲师)充当一个叶节点,另一个是非叶节点。一个或多个员工可以向一个部门主管报告。因此,它被视为非叶(或分支)节点。同样,所有的 hod 都向校长报告。所以,Principal是另一个非叶节点。

数学讲师名叫 m .乔伊和 m .鲁尼。CSE 的老师被命名为 c .萨姆,c .琼斯和 c .马里乌姆。这些讲师不监督任何人,所以他们被视为叶节点。

CompositeEmployee类维护一个列表和另外两个名为AddEmployee(...)RemoveEmployee(...)的方法。这些方法向列表中添加雇员或从列表中删除雇员。

现在浏览完整的实现,并参考支持性的评论。

using System;
/* For List<Employee> using
 * the following namespace.
 */
using System.Collections.Generic;

namespace CompositePattern
{
    interface IEmployee
    {
        // To set an employee name
        string Name { get; set; }
        // To set an employee department
        string Dept { get; set; }
        // To set an employee designation
        string Designation { get; set; }
        // To display an employee details
        void DisplayDetails();
    }
    // Leaf node
    class Employee : IEmployee
    {
        public string Name { get; set; }
        public string Dept { get; set; }
        public string Designation { get; set; }
        // Details of a leaf node
        public void DisplayDetails()
        {
            Console.WriteLine($"\t{Name} works in { Dept} department.Designation:{Designation}");
        }
    }
    // Non-leaf node
    class CompositeEmployee : IEmployee
    {
        public string Name { get; set; }
        public string Dept { get; set; }
        public string Designation { get; set; }

        // The container for child objects
        private List<IEmployee> subordinateList = new List<IEmployee>();

        // To add an employee
        public void AddEmployee(IEmployee e)
        {
            subordinateList.Add(e);
        }

        // To remove an employee
        public void RemoveEmployee(IEmployee e)
        {
            subordinateList.Remove(e);
        }

        // Details of a composite node
        public void DisplayDetails()
        {
            Console.WriteLine($"\n{Name} works in {Dept} department.Designation:{Designation}");
            foreach (IEmployee e in subordinateList)
            {
                e.DisplayDetails();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Composite Pattern Demo. ***");

            #region Mathematics department
            // 2 lecturers work in Mathematics department
            Employee mathTeacher1 = new Employee { Name = "M.Joy", Dept = "Mathematic", Designation = "Lecturer" };
            Employee mathTeacher2 = new Employee { Name = "M.Roony", Dept = "Mathematics", Designation = "Lecturer" };

            // The college has a Head of Department in Mathematics
            CompositeEmployee hodMaths = new CompositeEmployee { Name = "Mrs.S.Das", Dept = "Maths", Designation = "HOD-Maths" };

            // Lecturers of Mathematics directly reports to HOD-Maths
            hodMaths.AddEmployee(mathTeacher1);
            hodMaths.AddEmployee(mathTeacher2);
            #endregion

            #region Computer Science department
            // 3 lecturers work in Computer Sc. department
            Employee cseTeacher1 = new Employee { Name = "C.Sam", Dept = "Computer Science", Designation = "Lecturer" };
            Employee cseTeacher2 = new Employee { Name = "C.Jones", Dept = "Computer Science.", Designation = "Lecturer" };
            Employee cseTeacher3 = new Employee { Name = "C.Marium", Dept = "Computer Science", Designation = "Lecturer" };

            // The college has a Head of Department in Computer science
            CompositeEmployee hodCompSc = new CompositeEmployee { Name = "Mr. V.Sarcar", Dept = "Computer Sc.", Designation = "HOD-Computer Sc." };

            /* Lecturers of Computer Sc. directly reports to HOD-CSE */
            hodCompSc.AddEmployee(cseTeacher1);
            hodCompSc.AddEmployee(cseTeacher2);
            hodCompSc.AddEmployee(cseTeacher3);
            #endregion

            #region Top level management
            // The college also has a Principal
            CompositeEmployee principal = new CompositeEmployee { Name = "Dr.S.Som", Dept = "Planning-Supervising-Managing", Designation = "Principal" };

            /* Head of Departments's of Maths and Computer Science directly reports to Principal.*/
            principal.AddEmployee(hodMaths);
            principal.AddEmployee(hodCompSc);
            #endregion

            /*
             * Printing the leaf-nodes and branches in the same way. i.e. in each case, we are calling DisplayDetails() method.
             */
            Console.WriteLine("\nDetails of a Principal object is as follows:");
            // Prints the complete structure
            principal.DisplayDetails();

            Console.WriteLine("\nDetails of a HOD object is as follows:");
            /* Prints the details of Computer Science department */
            hodCompSc.DisplayDetails();

            // Leaf node
            Console.WriteLine("\nDetails of an individual employee(leaf node) is as follows:");
            mathTeacher1.DisplayDetails();

            /*
             * Suppose, one Computer Science lecturer(C.Jones)
             * is leaving now from the organization.
             */
            hodCompSc.RemoveEmployee(cseTeacher2);
            Console.WriteLine("\nAfter the resignation of C.Jones, the organization has the following members:");
            principal.DisplayDetails();
            // Wait for user
            Console.ReadKey();
        }
    }
}

输出

这是输出。

***Composite Pattern Demo. ***

Details of a Principal object is as follows:

Dr. S.Som works in Planning-Supervising-Managing department.Designation:Principal

Mrs. S.Das works in Maths department.Designation:HOD-Maths
        M.Joy works in Mathematic department.Designation:Lecturer
        M.Roony works in Mathematics department.Designation:Lecturer

Mr. V.Sarcar works in Computer Sc. department.Designation:HOD-Computer Sc.
        C.Sam works in Computer Science department.Designation:Lecturer
        C.Jones works in Computer Science. department.Designation:Lecturer
        C.Marium works in Computer Science department.Designation:Lecturer

Details of a HOD object is as follows:

Mr. V.Sarcar works in Computer Sc. department.Designation:HOD-Computer Sc.
        C.Sam works in Computer Science department.Designation:Lecturer
        C.Jones works in Computer Science. department.Designation:Lecturer
        C.Marium works in Computer Science department.Designation:Lecturer

Details of an individual employee(leaf node) is as follows:
        M.Joy works in Mathematic department.Designation:Lecturer

After the resignation of C.Jones, the organization has the following members:

Dr. S.Som works in Planning-Supervising-Managing department.Designation:Principal

Mrs. S.Das works in Maths department.Designation:HOD-Maths
        M.Joy works in Mathematic department.Designation:Lecturer
        M.Roony works in Mathematics department.Designation:Lecturer

Mr. V.Sarcar works in Computer Sc. department.Designation:HOD-Computer Sc.
        C.Sam works in Computer Science department.Designation:Lecturer
        C.Marium works in Computer Science department.Designation:Lecturer

问答环节

11.1 使用组合设计模式的优势是什么?

以下是一些优点。

  • 在树状结构中,您可以统一处理组合对象(分支节点)和单个对象(叶节点)。在这个例子中,我使用了一个名为DisplayDetails的常用方法来打印组合对象结构(校长或部门领导)和单个对象(讲师)。

  • 使用这种设计模式实现部分-整体层次结构是很常见的。

  • 您可以轻松地向架构中添加新的组件,或者从架构中删除现有的组件。

11.2 使用组合设计模式有哪些挑战?

以下是一些缺点。

  • 如果您想要保持子节点的顺序(例如,如果解析树被表示为组件),您可能需要特别小心。

  • 如果您正在处理不可变的对象,您不能删除它们。

  • 您可以轻松地添加新组件,但是经过一段时间后,维护可能会很困难。有时,您可能想要处理具有特殊组件的组合材料。这种约束可能会导致额外的开发成本,因为您可能需要实现一个动态检查机制来支持这个概念。

11.3 在这个例子中,你使用了一个列表 数据结构 。其他数据结构可以用吗?

绝对的。没有放之四海而皆准的规则。您可以自由使用您喜欢的数据结构。GoF 还确认了没有必要使用通用数据结构。

如何将 迭代器设计模式 连接到组合设计模式?

在这个例子中,如果您想要检查一个组合对象架构,您可能需要迭代对象。此外,如果您想对一些分支执行一些特殊的活动,您可能需要迭代它的叶节点和非叶节点。

11.5 在你的实现中,在这个界面中,你只定义了一个方法, DisplayDetails 。但是您使用了附加的方法来添加和移除组合类中的对象( CompositeEmployee )。为什么不把这些方法放在接口中?

不错的观察。甚至 GoF 也讨论过这个问题。让我们看看如果在接口中放入AddEmployee(...)RemoveEmployee(...)方法会发生什么。在这种情况下,叶节点需要实现这些添加和删除操作。但是这种情况下会有意义吗?答案是否定的,在这种情况下,可能会显得你失去了透明性,但我相信你更安全,因为我在叶子节点中屏蔽了无意义的操作。这就是为什么 GoF 提到这种决定涉及到安全性和透明度之间的权衡。

我想用一个抽象类来代替接口。这是允许的吗?

在大多数情况下,简单的答案是肯定的,但是您需要理解抽象类和接口之间的区别。在典型的场景中,您可能会发现其中一个比另一个更有用。在整本书中,我只给出简单易懂的例子,所以你可能看不出它们之间有什么区别。

Note

在第三章的“问答”部分,我讨论了如何在抽象类和接口之间做出选择。

十二、桥接模式

本章涵盖了桥接模式。

GoF 定义

将抽象与其实现解耦,这样两者可以独立变化。

概念

这种模式也称为手柄/主体模式。使用它,您可以通过在实现类和抽象类之间提供一个桥梁来将它们解耦。

这个桥接口使得具体类的功能独立于接口实现者类。您可以在结构上改变不同种类的类,而不会相互影响。这种模式最初可能看起来很复杂,这就是为什么在这一章中,有两种不同的实现并有很多解释。当你浏览这些例子时,这个概念会更清楚。

真实世界的例子

在软件产品开发公司中,开发团队和营销团队都扮演着至关重要的角色。营销团队做市场调查,收集客户需求。开发团队在产品中实现这些需求,以满足客户需求。一个团队中的任何变化(如运营策略)都不应对另一个团队产生直接影响。在这种情况下,营销团队在产品的客户和软件公司的开发团队之间扮演着桥梁的角色。

计算机世界的例子

GUI 框架可以使用桥模式将抽象从平台特定的实现中分离出来。例如,使用这种模式,您可以从 Linux 或 macOS 的窗口实现中分离出一个窗口抽象。

履行

假设你需要为销售不同电子产品的卖家设计一个软件。为简单起见,我们假设卖家目前在销售电视机和 DVD 播放器,他以线上和线下(在不同的展厅)两种模式销售。

在这种情况下,您可以从图 12-1 或图 12-2 所示的设计开始。

img/463942_2_En_12_Fig2_HTML.jpg

图 12-2

方法 2

img/463942_2_En_12_Fig1_HTML.jpg

图 12-1

方法 1

经过进一步分析,您发现方法 1 很混乱,很难维护。

首先,方法 2 看起来更清晰,但是如果您想要包含新的价格(例如,ThirdPartyPriceFestiveTimePrice等)。),或者如果您想要包含新的电子产品(例如,空调、冰箱等)。),您面临着新的挑战,因为在这种设计中,各种元素紧密耦合。但是在真实的场景中,这种增强是经常需要的。

因此,为了将来的增强,您需要从一个松散耦合的系统开始,这样这两个层次(电子产品及其价格)中的任何一个都可以独立增长。桥接模式非常适合这种情况。因此,当您使用桥接模式时,结构可能看起来如图 12-3 所示。

img/463942_2_En_12_Fig3_HTML.jpg

图 12-3

使用桥接模式维护两个独立的层次结构

现在让我们从一个桥模式最常见的类图开始(见图 12-4 )。

img/463942_2_En_12_Fig4_HTML.jpg

图 12-4

经典的桥梁模式

在这个类图中,

  • Abstraction定义抽象接口,维护Implementor引用。在我的例子中,它是一个抽象类,但是非常重要的是要注意,你不应该假设你需要一个抽象类或接口来定义一个抽象。重要的是要知道这里的单词 abstraction 关于去除复杂性的方法说了什么。这些方法只是对客户端代码隐藏了它们工作的内部细节。

  • RefinedAbstraction(一个具体类)扩展了Abstraction定义的接口。这是客户在演示 1 中使用的。

  • 定义实现类的接口。这个接口方法不必与抽象方法完全对应。通常,它包括原语操作,抽象定义了基于这些原语的高级操作。还要注意,抽象类方法和实现者方法之间不需要一对一的映射。您可以在抽象类方法中使用实现者方法的组合。演示 2 说明了这一点,也可以参考 Q & A 12.5。

  • ConcreteImplementor(一个具体的类)实现了Implementor接口。

在即将到来的演示中,我遵循类似的设计。供您参考,我用注释指出了实现中的所有参与者。

类图

图 12-5 显示了类图。

img/463942_2_En_12_Fig5_HTML.jpg

图 12-5

类图

解决方案资源管理器视图

图 12-6 显示了程序的高层结构。

img/463942_2_En_12_Fig6_HTML.jpg

图 12-6

解决方案资源管理器视图

演示 1

在这个例子中,ElectronicGoods是我们的抽象类。它被放置在层级 1 中。该类定义如下。

// Abstraction
    public abstract class ElectronicGoods
    {
        public IPrice Price { get; set; }
        public string ProductType { get; set; }
        public abstract void Details();
    }

IPrice接口是我们的实现者接口。它维护第二个层次结构,定义如下。

// Implementor
    public interface IPrice
    {
        void DisplayDetails(string product);
    }

Television是覆盖Details()方法的具体抽象类,如下所示。

// Refined Abstraction
    public class Television : ElectronicGoods
    {
        /*
         * Implementation specific:
         * Delegating the task
         * to the Implementor object.
         */

        public override void Details()
        {
            Price.DisplayDetails(ProductType);
        }
    }

通过支持注释,您可以看到在Details()方法中,我从另一个层次结构中调用了DisplayDetails()方法,并传递了关于产品类型的信息。

具体的实现者(OnlinePrice, ShowroomPrice)捕获这些信息并在DisplayDetails(...)中使用它们。两个具体的实现是相似的。下面展示了其中的一个,供您参考。

    // This is ConcreteImplementor-1
    // OnlinePrice class
    public class OnlinePrice : IPrice
    {
        public void DisplayDetails(string productType)
        {
            Console.Write($"\n{productType} price at online is : 2000$");
        }
    }

为了简单起见,我没有改变演示 1 中的价格,但是在演示 2 中,您会注意到使用这种模式的灵活性,我也改变了价格。现在进行完整的演示,如下所示。

using System;

namespace BridgePattern
{
    // Abstraction
    public abstract class ElectronicGoods
    {
        public IPrice Price { get; set; }
        public string ProductType { get; set; }
        public abstract void Details();

    }
    // Refined Abstraction
    public class Television : ElectronicGoods
    {
        /*
         * Implementation specific:
         * Delegating the task
         * to the Implementor object.
         */

        public override void Details()
        {
            Price.DisplayDetails(ProductType);
        }
    }

    // Implementor
    public interface IPrice
    {
        void DisplayDetails(string product);
    }
    // This is ConcreteImplementor-1
    // OnlinePrice class
    public class OnlinePrice : IPrice
    {
        public void DisplayDetails(string productType)
        {
            Console.Write($"\n{productType} price at online is : 2000$");
        }
    }
    // This is ConcreteImplementor-2
    // ShowroomPrice class
    public class ShowroomPrice : IPrice
    {
        public void DisplayDetails(string productType)
        {
            Console.Write($"\n{productType} price at showroom is : 3000$");
        }
    }
    // Client code
    class Client
    {
        static void Main(string[] args)

        {
            Console.WriteLine("***Bridge Pattern Demo.***");
            Console.WriteLine("Verifying the market price of a television.");
            ElectronicGoods eItem = new Television();
            eItem.ProductType = "Sony Television";
            // Verifying online  price
            IPrice price = new OnlinePrice();
            eItem.Price = price;
            eItem.Details();
            // Verifying showroom price
            price = new ShowroomPrice();
            eItem.Price = price;
            eItem.Details();
        }
    }
}

输出

这是输出。

***Bridge Pattern Demo.***
Verifying the market price of a television.

Sony Television price at online is : 2000$
Sony Television price at showroom is : 3000$

附加实现

我在这一章中包含了一个额外的实现,以帮助您了解使用这一模式的灵活性。在这个例子中,我使用了构造函数,而不是属性。但是在我向您展示灵活性之前,让我们假设卖方对出售的产品提供折扣。

为了适应这一点,在这个实现中,让我们在抽象类中添加下面的方法(ElectronicGoods)。

// Additional method
public void Discount(int percentage)
{
     price.GetDiscount(percentage);
}

以及实现接口中的以下方法(IPrice)。

void GetDiscount(int percentage);

因为Discount方法是而不是抽象的,所以Television类或者ElectronicGoods的任何派生类继承了这个方法。但是由于在IPrice接口中添加了GetDiscount(int percentage)方法,具体的实现者需要实现这个方法。下面是来自OnlinePrice类实现者的这样一个实现。

public void GetDiscount(int percentage)
{
    Console.Write($"\nAt online, you can get upto {percentage}% discount.");
}

Note

同样,这些修改只是为了提供对折扣方法的支持。你应该感觉不到原来的桥模式受到改变的影响。为了保持演示 1 简短,我没有包括这个方法。

现在是灵活性部分。让我们假设卖家想卖叫做 DVD 的电子产品。卖家有时会对所有产品打折,但在节日期间,只对 DVD 提供额外折扣。

因此,DVD 类现在需要包含另一种方法来提供双重折扣(正常折扣+附加折扣)。你不能在ElectronicGoods抽象类中添加这个方法,因为在那种情况下,Television 类也会有你不想要的这个方法。最重要的是,尽管包含了 DVD 类,但是旧的代码结构不能改变。

桥接模式解决了这个问题。类图给了你一个线索。除此之外,请注意我是如何在 DVD 类中实现以下方法的。

        // Specific method in DVD
        public void DoubleDiscount()
        {
            // Normal discount(10%)
            Discount(10);
            // Festive season additional discount(5%)
            Discount(5);
        }

Note

你可以看到在DoubleDiscount()方法内部,使用了ElectronicGoodsDiscount(...)方法,所以我是按照超类抽象来编码的,它允许抽象和实现独立变化。

因为我使用了构造函数而不是属性,所以让我们先来看看变化。以下是用Details(...)Discount(...)方法进行的抽象。

    // Abstraction
    public abstract class ElectronicGoods
    {
       //public IPrice Price { get; set; }
        private IPrice price;
        public string type;
        public double cost;
        public ElectronicGoods(IPrice price)
        {
            this.price = price;
        }
        public void Details()
        {
            price.DisplayDetails(type, cost);
        }
        // Additional method
        public void Discount(int percentage)
        {
           price.GetDiscount(percentage);
        }
    }

现在,这是第一个精炼的抽象(Television类)。在这个类中,没有定义新的方法,这仅仅意味着Television类准备使用它的父类方法,并且不希望提供任何新的行为。

    // Refined Abstraction-1
    // Television class uses the default discount method.
    public class Television : ElectronicGoods
    {
        public Television(IPrice price):base(price)
        {
            this.type = "Television";
            this.cost = 2000;
        }
        // No additional method exists for Television

    }

下面是我们第二个精炼抽象(DVD类),是新加入的。在这个类中,定义了一个名为DoubleDiscount(...)的新方法,这仅仅意味着客户端可以使用这个特定于 DVD 类的方法。这个方法是在超类抽象中编码的,其他层次结构不会因为这个 DVD 类的添加而受到影响。(我的意思是,由于在层级 1 中添加了 DVD 类(或任何其他类似的类),您不需要更改位于层级 2 中的ShowroomPriceOnlinePrice等。即使您向抽象类添加了一些额外的方法,您也不需要对层次结构 2 进行更改。类似地,如果您在 implementor 中添加一个方法,您不需要在 hierarchy 1 中进行更改。)

Note

简而言之,这里您将“客户端使用的方法”与“这些方法是如何实现的”分开

    // Refined Abstraction-2
    // DVD class can give additional discount.
    public class DVD : ElectronicGoods
    {
        public DVD(IPrice price) : base(price)
        {
            this.type = "DVD";
            this.cost = 3000;
        }

        // Specic method in DVD
        public void DoubleDiscount()
        {
            // Normal discount(10%)
            Discount(10);
            // Festive season additional discount(5%)
            Discount(5);
        }
}

对照图 12-7 所示的类图。然后直接按照完整的演示输出。对于这个修改后的实现,我没有显示 Solution Explorer 视图,因为根据前面的讨论和下面的类图,它很容易理解。

img/463942_2_En_12_Fig7_HTML.jpg

图 12-7

演示 2 的类图

类图

图 12-7 显示了修改后的类图。

演示 2

下面是完整的实现。

using System;

namespace BridgePatternDemo2
{
    // Abstraction
    public abstract class ElectronicGoods
    {
       //public IPrice Price { get; set; }
        private IPrice price;
        public string type;
        public double cost;
        public ElectronicGoods(IPrice price)
        {
            this.price = price;
        }
        public void Details()
        {
            price.DisplayDetails(type,cost);
        }
        // additional method
        public void Discount(int percentage)
        {
           price.GetDiscount(percentage);
        }

    }
    // Refined Abstraction-1
    // Television class uses the default discount method.
    public class Television : ElectronicGoods
    {
        public Television(IPrice price):base(price)
        {
            this.type = "Television";
            this.cost = 2000;
        }
        // No additional method exists for Television

    }
    // Refined Abstraction-2
    // DVD class can give additional discount.
    public class DVD : ElectronicGoods
    {
        public DVD(IPrice price) : base(price)
        {
            this.type = "DVD";
            this.cost = 3000;
        }

        // Specic method in DVD
        public void DoubleDiscount()
        {
            // Normal discount(10%)
            Discount(10);
            // Festive season additional discount
            Discount(5);
        }
    }

    // Implementor
    public interface IPrice
    {
        void DisplayDetails(string product, double price);
        // additional method
        void GetDiscount(int percentage);
    }
    // This is ConcreteImplementor-1
    // OnlinePrice class
    public class OnlinePrice : IPrice
    {
        public void DisplayDetails(string productType, double price)
        {
            Console.Write($"\n{productType} price at online is : {price}$");
        }
        public void GetDiscount(int percentage)
        {
            Console.Write($"\nAt online, you can get upto {percentage}% discount.");
        }
    }
    // This is ConcreteImplementor-2
    // ShowroomPrice class
    public class ShowroomPrice : IPrice
    {
        public virtual void DisplayDetails(string productType, double price)
        {
            // Showroom price is 300$ more
            Console.Write($"\n{productType} price at showroom is : {price + 300}$");
        }
        public void GetDiscount(int percentage)
        {
            Console.Write($"\nAt showroom, additional {percentage}% discount can be approved.");
        }
    }
    // Client code
    class Client
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Alternative Implementation of Bridge Pattern.***");
            #region Television details
            Console.WriteLine("Verifying the market price of a television.");
             ElectronicGoods eItem = new Television(new OnlinePrice());
            // Verifying online price details
            eItem.Details();
            // Giving 10% discount
            eItem.Discount(10);
            // Verifying showroom price
            eItem = new Television(new ShowroomPrice());
            eItem.Details();
            // Giving 10% discount
            eItem.Discount(10);
            #endregion

            #region DVD details
            Console.WriteLine("\n\nNow checking the DVD details.");
            // Verifying online  price
            eItem = new DVD(new OnlinePrice());
            eItem.Details();
            // Giving 10% discount
            eItem.Discount(10);
            // Verifying showroom price
            eItem = new DVD(new ShowroomPrice());
            eItem.Details();
            Console.WriteLine("\nIn showroom, you want to give double discounts at festive season.");
            Console.WriteLine("For DVD, you can get double discounts using the DoubleDiscount() method.");
            //eItem.Discount();
            Console.WriteLine("For example, in festive season:");
            ((DVD)eItem).DoubleDiscount();
            #endregion
        }
    }
}

输出

***Alternative Implementation of Bridge Pattern.***
Verifying the market price of a television.

Television price at online is : 2000$
At online, you can get upto 10% discount.
Television price at showroom is : 2300$
At showroom, additional 10% discount can be approved.

Now checking the DVD details.

DVD price at online is : 3000$
At online, you can get upto 10% discount.
DVD price at showroom is : 3300$
In showroom, you want to give double discounts at festive season.
For DVD , you can get double discounts using the DoubleDiscount() method.
For example, in festive season:

At showroom, additional 10% discount can be approved.
At showroom, additional 5% discount can be approved.

问答环节

12.1 这种模式如何让我的 编程 生活更轻松?

本章介绍了两个具有以下主要意图的示例。

  • 避免项目及其相应价格之间的紧密耦合

  • 维护两个不同的层次结构,在这两个层次结构中,两者都可以扩展而不会相互影响

  • 处理实现在它们之间共享的多个对象

你可以使用简单的子类化来代替这种设计。这是正确的吗?

不。通过简单的子类化,你的实现不能动态变化。您的实现可能看起来行为不同,但是它们在编译时被绑定到抽象。

12.3 我可以在抽象类中使用构造函数而不是属性吗?

是的。有些开发人员更喜欢构造函数而不是属性(或者 getter-setter 方法)。因此,我在两个演示中向您展示了这两种用法。

使用桥梁设计模式的主要优势是什么?

以下是一些优点。

  • 实现不局限于抽象。

  • 抽象和实现都可以独立发展。

  • 具体类独立于接口实现者类。换句话说,其中一个的变化不会影响另一个。因此,您也可以用不同的方式改变抽象和实现层次。

12.5 与此模式相关的 挑战 有哪些?

整体结构可能变得复杂。这里你不直接调用一个方法。相反,抽象层将工作委托给实现层。因此,在执行操作时,您可能会注意到轻微的性能影响。

有时,桥接模式与适配器模式相混淆。(请记住,适配器模式的主要目的是只处理不兼容的接口。)

当你使用抽象类方法时,你可以结合使用实现者方法。在演示 2 中,您会看到这一点。”你能详细说明一下吗?

演示 2 中的DoubleDiscount()方法显示了这一点,其中您调用了两次Discount()方法。再举一个例子,假设实现者有下面的GiveThanks()方法。

public interface IPrice
    {
        void DisplayDetails(string product, double price);
        // Additional method
        void GetDiscount(int percentage);
        // Added for Q&A session
        void GiveThanks();
    }

具体实现者实现了该方法。假设OnlinePrice如下实现了这个方法。

public void GiveThanks()
 {
  Console.Write("Thank you, please visit the site again.");
 }

另一个具体的实现者ShowroomPrice如下实现这个方法。

public void GiveThanks()
{
Console.Write("Thank you for coming. please visit the shop again.");
 }

现在,在抽象内部,您可以添加这个方法(如果您愿意)。例如,您更新后的Discount可能如下所示。

// Additional method
public void Discount(int percentage)
{
 price.GetDiscount(percentage);
 // Added for Q&A session
 price.GiveThanks();
}

当您使用这些更改运行程序(演示 2)时,您会看到以下修改后的输出。

***Alternative Implementation of Bridge Pattern.***
Verifying the market price of a television.

Television price at online is : 2000$
At online, you can get upto 10% discount.Thank you, please visit the site again.
Television price at showroom is : 2300$
At showroom, additional 10% discount can be approved. Thank you for coming. Please visit the shop again.

Now checking the DVD details.

DVD price at online is : 3000$
At online, you can get upto 10% discount. Thank you, please visit the site again.
DVD price at showroom is : 3300$
In showroom, you want to give double discounts at festive season.
For DVD , you can get double discounts using the DoubleDiscount() method.
For example, in festive season:

At showroom, additional 10% discount can be approved. Thank you for coming. Please visit the shop again.
At showroom, additional 5% discount can be approved. Thank you for coming. Please visit the shop again.

Note

一个高级抽象方法可以包含多个实现者方法,但是客户可能不知道这一点。