设计模式:结构型模式(套路:包一层)

文章目录

结构型模式(Structural Pattern)

1.适配器模式(Adapter)

2.桥接模式(Bridge)

3.组合模式(Composite)

4.装饰模式(Decorator)

5.外观模式(Facade)

6.享元模式(Flyweight)

7.代理模式(Proxy)

 

 

结构型模式(Structural Pattern)

结构型模式是面向对象设计中的一类设计模式,主要关注于如何构建类和对象的结构,以实现更灵活、可复用和可扩展的软件系统。这些模式提供了一种方法,通过将类或对象以特定方式组合,形成更大、更复杂的结构,而无需改变其内部逻辑。

结构型模式可以分为两个主要子类别:

1. 类结构型模式:这些模式处理类之间的组合,通常通过继承来实现不同类之间的静态结构。例如,适配器模式(Adapter Pattern)将一个类的接口转换成另一个类的接口,使得不兼容的接口可以一起工作。

2. 对象结构型模式:这些模式涉及类和对象的组合,通常通过对象组合而不是继承来实现更灵活的结构。例如,组合模式(Composite Pattern)允许客户端统一处理单个对象和对象组合,而不必关心它们的差异。

这些模式的目标包括促进代码的重用、降低系统的耦合度、提高系统的扩展性和灵活性,并且遵循了一些重要的设计原则,如开闭原则(Open/Closed Principle)和合成复用原则(Composition/Aggregation Reuse Principle)等。通过合理应用结构型模式,开发人员能够更好地设计和构建软件系统,使其更易于理解、扩展和维护。

 

1.适配器模式(Adapter)

1.1.定义

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

理解:适配器模式用于解决两个不兼容接口之间的兼容性问题。它允许将一个类的接口转换成另一个类的接口,使得原本由于接口不匹配而无法一起工作的类能够协同工作。

其核心目标是创建一个适配器(Adapter),该适配器作为两个不兼容接口之间的桥梁,使得客户端能够通过统一的接口与不同接口的类交互。适配器模式通常涉及一个适配器类,该类实现了客户端期望的目标接口,并包含了一个对原有类的引用,通过适配器将原有类的接口转换成目标接口。

适配器模式可以分为两种主要形式:

1. 类适配器:使用继承的方式实现适配器。适配器类继承目标接口,并持有原始类的实例,在适配器类中进行接口转换和调用。

2. 对象适配器:使用组合的方式实现适配器。适配器类持有原始类的实例,并实现目标接口,通过调用原始类的方法来实现接口转换和适配。

适配器模式常用于系统扩展、代码重用或与第三方库的集成中,它可以有效地解决不同接口间的兼容性问题,使得原本无法协同工作的类能够无缝交互。

1.2.结构

适配器模式包含如下角色:

  • Target:目标抽象类
  • Adapter:适配器类
  • Adaptee:适配者类
  • Client:客户类

适配器模式有对象适配器和类适配器两种实现:

对象适配器:

 类适配器:

1.3.时序图

1.4.代码实现

1.4.1.对象适配器模式实现

场景:假设我们有两个不同的音乐播放器,其中一个是 MP3 播放器(MP3Player),另一个是新型的音乐播放器(AdvancedMusicPlayer),我们想要通过适配器将新型音乐播放器适配到现有的 MP3 播放器接口上。

using System;

// 目标接口:MP3 播放器
interface IAudioPlayer
{
    void Play(string audioType, string fileName);
}

// 新型音乐播放器:AdvancedMusicPlayer
class AdvancedMusicPlayer
{
    public void PlayAdvancedFormat(string fileName)
    {
        Console.WriteLine($"播放新型音乐文件:{fileName}");
    }
}

// 对象适配器:适配器将 AdvancedMusicPlayer 适配到 IAudioPlayer 接口
class MusicPlayerAdapter : IAudioPlayer
{
    private readonly AdvancedMusicPlayer advancedPlayer;

    public MusicPlayerAdapter(AdvancedMusicPlayer advancedPlayer)
    {
        this.advancedPlayer = advancedPlayer;
    }

    public void Play(string audioType, string fileName)
    {
        if (audioType.Equals("advanced", StringComparison.OrdinalIgnoreCase))
        {
            advancedPlayer.PlayAdvancedFormat(fileName);
        }
        else
        {
            Console.WriteLine("不支持该格式");
        }
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        IAudioPlayer mp3Player = new MusicPlayerAdapter(new AdvancedMusicPlayer());

        mp3Player.Play("advanced", "song.mp4"); // 使用适配器播放新型音乐文件
        mp3Player.Play("mp3", "song.mp3"); // 不支持该格式
    }
}

注释说明:

  • IAudioPlayer 是目标接口,代表 MP3 播放器接口,有 Play() 方法用于播放音频文件。
  • AdvancedMusicPlayer 是新型音乐播放器,具有 PlayAdvancedFormat() 方法用于播放新型音乐文件。
  • MusicPlayerAdapter 是适配器类,实现了 IAudioPlayer 接口,并将新型音乐播放器适配到 IAudioPlayer 接口上。
  • 在客户端代码中,创建了适配器实例,然后使用适配器播放新型音乐文件和不支持的音频文件。

1.4.2.类适配器模式实现

场景:假设我们有一个老式的播放器(OldPlayer),它有一个播放方法 PlayOldFormat(),我们想要通过适配器将其适配到一个新的播放器接口(INewPlayer)上。

using System;

// 目标接口:新型播放器
interface INewPlayer
{
    void PlayNewFormat(string fileName);
}

// 需要适配的类:老式播放器
class OldPlayer
{
    public void PlayOldFormat(string fileName)
    {
        Console.WriteLine($"播放老式音频文件:{fileName}");
    }
}

// 类适配器:适配器继承老式播放器并实现新型播放器接口
class PlayerAdapter : OldPlayer, INewPlayer
{
    public void PlayNewFormat(string fileName)
    {
        base.PlayOldFormat(fileName); // 调用老式播放器的方法来实现新型播放器接口
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        INewPlayer newPlayer = new PlayerAdapter();

        newPlayer.PlayNewFormat("song.mp3"); // 使用适配器播放老式音频文件
    }
}

注释说明:

  • INewPlayer 是目标接口,代表新型播放器接口,有 PlayNewFormat() 方法用于播放新型音频文件。
  • OldPlayer 是需要适配的类,具有 PlayOldFormat() 方法用于播放老式音频文件。
  • PlayerAdapter 是类适配器,它继承了 OldPlayer 类,并且实现了 INewPlayer 接口,通过继承的方式实现了适配。
  • 在客户端代码中,创建了适配器实例,然后使用适配器播放老式音频文件。

1.5.优缺点

1.5.1.对象适配器模式

优点:

1. 灵活性:对象适配器模式使用组合关系,因此可以适配多个不同的源对象,使得适配器可以和多个源对象协同工作,提高了灵活性和扩展性。

2. 解耦合:适配器通过委托关系将不同的接口连接起来,有助于解耦合适配器和被适配的类,同时使得适配器可以独立修改和扩展。

3. 复用性:对象适配器模式允许将已有的类和新的接口进行组合,从而重用已有的功能,提高了代码的复用性。

缺点:

1. 增加对象数量:每个适配器需要包含一个或多个被适配对象的引用,这可能会导致系统中对象数量增加,增加了系统的复杂性。

2. 过多使用导致混乱:过多地使用适配器可能会导致系统设计变得复杂,不合理的使用可能会使得代码难以理解和维护。

3. 性能损耗:对象适配器需要通过多层委托来实现适配,可能会引入额外的性能损耗,尤其在多次嵌套适配时会产生更多的开销。

对象适配器模式在提高系统灵活性和可扩展性方面有很多优点,但需要谨慎使用,以避免引入过多不必要的复杂性和性能损耗。

1.5.2.类适配器模式

优点:

1. 单一适配器:类适配器模式使用继承来进行适配,因此一个适配器类可以适配一个源类,简化了适配器的设计。

2. 重定义行为:可以通过重定义父类的行为或方法来实现适配器,这使得适配器可以在不修改源类代码的情况下,改变源类的行为。

缺点:

1. 仅适用于单一源类:类适配器模式只能适配一个源类,因为它基于继承,一个适配器只能继承一个源类,这限制了其灵活性。

2. 不支持接口适配:如果源类是一个接口而不是一个具体类,类适配器模式就无法直接适配,因为它要求适配器类继承源类。

3. 破坏封装性:适配器类需要访问源类的方法和属性,这可能需要对源类的一些方法和属性进行公开或重写,导致破坏封装性。

4. 难以替换源类:由于适配器类与源类耦合,难以在不修改适配器类的情况下替换源类,这可能限制了系统的灵活性。

类适配器模式在适配一个单一的源类时具有一定的优点,但其基于继承的特性也带来了一些限制和缺点,需要根据具体情况慎重选择使用。

1.6.使用场景

  • 系统需要使用现有的类,而这些类的接口不符合系统的需要。
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

1.7.总结

  • 结构型模式描述如何将类或者对象结合在一起形成更大的结构。
  • 适配器模式用于将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
  • 适配器模式包含四个角色:目标抽象类定义客户要用的特定领域的接口;适配器类可以调用另一个接口,作为一个转换器,对适配者和抽象目标类进行适配,它是适配器模式的核心;适配者类是被适配的角色,它定义了一个已经存在的接口,这个接口需要适配;在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。
  • 在类适配器模式中,适配器类实现了目标抽象类接口并继承了适配者类,并在目标抽象类的实现方法中调用所继承的适配者类的方法;在对象适配器模式中,适配器类继承了目标抽象类并定义了一个适配者类的对象实例,在所继承的目标抽象类方法中调用适配者类的相应业务方法。
  • 适配器模式的主要优点是将目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“开闭原则”;类适配器模式的缺点是适配器类在很多编程语言中不能同时适配多个适配者类,对象适配器模式的缺点是很难置换适配者类的方法。
  • 适配器模式适用情况包括:系统需要使用现有的类,而这些类的接口不符合系统的需要;想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作。

 

2.桥接模式(Bridge)

2.1.定义

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

理解:桥接模式用于将抽象部分与其实现部分分离,使它们可以独立变化而互不影响。该模式通过使用组合关系,将抽象部分和实现部分分离,从而允许它们可以在系统中独立地变化和扩展,而不会相互耦合。

在桥接模式中,有两个独立变化的维度:抽象和实现。抽象部分定义了客户端所需的接口,而实现部分则提供了具体的实现。通过将抽象部分和实现部分分离,桥接模式允许这两部分可以独立地进行扩展和变化。这种分离允许在不同的抽象和实现组合下创建新的对象,同时避免了类爆炸问题,即不需要为每一种组合创建一个新的类。

桥接模式中的关键角色包括:

  • 抽象类(Abstraction):定义抽象部分的接口,维护一个指向实现部分的对象的引用。
  • 扩展抽象类(Refined Abstraction):对抽象类进行扩展,提供更加具体的功能实现。
  • 实现类接口(Implementor):定义实现部分的接口,通常提供了一组供具体实现类覆盖的基本操作。
  • 具体实现类(Concrete Implementor):实现了实现类接口,并定义其具体的操作。

桥接模式常用于以下情况:

  • 当需要独立变化抽象部分和实现部分时。
  • 当希望在运行时能够选择或切换不同的实现时。
  • 当避免类爆炸问题,即不希望为每一种抽象和实现的组合创建新的类时。

桥接模式能够有效地提高系统的灵活性和可扩展性,同时使得系统更易于理解和维护。

2.2.结构

桥接模式包含如下角色:

  • Abstraction:抽象类
  • RefinedAbstraction:扩充抽象类
  • Implementor:实现类接口
  • ConcreteImplementor:具体实现类

2.3.时序图

2.4.代码实现

场景:假设我们有一个形状(Shape)的抽象类和两个不同的颜色(Color)实现类,我们希望通过桥接模式将形状和颜色分离,使得形状和颜色可以独立变化。

using System;

// 实现类接口:颜色
interface IColor
{
    string FillColor();
}

// 具体实现类:红色
class RedColor : IColor
{
    public string FillColor()
    {
        return "红色";
    }
}

// 具体实现类:蓝色
class BlueColor : IColor
{
    public string FillColor()
    {
        return "蓝色";
    }
}

// 抽象类:形状
abstract class Shape
{
    protected IColor color;

    public Shape(IColor color)
    {
        this.color = color;
    }

    public abstract string Draw();
}

// 扩展抽象类:圆形
class Circle : Shape
{
    public Circle(IColor color) : base(color)
    {
    }

    public override string Draw()
    {
        return $"绘制圆形,颜色为:{color.FillColor()}";
    }
}

// 扩展抽象类:矩形
class Rectangle : Shape
{
    public Rectangle(IColor color) : base(color)
    {
    }

    public override string Draw()
    {
        return $"绘制矩形,颜色为:{color.FillColor()}";
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        // 创建红色圆形
        IColor redColor = new RedColor();
        Shape redCircle = new Circle(redColor);
        Console.WriteLine(redCircle.Draw());

        // 创建蓝色矩形
        IColor blueColor = new BlueColor();
        Shape blueRectangle = new Rectangle(blueColor);
        Console.WriteLine(blueRectangle.Draw());
    }
}

注释说明:

  • IColor 是颜色的实现类接口,有 FillColor() 方法用于返回颜色名称。
  • RedColor BlueColor 是具体的颜色实现类。
  • Shape 是形状的抽象类,拥有一个颜色实现类的引用。
  • CircleRectangle 是扩展抽象类,继承自 Shape,并实现了 Draw() 方法来描述绘制的形状和颜色。
  • 在客户端代码中,创建了红色圆形和蓝色矩形,并分别输出其绘制信息。

2.5.优缺点

优点:

1. 分离抽象接口及其实现部分。

2. 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。

3. 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。

4. 实现细节对客户透明,可以对用户隐藏实现细节。

缺点:

桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

2.6.使用场景

  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

2.7.总结

  • 桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
  • 桥接模式包含如下四个角色:抽象类中定义了一个实现类接口类型的对象并可以维护该对象;扩充抽象类扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法;实现类接口定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的操作;具体实现类实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象将替换其父类对象,提供给客户端具体的业务操作方法。
  • 在桥接模式中,抽象化(Abstraction)与实现化(Implementation)脱耦,它们可以沿着各自的维度独立变化。
  • 桥接模式的主要优点是分离抽象接口及其实现部分,是比多继承方案更好的解决方法,桥接模式还提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以对用户隐藏实现细节;其主要缺点是增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。
  • 桥接模式适用情况包括:需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系;抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响;一个类存在两个独立变化的维度,且这两个维度都需要进行扩展;设计要求需要独立管理抽象化角色和具体化角色;不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统。

 

3.组合模式(Composite)

3.1.定义

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。

理解:组合模式允许将对象组合成树形结构以表示“整体-部分”层次结构。它能够让客户端统一处理单个对象和组合对象,对它们具有一致性的访问方式。

在组合模式中,有以下关键角色:

抽象构件(Component):定义了组合中对象的统一接口,可以是抽象类或接口,它声明了用于管理子部件的方法,以及叶子节点和组合节点的共同行为。

叶子(Leaf):表示组合中的叶子节点,叶子节点没有子节点,实现了组件接口的方法。

组合(Composite):表示具有子部件的节点,它包含了叶子和其他组合节点,实现了组件接口,可通过递归方式管理其子节点。

组合模式允许将对象组织成树形结构,使得客户端可以以统一的方式处理单个对象和组合对象。客户端无需知道正在处理的对象是叶子还是组合,只需通过统一的组件接口操作对象。这种模式使得增加新类型的组件变得简单,并且提供了更灵活的方式来操作复杂的层次结构。

组合模式在以下情况下特别有用:

1. 当需要表示对象的整体-部分层次结构时,如树形结构、目录结构等。
2. 当希望客户端能够统一处理单个对象和组合对象时,不需要区分它们的具体类型。
3. 当希望添加或移除对象的结构时,使得结构更加灵活并能够递归地进行操作。

组合模式通过统一的接口处理单个对象和组合对象,提供了一种递归结构来表示“整体-部分”关系,从而能够更加灵活地操作复杂的对象结构。

3.2.结构

组合模式包含如下角色:

  • Component: 抽象构件
  • Leaf: 叶子构件
  • Composite: 容器构件

3.3.代码实现

场景1:组织机构的层次结构

using System;
using System.Collections.Generic;

// 抽象组件:定义了树形结构中对象的通用接口
interface IComponent
{
    void Display(int depth);
}

// 叶子组件:表示组合中的叶子节点
class Employee : IComponent
{
    private string name;

    public Employee(string name)
    {
        this.name = name;
    }

    public void Display(int depth)
    {
        Console.WriteLine(new string('-', depth) + " " + name);
    }
}

// 组合组件:表示具有子部件的节点
class Department : IComponent
{
    private string name;
    private List<IComponent> subordinates = new List<IComponent>();

    public Department(string name)
    {
        this.name = name;
    }

    public void Add(IComponent component)
    {
        subordinates.Add(component);
    }

    public void Remove(IComponent component)
    {
        subordinates.Remove(component);
    }

    public void Display(int depth)
    {
        Console.WriteLine(new string('-', depth) + " " + name);

        // 递归显示子部件
        foreach (var subordinate in subordinates)
        {
            subordinate.Display(depth + 2);
        }
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        // 构建组织结构
        Department organization = new Department("总部");

        Department hrDepartment = new Department("人力资源部");
        hrDepartment.Add(new Employee("张三"));
        hrDepartment.Add(new Employee("李四"));

        Department itDepartment = new Department("IT部门");
        itDepartment.Add(new Employee("王五"));
        itDepartment.Add(new Employee("赵六"));

        organization.Add(hrDepartment);
        organization.Add(itDepartment);

        // 显示组织结构
        organization.Display(0);
    }
}

注释说明:

  • IComponent 是抽象组件,定义了树形结构中对象的通用接口。
  • Employee 是叶子组件,表示组合中的叶子节点,实现了组件接口的方法。
  • Department 是组合组件,表示具有子部件的节点,包含了叶子节点和其他组合节点,实现了组件接口,并递归方式管理其子节点。
  • 客户端代码构建了一个简单的组织结构,包含了部门和员工,并展示了整个组织结构。

 

场景2:实现文件系统的层次结构

using System;
using System.Collections.Generic;

// 抽象组件:定义了树形结构中对象的通用接口
interface IFileComponent
{
    void Display(int depth);
}

// 叶子组件:表示组合中的叶子节点(文件)
class File : IFileComponent
{
    private string name;

    public File(string name)
    {
        this.name = name;
    }

    public void Display(int depth)
    {
        Console.WriteLine(new string('-', depth) + " " + name);
    }
}

// 组合组件:表示具有子部件的节点(文件夹)
class Folder : IFileComponent
{
    private string name;
    private List<IFileComponent> children = new List<IFileComponent>();

    public Folder(string name)
    {
        this.name = name;
    }

    public void Add(IFileComponent component)
    {
        children.Add(component);
    }

    public void Remove(IFileComponent component)
    {
        children.Remove(component);
    }

    public void Display(int depth)
    {
        Console.WriteLine(new string('-', depth) + " " + name);

        // 递归显示子部件
        foreach (var child in children)
        {
            child.Display(depth + 2);
        }
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        // 构建文件系统结构
        Folder root = new Folder("根目录");

        Folder documents = new Folder("文档");
        documents.Add(new File("简历.docx"));
        documents.Add(new File("报告.pptx"));

        Folder pictures = new Folder("图片");
        pictures.Add(new File("照片1.jpg"));
        pictures.Add(new File("照片2.jpg"));

        root.Add(documents);
        root.Add(pictures);

        // 显示文件系统结构
        root.Display(0);
    }
}

注释说明:

  • IFileComponent 是抽象组件,定义了树形结构中对象的通用接口。
  • File 是叶子组件,表示组合中的叶子节点(文件),实现了组件接口的方法。
  • Folder 是组合组件,表示具有子部件的节点(文件夹),包含了叶子节点和其他组合节点,实现了组件接口,并递归方式管理其子节点。
  • 客户端代码构建了一个简单的文件系统结构,包含了文件夹和文件,并展示了整个文件系统的结构。

3.4.优缺点

优点:

1. 统一接口:组合模式通过统一的接口处理单个对象和组合对象,客户端不需要区分叶子节点和组合节点,使得操作对象更加简单一致。

2. 灵活性和可扩展性:可以很容易地增加新的叶子节点或组合节点,对于整体-部分的层次结构变得更加灵活和可扩展。

3. 简化客户端代码:客户端代码可以统一处理单个对象和组合对象,不需要在意具体对象的类型,减少了客户端代码的复杂性。

4. 递归结构:组合模式适合描述层次结构,因此可以递归地访问和操作组合结构中的对象。

缺点:

1. 限制类型的处理:由于统一的接口可能限制了组合内部不同类型对象的处理,某些特定类型的操作可能需要额外的类型检查和转换。

2. 过度一般化:在某些情况下,过度使用组合模式可能会导致设计变得过于一般化,增加了代码的抽象程度和理解难度。

3. 性能影响:当组合对象很大或者组合层次很深时,可能会影响性能,因为递归遍历整个树形结构可能导致性能损失。

组合模式通过统一接口简化了对于单个对象和组合对象的操作,提供了灵活性和可扩展性,并且适合于描述层次结构。然而,需要权衡其使用,避免过度一般化导致复杂性增加,以及在性能敏感的场景下考虑其可能带来的性能影响。

3.5.使用场景

  • 整体-部分层次结构:当需要表示对象的整体-部分层次结构时,例如树形结构、目录结构等,组合模式特别有用。它能够将对象组织成树状结构,并且能够以统一的方式处理单个对象和组合对象。
  • 层次结构操作:当需要对整个层次结构进行统一操作时,组合模式能够递归地访问和操作组合结构中的对象,简化了操作和管理。
  • 简化客户端代码:当希望客户端能够以一致的方式处理单个对象和组合对象时,组合模式可以简化客户端代码,无需对叶子节点和组合节点做区分。
  • 动态组织对象结构:当需要动态地组织对象结构,并且需要在运行时增加或移除对象时,组合模式使得对结构的修改更加灵活和简单。

文件系统、组织机构、图形用户界面中的UI组件等都是很好的组合模式的应用场景。在这些情况下,对象存在层次结构关系,可以通过组合模式来管理和操作,以便统一处理整体和部分之间的关系,并且能够轻松扩展或修改这些结构。

3.6.总结

  • 使用组合模式可以让用户可以使用统一的方式处理整个树形结构的个别对象和组合对象,从而简化客户端的操作。
  • 组合模式具有较强的扩展性,想要更改组合对象时,只需要调整内部的层次关系即可,客户端不需要作出任何改动。
  • 客户端不用考虑组合中的细节,通过添加节点和叶子就可以创建出复杂的树形结构。
  • 当需要处理的对象是树形结构时可以考虑使用组合模式。

 

4.装饰模式(Decorator)

4.1.定义

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

理解:装饰模式允许在不改变现有对象结构的情况下动态地向对象添加新的功能。这种模式通过将对象放入包装器中(也称为装饰器),然后用一个或多个装饰器对象包装原始对象,以此来为原始对象添加新的行为或责任。

在装饰模式中,核心是一个抽象组件(Component),它定义了被装饰的对象的接口,可以是抽象类或接口。具体组件(Concrete Component)是实现了抽象组件接口的具体对象。装饰器(Decorator)实现了抽象组件接口,并包含一个指向抽象组件的引用,它具有与抽象组件相同的接口,同时可以包装其他装饰器或具体组件。

装饰模式的核心思想是通过一系列嵌套的装饰器对象来动态地为对象添加新的行为。每个装饰器封装了一个组件,并且可以添加额外的行为,但其对外表现仍然是抽象组件的接口。

这种模式的优点包括:

  • 灵活性:可以在运行时动态地添加或移除对象的功能,而无需改变其结构。
  • 可扩展性:允许通过组合多个装饰器来创建不同的组合,实现多种功能组合。
  • 避免类爆炸:相对于静态继承,装饰模式避免了类爆炸问题,使得系统更加灵活、可维护。

然而,装饰模式也可能引入大量的小对象,增加了复杂性,使得理解和调试变得困难。但在需要动态地为对象添加功能且避免静态继承带来的限制时,装饰模式是一个有力的设计选择。

4.2.结构

装饰模式包含如下角色:

  • Component: 抽象构件
  • ConcreteComponent: 具体构件
  • Decorator: 抽象装饰类
  • ConcreteDecorator: 具体装饰类

4.3.时序图

4.4.代码实现

场景:假设我们有一个咖啡店,咖啡是抽象组件(ICoffee),有不同种类的具体咖啡(SimpleCoffee),我们希望通过装饰器模式动态地添加调料(装饰器)来改变咖啡的味道。

using System;

// 抽象组件:咖啡接口
interface ICoffee
{
    string GetDescription();
    double GetCost();
}

// 具体组件:简单咖啡
class SimpleCoffee : ICoffee
{
    public string GetDescription()
    {
        return "普通咖啡";
    }

    public double GetCost()
    {
        return 2.0;
    }
}

// 装饰器:调料
abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee decoratedCoffee;

    public CoffeeDecorator(ICoffee coffee)
    {
        this.decoratedCoffee = coffee;
    }

    public virtual string GetDescription()
    {
        return decoratedCoffee.GetDescription();
    }

    public virtual double GetCost()
    {
        return decoratedCoffee.GetCost();
    }
}

// 具体装饰器:牛奶
class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return $"{decoratedCoffee.GetDescription()} + 牛奶";
    }

    public override double GetCost()
    {
        return decoratedCoffee.GetCost() + 1.0;
    }
}

// 具体装饰器:糖
class SugarDecorator : CoffeeDecorator
{
    public SugarDecorator(ICoffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return $"{decoratedCoffee.GetDescription()} + 糖";
    }

    public override double GetCost()
    {
        return decoratedCoffee.GetCost() + 0.5;
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        // 制作一杯普通咖啡
        ICoffee coffee = new SimpleCoffee();
        Console.WriteLine(coffee.GetDescription() + ",价格:" + coffee.GetCost() + "");

        // 加入牛奶
        coffee = new MilkDecorator(coffee);
        Console.WriteLine(coffee.GetDescription() + ",价格:" + coffee.GetCost() + "");

        // 加入糖
        coffee = new SugarDecorator(coffee);
        Console.WriteLine(coffee.GetDescription() + ",价格:" + coffee.GetCost() + "");
    }
}

注释说明:

  • ICoffee 是咖啡的抽象组件接口,有获取描述和价格的方法。
  • SimpleCoffee 是具体的咖啡类。
  • CoffeeDecorator 是装饰器抽象类,继承了 ICoffee 接口,包含了一个指向抽象组件的引用。
  • MilkDecoratorSugarDecorator 是具体的装饰器,继承自 CoffeeDecorator,通过添加不同的调料来改变咖啡的味道和价格。
  • 在客户端代码中,制作了一杯普通咖啡,然后通过装饰器动态地添加了牛奶和糖,观察其描述和价格的变化。

4.5.优缺点

优点:

1. 灵活性和扩展性:装饰模式允许动态地为对象添加新的功能,无需修改现有代码,因此具有很高的灵活性和扩展性。

2. 避免类爆炸问题:相比于静态继承,装饰模式避免了类爆炸问题,因为它可以通过组合多个装饰器来灵活扩展对象的功能,而不是通过继承产生大量的子类。

3. 单一职责原则:装饰模式将责任分散到各个装饰器中,每个装饰器只关注特定的功能,符合单一职责原则。

4. 支持动态组合:可以通过不同的方式组合装饰器,创建不同的组合,实现更灵活的功能组装。

缺点:

1. 复杂性增加:装饰模式可能引入大量小的对象和类,增加了系统的复杂性,使得代码难以理解和维护。

2. 设计误用:如果过度使用装饰器模式,可能会导致设计过于复杂,不适当的使用装饰器可能会使得系统变得难以理解和维护。

3. 可能引起性能问题:当装饰器层级较深时,每层装饰器都需要进行额外的处理,可能会带来一些性能上的损耗。

装饰模式是一种灵活、可扩展的设计模式,能够在不修改现有对象结构的情况下,动态地为对象添加新的功能。然而,需要在使用时慎重考虑其复杂性和影响,避免过度使用装饰器造成不必要的复杂性。

4.6.使用场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类).

4.7.总结

  • 装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功 能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模 式。
  • 装饰模式包含四个角色:抽象构件定义了对象的接口,可以给这些对 象动态增加职责(方法);具体构件定义了具体的构件对象,实现了 在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法); 抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具 体职责在其子类中实现;具体装饰类是抽象装饰类的子类,负责向构 件添加新的职责。
  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动 态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子 类的情况下,将对象的功能加以扩展。
  • 装饰模式的主要优点在于可以提供比继承更多的灵活性,可以通过一种动态的 方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的 排列组合,可以创造出很多不同行为的组合,而且具体构件类与具体装饰类可 以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类;其主要缺 点在于使用装饰模式进行系统设计时将产生很多小对象,而且装饰模式比继承 更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需 要逐级排查,较为烦琐。
  • 装饰模式适用情况包括:在不影响其他对象的情况下,以动态、透明的方式给 单个对象添加职责;需要动态地给一个对象增加功能,这些功能也可以动态地 被撤销;当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展 和维护时。
  • 装饰模式可分为透明装饰模式和半透明装饰模式:在透明装饰模式中,要求客 户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构 件类型和具体装饰类型,而应该全部声明为抽象构件类型;半透明装饰模式允 许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。

 

5.外观模式(Facade)

5.1.定义

外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。

理解 :外观模式提供了一个统一的高级接口,用于访问子系统中的一组接口或功能。这种模式的主要目的是简化复杂系统的接口,为客户端提供一个更高层次、更易于使用的接口,同时隐藏了子系统内部的复杂性。

在外观模式中,外观(Facade)充当了客户端和子系统之间的中间层,它封装了子系统中的一组接口,提供了一个简化和统一的接口给客户端。外观本身不执行实际的业务逻辑,而是将请求委派给子系统中的适当对象来处理。

外观模式的核心是提供了一个更高级别的接口,将复杂的子系统进行了封装,客户端只需要与外观接口进行交互,无需直接与子系统的各个组件打交道,从而降低了耦合度,提高了系统的可维护性和灵活性。

外观模式的关键角色包括:

  • 外观(Facade): 提供了简化和统一的接口给客户端,封装了对子系统的访问。

  • 子系统(Subsystem): 包含了系统中的各个模块、类或接口,负责实际的业务逻辑和功能。

外观模式通常应用于以下情况:

  • 当需要简化一个复杂系统的接口时。
  • 当需要将子系统与客户端分离,降低耦合度,提高系统的灵活性和可维护性时。
  • 当需要隐藏系统的复杂性,对外提供更高级别的接口时。

外观模式能够有效地降低系统的复杂性,提高了系统的可维护性和扩展性,同时提供了一个更友好的接口给客户端。

5.2.结构

外观模式包含如下角色:

  • Facade: 外观角色
  • SubSystem:子系统角色

5.3.时序图

5.4.代码实现

场景:模拟一个家庭影院系统。这个系统包含多个子系统,如投影仪、音响、灯光等。外观模式可以提供一个简化的接口,让用户可以方便地操作整个家庭影院系统。

using System;

// 子系统:投影仪
class Projector
{
    public void TurnOn()
    {
        Console.WriteLine("投影仪已打开");
    }

    public void TurnOff()
    {
        Console.WriteLine("投影仪已关闭");
    }
}

// 子系统:音响
class SoundSystem
{
    public void TurnOn()
    {
        Console.WriteLine("音响已打开");
    }

    public void TurnOff()
    {
        Console.WriteLine("音响已关闭");
    }
}

// 子系统:灯光
class Lights
{
    public void Dim()
    {
        Console.WriteLine("灯光调暗");
    }

    public void Brighten()
    {
        Console.WriteLine("灯光调亮");
    }
}

// 外观:家庭影院外观
class HomeTheaterFacade
{
    private Projector projector;
    private SoundSystem soundSystem;
    private Lights lights;

    public HomeTheaterFacade()
    {
        projector = new Projector();
        soundSystem = new SoundSystem();
        lights = new Lights();
    }

    public void WatchMovie()
    {
        Console.WriteLine("准备开始观影...");
        lights.Dim();
        soundSystem.TurnOn();
        projector.TurnOn();
    }

    public void EndMovie()
    {
        Console.WriteLine("结束观影...");
        projector.TurnOff();
        soundSystem.TurnOff();
        lights.Brighten();
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        HomeTheaterFacade homeTheater = new HomeTheaterFacade();

        // 观影
        homeTheater.WatchMovie();
        Console.WriteLine("-------------------");
        // 结束观影
        homeTheater.EndMovie();
    }
}

在这个示例中:

  • ProjectorSoundSystemLights 是家庭影院子系统的具体类。
  • HomeTheaterFacade 是外观类,它封装了对子系统的操作,并提供了 WatchMovie() EndMovie() 方法作为客户端操作的入口。
  • 客户端代码创建了 HomeTheaterFacade 的实例,然后通过外观类提供的简化接口来操作家庭影院系统,如开始观影和结束观影。

5.5.优缺点

优点:

1. 简化接口:外观模式提供了一个简化的接口,隐藏了子系统的复杂性,让客户端可以更轻松地使用系统。

2. 降低耦合度:外观模式将客户端与子系统解耦,客户端不需要了解子系统的实现细节,减少了依赖和耦合。

3. 更好的封装性:外观模式提供了一个统一的入口,有助于实现子系统的封装,提高了系统的可维护性和可扩展性。

4. 易于使用:外观模式提供了一个简单易用的接口,使得客户端使用系统变得更加方便和直观。

缺点:

1. 可能不够灵活:外观模式下,客户端只能使用外观提供的接口,可能无法满足所有特定需求,如果需要更加灵活的定制,可能需要修改外观或直接访问子系统。

2. 增加了类的数量:引入外观模式会增加额外的类,可能会导致系统中类的数量增加,复杂度增加。

3. 不符合开闭原则:当需要修改子系统的功能或者增加新的子系统时,可能需要修改外观类,不符合开闭原则。

外观模式提供了一个简化的接口,有助于降低系统复杂性和提高可维护性,但在使用时需要权衡其对系统灵活性和扩展性带来的影响,确保不会因为过度封装而限制了系统的灵活性。

5.6.使用场景

  • 当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统。
  • 客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

5.7.总结

  • 在外观模式中,外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
  • 外观模式包含两个角色:外观角色是在客户端直接调用的角色,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理;在软件系统中可以同时有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能。
  • 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
  • 外观模式主要优点在于对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间的松耦合关系,并降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程;其缺点在于不能很好地限制客户使用子系统类,而且在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
  • 外观模式适用情况包括:要为一个复杂子系统提供一个简单接口;客户程序与多个子系统之间存在很大的依赖性;在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不直接产生联系。
 

6.享元模式(Flyweight)

6.1.定义

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

理解:享元模式用于优化系统中存在大量细粒度对象的情况。该模式旨在通过共享对象来减少系统中对象的数量,从而节省内存和提高性能。

在享元模式中,共享对象被用于代表大量具有相似性质的小型对象,这些对象被称为享元对象。这些享元对象包含了可以被共享的状态(内部状态)和不能被共享的状态(外部状态)。内部状态是对象共享的部分,而外部状态是对象特有的部分。

关键概念包括:

  • 享元工厂(Flyweight Factory):负责创建和管理享元对象,通常实现了对象的池化,确保相同对象只被创建一次并被共享。
  • 享元接口(Flyweight):定义了享元对象的接口,可以包含设置外部状态的方法。
  • 具体享元(Concrete Flyweight):实现了享元接口,包含了内部状态,并能处理外部状态。
  • 客户端(Client):通过享元工厂获取享元对象,并负责设置和管理享元对象的外部状态。

享元模式主要应用于以下情况:

1. 系统中存在大量相似对象,并且这些对象的大部分状态可以被共享。
2. 对象的创建和销毁开销较大,通过共享对象可以减少系统资源的消耗。
3. 外部状态可以相对容易地分离出来,并且对象可以通过外部状态进行参数化。

通过共享内部状态,享元模式可以有效地减少系统中对象的数量,节省内存空间,并且可以提高系统的性能。然而,需要注意的是,在享元模式中对外部状态的处理需要合理设计,避免因为共享对象而引入不必要的复杂性。

6.2.结构

享元模式包含如下角色:

  • Flyweight: 抽象享元类
  • ConcreteFlyweight: 具体享元类
  • UnsharedConcreteFlyweight: 非共享具体享元类
  • FlyweightFactory: 享元工厂类

6.3.时序图

6.4.代码实现

场景:一个图书馆管理系统。图书馆中有很多书籍,每本书都有自己的书名和位置信息,我们可以使用享元模式来共享相同书名的书籍信息。

using System;
using System.Collections.Generic;

// 享元接口:书籍
interface IBook
{
    void Display(string location);
}

// 具体享元类:具体书籍
class Book : IBook
{
    private string title;

    public Book(string title)
    {
        this.title = title;
    }

    public void Display(string location)
    {
        Console.WriteLine($"书名:{title},位置:{location}");
    }
}

// 享元工厂:书籍工厂
class BookFactory
{
    private Dictionary<string, IBook> books = new Dictionary<string, IBook>();

    public IBook GetBook(string title)
    {
        if (!books.ContainsKey(title))
        {
            books[title] = new Book(title);
        }
        return books[title];
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        BookFactory bookFactory = new BookFactory();

        // 从享元工厂获取书籍对象
        IBook book1 = bookFactory.GetBook("Design Patterns");
        IBook book2 = bookFactory.GetBook("Design Patterns");

        // 分别展示书籍信息
        book1.Display("Shelf A");
        book2.Display("Shelf B");

        // 判断是否为同一个对象
        Console.WriteLine("book1 和 book2 是否为同一个对象:" + ReferenceEquals(book1, book2));
    }
}

示例中:

  • IBook 是享元接口,定义了书籍的展示方法。
  • Book 是具体的享元类,表示书籍,包含书名信息,并实现了展示方法。
  • BookFactory 是享元工厂,负责创建和管理书籍对象,确保相同书名的书籍对象只被创建一次并被共享。
  • 客户端代码创建了一个书籍工厂,并从工厂中获取两本相同书名的书籍对象,并展示它们的信息。通过判断两个对象是否相同,验证了享元模式下对象的共享情况。

6.5.优缺点

优点:

1. 减少内存占用:通过共享相同的对象实例,节省了系统中大量对象所占用的内存空间,尤其是对于大量细粒度对象的场景,可以显著减少内存消耗。

2. 提高性能:减少了对象数量,可以降低系统的资源消耗,提高了系统的性能,特别是在需要频繁创建和销毁对象的场景下。

3. 灵活性:外部状态可以被传递给享元对象,使得可以在不同环境下共享对象,并根据不同的外部状态进行定制。

缺点:

1. 复杂性增加:引入了共享对象和非共享对象之间的区别,可能会增加系统的复杂性,需要维护外部状态和内部状态之间的关系。

2. 对系统设计的影响:如果使用不当,可能会导致系统过度优化,增加了设计的复杂度,降低了灵活性。

3. 不适用于所有场景:享元模式适用于大量细粒度对象的场景,如果对象数量较少或者对象不具备共享条件,使用享元模式可能无法带来明显的性能提升。

享元模式适用于需要管理大量细粒度对象且能够共享一部分状态的场景,能够有效地节省内存空间和提高性能。然而,需要根据具体的应用场景权衡其优缺点,确保在适合的场景下使用。

6.6.使用场景

  • 一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。

6.7.总结

  • 享元模式运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用,它是一种对象结构型模式。
  • 享元模式包含四个角色:抽象享元类声明一个接口,通过它可以接受并作用于外部状态;具体享元类实现了抽象享元接口,其实例称为享元对象;非共享具体享元是不能被共享的抽象享元类的子类;享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中。
  • 享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态和外部状态。其中内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享;外部状态是随环境改变而改变的、不可以共享的状态。
  • 享元模式主要优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份;其缺点是使得系统更加复杂,并且需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
  • 享元模式适用情况包括:一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费;对象的大部分状态都可以外部化,可以将这些外部状态传入对象中;多次重复使用享元对象。

 

7.代理模式(Proxy)

7.1.定义

代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。

理解:代理模式允许通过代理对象控制对其他对象的访问。代理模式提供了一个代理对象,这个代理对象可以代替原始对象进行访问,允许在访问对象之前或之后添加额外的行为。

在代理模式中,主要有以下角色:

  • 抽象主题(Subject):定义了真实主题和代理类的共同接口,客户端通过该接口访问真实主题或代理对象。
  • 真实主题(Real Subject):实现了抽象主题定义的接口,是代理模式中所要代理的对象。
  • 代理(Proxy):实现了抽象主题的接口,保存了对真实主题的引用,在客户端访问真实主题之前或之后执行一些额外操作。

代理模式主要有几种不同的类型:

  • 远程代理(Remote Proxy):控制对远程对象的访问,允许本地对象访问位于远程位置的对象。
  • 虚拟代理(Virtual Proxy):控制对创建开销大的对象的访问,延迟对象的创建直到需要真正使用时。
  • 保护代理(Protection Proxy):控制对对象的访问权限,基于访问者的权限对请求进行过滤和控制。

代理模式的核心思想是通过引入一个代理对象来间接访问真实对象,从而可以在不改变真实对象的情况下实现对其的控制和扩展。这种模式可以增强系统的安全性、减轻真实对象的负载、提高系统性能或延迟加载真实对象等。

7.2.结构

代理模式包含如下角色:

  • Subject: 抽象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 真实主题角色

7.3.时序图

7.4.代码实现

场景1:远程服务的访问

using System;

// 抽象主题:定义了真实主题和代理类的共同接口
interface IService
{
    void Request();
}

// 真实主题:远程服务
class RemoteService : IService
{
    public void Request()
    {
        Console.WriteLine("访问远程服务...");
    }
}

// 代理:远程服务代理
class RemoteServiceProxy : IService
{
    private RemoteService service;

    public void Request()
    {
        // 在访问真实主题之前或之后可以执行一些额外操作
        PreRequest();
        if (service == null)
        {
            service = new RemoteService();
        }
        service.Request();
        PostRequest();
    }

    private void PreRequest()
    {
        Console.WriteLine("代理:执行一些预处理操作...");
    }

    private void PostRequest()
    {
        Console.WriteLine("代理:执行一些后续操作...");
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        // 使用代理访问远程服务
        IService proxy = new RemoteServiceProxy();
        proxy.Request();
    }
}

示例中:

  • IService 是抽象主题,定义了真实主题和代理类的共同接口。
  • RemoteService 是真实主题,表示远程服务,实现了抽象主题定义的接口。
  • RemoteServiceProxy 是代理类,实现了抽象主题的接口,在访问真实主题之前或之后执行一些额外操作。
  • 客户端代码创建了代理对象并通过代理访问远程服务。

示例展示了代理模式的基本结构,在访问真实主题之前或之后,代理可以执行一些预处理或后续操作,并在不改变真实主题的情况下对其进行控制或扩展。

 

场景2:代理权限控制

using System;

// 抽象主题:定义了真实主题和代理类的共同接口
interface IAdminService
{
    void GrantAccess();
}

// 真实主题:管理员服务
class AdminService : IAdminService
{
    public void GrantAccess()
    {
        Console.WriteLine("管理员服务:授予访问权限");
    }
}

// 代理:管理员服务代理
class AdminServiceProxy : IAdminService
{
    private AdminService adminService;
    private string adminKey = "admin123"; // 管理员密钥

    public void GrantAccess()
    {
        // 进行权限验证
        if (IsAdmin())
        {
            if (adminService == null)
            {
                adminService = new AdminService();
            }
            adminService.GrantAccess();
        }
        else
        {
            Console.WriteLine("无权限访问");
        }
    }

    private bool IsAdmin()
    {
        // 模拟权限验证
        Console.WriteLine("代理:进行权限验证...");
        // 假设有一个密钥,只有持有密钥的用户才能访问
        string userKey = GetUserKey(); // 假设从某处获取用户提供的密钥
        return userKey == adminKey;
    }

    private string GetUserKey()
    {
        // 假设从用户输入获取密钥
        Console.WriteLine("请输入密钥:");
        return Console.ReadLine();
    }
}

// 客户端代码
class Client
{
    static void Main(string[] args)
    {
        IAdminService adminService = new AdminServiceProxy();

        // 用户尝试访问服务
        adminService.GrantAccess();
    }
}

示例中:

  • IAdminService 是抽象主题,定义了真实主题和代理类的共同接口。
  • AdminService 是真实主题,表示管理员服务,实现了抽象主题定义的接口。
  • AdminServiceProxy 是代理类,实现了抽象主题的接口,并在访问真实主题之前进行权限验证,只有持有特定密钥的用户才能访问管理员服务。
  • 客户端代码创建了代理对象,并尝试访问服务,代理会进行权限验证,如果用户提供的密钥与预设密钥匹配,则授予访问权限,否则拒绝访问。

代理模式在权限控制场景中的应用,通过代理对象进行权限验证并控制对真实主题的访问。

7.5.优缺点

优点:

1. 代理对象的隐藏:代理模式可以隐藏真实对象的具体实现细节,客户端可以通过代理访问真实对象,而无需了解其具体实现。

2. 访问控制:代理对象可以控制客户端对真实对象的访问,实现对真实对象的保护和安全控制,如权限验证、延迟加载等。

3. 增强真实对象:代理对象可以在访问真实对象前后进行额外操作,如缓存、日志记录、性能监控等,以增强真实对象的功能。

4. 减少资源消耗:在某些场景下,代理模式可以减少资源消耗,如虚拟代理可以延迟加载大对象,避免不必要的资源开销。

缺点:

1. 增加复杂性:引入代理对象会增加系统的复杂性,因为需要维护代理和真实对象之间的关系,可能会增加代码的理解和维护难度。

2. 可能引入性能开销:在执行额外操作(如权限验证、日志记录等)时,可能会引入一定的性能开销,特别是在大量调用时。

3. 不适用于每个场景:代理模式并不是适用于所有情况的解决方案,某些情况下可能会引入不必要的复杂性,甚至无法带来实际的收益。

代理模式是一种非常有用的设计模式,能够在不改变真实对象的情况下,对其进行控制和增强,但在应用时需要根据具体场景进行权衡,避免因为过度使用代理模式而引入不必要的复杂性和性能开销。

7.6.使用场景

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地 的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 另一台主机中,远程代理又叫做大使(Ambassador)。
  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟 到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

7.7.总结

  • 在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。
  • 代理模式包含三个角色:
  • 抽象主题角色声明了真实主题和代理主题的共同接口;
  • 代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象;
  • 真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方法。
  • 代理模式的优点在于能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;其缺点在于由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象,它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
  •  保护代理可以控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

 

posted @ 2024-01-19 22:09  程序员胡大圣  阅读(17)  评论(0编辑  收藏  举报