当前位置: 首页 > 编程日记 > 正文

技术图文:03 结构型设计模式(下)

结构型设计模式(下)

本教程主要介绍一系列用于如何将现有类或对象组合在一起形成更加强大结构的经验总结。

知识结构:

图1 知识结构


组合模式 – 树形结构的处理

Sunny 软件公司欲开发一个杀毒(AntiVirus)软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。

该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现需要提供该杀毒软件的整体框架设计方案。

在介绍 Sunny 公司开发人员提出的初始解决方案之前,我们先来分析一下操作系统中的文件目录结构:

图2 Windows目录结构

上图可以简化为如下图所示树形目录结构:

图3 树形目录结构示意图

在树形目录结构中,包含文件文件夹两类不同的元素,其中在文件夹中可以包含文件,还可以继续包含子文件夹,但是在文件中不能再包含子文件或者子文件夹。

称文件夹为容器Container),不同类型的各种文件是其成员,称为叶子Leaf),一个文件夹也可以作为另一个更大的文件夹的成员。

Sunny 软件公司的开发人员通过分析,定义了图像文件类ImageFile、文本文件类TextFile和文件夹类Folder

图像文件类:

public class ImageFile
{private readonly string _name;public ImageFile(string name){_name = name;}public void KillVirus(){Console.WriteLine("----对图像文件'" + _name + "'进行杀毒");}
}

文本文件类:

public class TextFile
{private readonly string _name;public TextFile(string name){_name = name;}public void KillVirus(){Console.WriteLine("----对文本文件'" + _name + "'进行杀毒");}
}

文件夹类:

public class Folder
{private readonly string _name;private readonly IList<Folder> _folderList = new List<Folder>();private readonly IList<ImageFile> _imageList = new List<ImageFile>();private readonly IList<TextFile> _textList = new List<TextFile>();public Folder(string name){_name = name;}public void AddFolder(Folder f){_folderList.Add(f);}public void RemoveFolder(Folder f){_folderList.Remove(f);}public void AddImageFile(ImageFile image){_imageList.Add(image);}public void RemoveImageFile(ImageFile image){_imageList.Remove(image);}public void AddTextFile(TextFile text){_textList.Add(text);}public void RemoveTextFile(TextFile text){_textList.Remove(text);}public void KillVirus(){Console.WriteLine("****对文件夹'" + _name + "'进行杀毒");foreach(Folder f in _folderList){f.KillVirus();}foreach (ImageFile image in _imageList){image.KillVirus();}foreach(TextFile text in _textList){text.KillVirus();}}
}

客户端代码:

class Program
{static void Main(string[] args){Folder folder1 = new Folder("Sunny的资料");Folder folder2 = new Folder("图像文件");Folder folder3 = new Folder("文本文件");ImageFile image1 = new ImageFile("小龙女.jpg");ImageFile image2 = new ImageFile("张无忌.gif");TextFile text1 = new TextFile("九阴真经.txt");TextFile text2 = new TextFile("葵花宝典.doc");folder2.AddImageFile(image1);folder2.AddImageFile(image2);folder3.AddTextFile(text1);folder3.AddTextFile(text2);folder1.AddFolder(folder2);folder1.AddFolder(folder3);folder1.KillVirus();}
}

输出结果如下图所示:

图4 输出结果

Sunny 公司开发人员“成功”实现了杀毒软件的框架设计,但通过仔细分析,发现该设计方案存在如下问题:

  • 文件夹类Folder的设计和实现都非常复杂,需要定义多个集合存储不同类型的成员,而且需要针对不同的成员提供增加、删除等管理成员的方法,存在大量的冗余代码,系统维护较为困难;
  • 由于系统没有提供抽象层,客户端代码必须有区别地对待充当容器的文件夹Folder和充当叶子的ImageFileTextFile,无法统一对它们进行处理;
  • 系统的灵活性和可扩展性差,如果需要增加新的类型的叶子和容器都需要对原有代码进行修改,例如在系统中增加一种新类型的视频文件VideoFile,则必须修改Folder类的源码,否则无法在文件夹中添加视频文件。

Sunny 公司的开发人员该如何解决这些问题呢?

为了让系统具有更好的灵活性和可扩展性,客户端可以一致地对待文件和文件夹,Sunny 公司开发人员重构了软件框架设计,其基本结构如下图所示:

图5 杀毒软件框架设计结构图

抽象文件类:

public abstract class AbstractFile
{public abstract void Add(AbstractFile file);public abstract void Remove(AbstractFile file);public abstract void KillVirus();
}

文件夹类:

public class Folder : AbstractFile
{private readonly string _name;private readonly IList<AbstractFile> _fileList = new List<AbstractFile>();public Folder(string name){_name = name;}public override void Add(AbstractFile file){_fileList.Add(file);}public override void Remove(AbstractFile file){_fileList.Remove(file);}public override void KillVirus(){Console.WriteLine("****对文件夹'" + _name + "'进行杀毒");foreach (AbstractFile f in _fileList){f.KillVirus();}}
}

图像文件类:

public class ImageFile : AbstractFile
{private readonly string _name;public ImageFile(string name){_name = name;}public override void Add(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public override void Remove(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public override void KillVirus(){Console.WriteLine("----对图像文件'" + _name + "'进行杀毒");}
}

文本文件类:

public class TextFile : AbstractFile
{private readonly string _name;public TextFile(string name){_name = name;}public override void Add(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public override void Remove(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public override void KillVirus(){Console.WriteLine("----对文本文件'" + _name + "'进行杀毒");}
}

视频文件类:

public class VideoFile : AbstractFile
{private readonly string _name;public VideoFile(string name){_name = name;}public override void Add(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public override void Remove(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public override void KillVirus(){Console.WriteLine("----对视频文件'" + _name + "'进行杀毒");}
}

客户端代码:

class Program
{static void Main(string[] args){AbstractFile folder1 = new Folder("Sunny的资料");AbstractFile folder2 = new Folder("图像文件");AbstractFile folder3 = new Folder("文本文件");AbstractFile folder4 = new Folder("视频文件");AbstractFile file1 = new ImageFile("小龙女.jpg");AbstractFile file2 = new ImageFile("张无忌.gif");AbstractFile file3 = new TextFile("九阴真经.txt");AbstractFile file4 = new TextFile("葵花宝典.doc");AbstractFile file5 = new VideoFile("笑傲江湖.rmvb");folder2.Add(file1);folder2.Add(file2);folder3.Add(file3);folder3.Add(file4);folder4.Add(file5);folder1.Add(folder2);folder1.Add(folder3);folder1.Add(folder4);folder1.KillVirus();}
}

输出结果如下图所示:

图6 输出结果

由于在抽象构件类中声明了所有方法,因此在ImageFile等叶子构件类中实现这些方法时必须进行相应的异常处理或错误提示。在容器构件类FolderkillVirus()方法中将递归调用其成员对象的killVirus()方法,从而实现对整个树形结构的遍历。

如果需要更换操作节点,客户端代码只需修改一行即可,将代码:

folder1.killVirus();

改为:

folder3.killVirus();

输出结果如下:

图7 输出结果

在具体实现时,我们可以创建图形化界面让用户选择所需操作的根节点,无须修改源代码,符合“开闭原则”,客户端无须关心节点的层次结构,可以对所选节点进行统一处理,提高系统的灵活性。

组合模式 为处理树形结构提供了一种较为完美的解决方案,它描述了如何将容器和叶子进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器和叶子。

组合模式Composite Pattern):
组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。

组合模式 引入了抽象构件类Component,它是所有容器类和叶子类的父类,客户端针对Component编程。组合模式结构如下图所示:


图8 组合模式类图

  • Component(抽象构件):为叶子和容器对象声明接口,定义了访问及管理它的子对象的方法。
  • Leaf(叶子):表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子对象的方法,可以通过异常等方式进行处理。
  • Composite(容器):表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子对象的方法,在其业务方法中可以递归调用其子节点的业务方法。

抽象构件:

public abstract class Component
{public abstract void Add(Component c);public abstract void Remove(Component c);public abstract Component GetChild(int i);public abstract void Operation();
}

叶子类:

public class Leaf : Component
{public override void Add(Component c){//异常处理或错误提示}public override void Remove(Component c){//异常处理或错误提示}public override Component GetChild(int i){//异常处理或错误提示return null;}public override void Operation(){//叶子构建具体业务方法的实现}
}

容器类:

public class Composite : Component
{private readonly IList<Component> _children = new List<Component>(); public override void Add(Component c){_children.Add(c);}public override void Remove(Component c){_children.Remove(c);}public override Component GetChild(int i){return _children[i];}public override void Operation(){foreach (Component c in _children){c.Operation();}}
}

组合模式 的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。

同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。

组合模式分类:

通过引入组合模式Sunny 公司设计的杀毒软件具有良好的可扩展性,在增加新的文件类型时,无须修改现有代码,只需增加一个新的文件类作为AbstractFile类的子类即可,但是由于在该类中声明了大量用于管理和访问子对象的方法,例如,Add()Remove()等,我们不得不在新增的文件类中实现这些方法,提供对应的错误提示和异常处理。为了简化代码,我们有以下两个解决方案:

解决方案一

将叶子对象的Add()Remove()等方法的实现代码移至AbstractFile类中,由AbstractFile提供统一的默认实现,代码如下:

public abstract class AbstractFile
{public void virtual Add(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public void virtual Remove(AbstractFile file){Console.WriteLine("对不起,不支持该方法!");}public abstract void KillVirus();
}

解决方案二

在抽象构件AbstractFile中不声明任何用于访问和管理成员构件的方法,代码如下所示:

public abstract class AbstractFile
{public abstract void KillVirus();
}

无论客户端如何定义叶子对象都无法调用到这些方法,不需要做任何错误和异常处理,容器对象再根据需要增加访问和管理成员的方法,但这时候也存在一个问题:客户端不得不使用容器类本身来声明容器对象,否则无法访问其中新增的Add()Remove()等方法,客户端代码片段如下所示:

static void Main(string[] args)
{Folder folder1 = new Folder("Sunny的资料");Folder folder2 = new Folder("图像文件");Folder folder3 = new Folder("文本文件");Folder folder4 = new Folder("视频文件");AbstractFile file1 = new ImageFile("小龙女.jpg");AbstractFile file2 = new ImageFile("张无忌.gif");
}

【1】透明组合模式

图9 透明组合模式结构图

透明组合模式 的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。

叶子对象不可能有下一个层次的对象,因此为其提供Add()Remove()以及GetChild()等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。

【2】 安全组合模式

图10 安全组合模式结构图

安全组合模式 的缺点是不够透明,因为叶子对象和容器对象具有不同的方法,且容器对象中那些用于管理子对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子对象和容器对象。


装饰模式 – 扩展系统功能

Sunny 软件公司基于面向对象技术开发了一套图形界面组件库,该组件库提供了大量的基本组件,如:

  • 窗体(Window
  • 文本框(TextBox
  • 列表框(ListBox
  • ……

在使用该组件库时,用户经常要求定制一些特效显示效果,如:

  • 带滚动条的窗体(ScrollBarWindow
  • 带黑色边框的文本框(BlackBorderTextBox
  • 既带滚动条又带黑色边框的列表框(ScrollBarAndBlackBorderListBox
  • ……

因此经常需要对该组件库进行扩展以增强其功能,如下图所示:


图11 带滚动条的窗体示意图

如何提高图形界面组件库的可扩展性并降低其维护成本是 Sunny 公司开发人员必须面对的一个问题。

Sunny 软件公司的开发人员针对上述要求,提出了一个基于继承复用的初始设计方案,其基本结构如下图所示:

图12 图形界面组件库初始设计方案

仔细分析该设计方案,我们不难发现存在如下几个问题:

【1】系统扩展麻烦,在某些编程语言中无法实现

  • 现在大多数面向对象语言不支持多继承,无法通过继承来实现对来自多个父类的方法的重用。
  • 例如,增加一个透明窗体类TransparentWindow,它是Window类的子类,现在需要一个同时拥有三项功能(带滚动条、带黑色边框、透明)的窗体,则必须增加一个类作为这三个窗体类的子类,C# 或 Java 语言无法实现。

【2】代码重复。由于窗体、文本框、列表框等都需要设置滚动条,因此在ScrollBarWindowScrollBarTextBoxScrollBarListBox等类中都需要包含SetScrollBar()方法,该方法的具体实现过程基本相同,代码重复,不利于对系统进行修改和维护。

【3】系统庞大,类的数目繁多

  • 3 种基本控件和 2 种扩展方式需要定义 9 个具体类;
  • 如果再增加 1 个基本控件还需要增加 3 个实体类;
  • 如果存在 3 种扩展方式,对于每一个控件而言,需要增加 7 个实体类。

根据“合成复用原则”,在实现功能复用时,要多用关联,少用继承。

SetScrollBar()方法抽取出来,封装在一个独立的类中,在这个类中定义一个Component类型的对象,通过调用ComponentDisplay()方法来显示最基本的组件,同时再通过SetScrollBar()方法对基本构件的功能进行增强。

根据“里氏代换原则”,使用子类的地方都可以透明的使用父类。

WindowListBoxTextBox都是Component的子类,程序在运行时,我们只要向这个独立的类中注入具体的Component子类的对象即可实现功能的扩展。

这个独立的类一般称为装饰器(Decorator),它的作用就是对已有对象的功能进行扩展,以获得更加符合用户需求的对象。

Sunny 公司开发人员重构了该界面组件库,基本结构如下图所示:

图13 图形界面组件库结构图

抽象组件类:

public abstract class Component
{public abstract void Display();
}

具体组件类:

public class Window : Component
{public override void Display(){Console.WriteLine("显示窗体。");}
}public class ListBox : Component
{public override void Display(){Console.WriteLine("显示列表框。");}
}public class TextBox : Component
{public override void Display(){Console.WriteLine("显示文本框。");}
}

装饰器类:

public class ComponentDecorator : Component
{private readonly Component _component;public ComponentDecorator(Component component){_component = component;}public override void Display(){_component.Display();}
}

具体装饰类:

public class ScrollBarDecorator : ComponentDecorator
{public ScrollBarDecorator(Component component) : base(component){}public void SetScrollBar(){Console.WriteLine("为组件增加滚动条。");}public override void Display(){base.Display();SetScrollBar();}
}public class BlackBorderDecorator : ComponentDecorator
{public BlackBorderDecorator(Component component) : base(component){}public void SetBlackBorder(){Console.WriteLine("为组件增加黑色边框。");}public override void Display(){base.Display();SetBlackBorder();}
}

客户端:

class Program
{static void Main(string[] args){Component component = new Window();Component componentSb = new ScrollBarDecorator(component);componentSb.Display();}
}

图14 运行结果

客户端:

class Program
{static void Main(string[] args){Component component = new Window();Component componentSb = new ScrollBarDecorator(component);Component componentBb = new BlackBorderDecorator(componentSb);componentBb.Display();}
}

图15 运行结果

装饰模式 是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在 装饰模式 中引入了装饰类,在装饰类中既可调用待装饰的原有类的方法,又可增加新的方法,以扩充原有类的功能。

装饰模式Decorator Pattern):
动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。

装饰模式 中通常定义一个装饰类,通过子类对原有组件进行装饰,其结构如下图所示:

图16 装饰模式类图

public abstract class Component
{public abstract void Operation();
}

Component(抽象组件类):它是ConcreteComponentDecorator的共同父类,声明了在ConcreteComponent中实现的业务方法。它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

public class ConcreteComponent : Component
{public override void Operation(){Console.WriteLine("具体对象的操作。");}
}

ConcreteComponent(具体组件类):它是抽象组件类的子类,用于定义具体的组件对象,实现了在抽象组件中声明的方法,装饰器可以给它增加额外的职责(方法)。

public class Decorator : Component
{private readonly Component _component;public Decorator(Component component){_component = component;}public override void Operation(){if (_component != null){_component.Operation();}}
}

Decorator(装饰类):它没有真正实施装饰,只是维护一个指向抽象组件对象的引用,通过该引用可以调用装饰之前组件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。

public class ConcreteDecoratorA : Decorator
{private string _addedState;public ConcreteDecoratorA(Component component) : base(component){}public override void Operation(){base.Operation();_addedState = "New State";Console.WriteLine("具体装饰对象A的操作");}
}public class ConcreteDecoratorB : Decorator
{public ConcreteDecoratorB(Component component) : base(component){}public void AddedBehavior(){}public override void Operation(){base.Operation();AddedBehavior();Console.WriteLine("具体装饰对象B的操作。");}
}

ConcreteDecorator(具体装饰类):由于在Decorator中注入的是Component类型的对象,可以将一个具体组件对象注入其中,再通过具体装饰类来进行装饰;也可以将一个已经装饰过的Decorator子类的对象再注入其中进行多次装饰,从而对原有功能多次扩展。

客户端:

class Program
{static void Main(string[] args){Component c = new ConcreteComponent();Component c1 = new ConcreteDecoratorA(c);Component c2 = new ConcreteDecoratorB(c1);c2.Operation();}
}

图17 输出结果

透明装饰模式与半透明装饰模式

装饰模式 存在一个问题:如果客户端希望单独调用实体装饰类新增的方法,而不想通过抽象组件中声明的方法来调用新增方法时将遇到一些麻烦,我们通过一个实例来对这种情况加以说明:

Sunny 软件公司开发的 OA 系统中,采购单(PurchaseRequest)和请假条(LeaveRequest)等文件(Document)对象都具有显示功能,现在要为其增加审批、删除等功能,使用装饰模式进行设计。

可以得到如下图所示结构图:

图18 文件对象功能增加实例结构图

抽象组建类:

public abstract class Document
{public abstract void Dispaly();
}

具体组建类:

public class PurchaseRequest : Document
{public override void Dispaly(){Console.WriteLine("显示采购单。");}
}public class LeaveRequest : Document
{public override void Dispaly(){Console.WriteLine("显示请假条。");}
}

装饰器类:

public class Decorator : Document
{private readonly Document _document;public Decorator(Document document){_document = document;}public override void Dispaly(){_document.Dispaly();}
}

具体装饰器类:

public class Approver : Decorator
{public Approver(Document document) : base(document){Console.WriteLine("增加审批功能。");}public void Approve(){Console.WriteLine("审批文件");}
}public class Deleter : Decorator
{public Deleter(Document document) : base(document){Console.WriteLine("增加删除功能。");}public void Delete(){Console.WriteLine("删除文件。");}
}

客户端:

class Program
{static void Main(string[] args){Document doc = new PurchaseRequest();Approver newDoc = new Approver(doc);newDoc.Dispaly();newDoc.Approve();}
}

图19 输出结果

Document中没有对Approve()方法声明,若newDoc使用Document类型来定义,将导致客户端无法调用新增业务方法Approve()

即:在客户端无法统一对待装饰之前的实体组件对象和装饰之后的组件对象。

在实际使用过程中,由于新增行为可能需要单独调用,因此这种形式的 装饰模式 也经常出现,这种 装饰模式 被称为 半透明装饰模式,而标准的 装饰模式透明装饰模式

【1】透明装饰模式 可以让客户端透明地使用装饰之前的对象和装饰之后的对象,无须关心它们的区别,此外,还可以对一个已装饰过的对象进行多次装饰,得到更为复杂、功能更为强大的对象。在实现 透明装饰模式 时,要求实体装饰类的operation()方法覆写装饰类的operation()方法,除了调用原有对象的operation()外还需要调用新增的AddedBehavior()方法来增加新行为。

【2】半透明装饰模式 对于客户端而言,具体组件类型无须关心,是透明的,但是具体装饰类型必须指定,这是不透明的。其最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象。在实现半透明的装饰模式时,我们只需在具体装饰类中增加一个独立的AddedBehavior()方法来封装相应的业务处理,由于客户端使用具体装饰类型来定义装饰后的对象,因此可以单独调用AddedBehavior()方法来扩展系统功能。


代理模式 – 间接访问真实对象

Sunny 软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:

  • 在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统;
  • 在进行商务信息查询时系统需要记录查询日志,以便根据查询次数收取查询费用。

该软件公司开发人员已完成了商务信息查询模块的开发任务,现希望能够以一种松耦合的方式向原有系统增加身份验证日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。

通过分析,可以在客户端和信息查询对象之间增加一个代理对象,客户端通过代理对象间接访问具有商务信息查询功能的真实对象,在代理对象中增加身份验证日志记录等功能,而无须直接对原有的商务信息查询对象进行修改,如下图所示:


图20 商务信息查询系统设计方案示意图

Sunny 软件公司的开发人员针对上述要求,提出了该商务信息查询系统的设计结构如下图所示:

图21 商务信息查询系统结构图

抽象主题类:

public interface  ISearcher
{string DoSearch(string userId, string keyword);
}

真实主题类:

public class RealSearcher : ISearch
{public string DoSearch(string userId, string keyword){Console.WriteLine("用户'{0}'使用关键词'{1}'查询商务信息!", userId, keyword);return "返回具体内容";}
}

权限控制类:

public class AccessValidator
{public bool Validate(string userId){Console.WriteLine("在数据库中验证用户'" + userId + "'是否是合法用户?");if (userId.Equals("杨过")){Console.WriteLine("'{0}'登录成功!", userId);return true;}Console.WriteLine("'{0}'登录失败!", userId);return false;}
}

日志记录类:

public class Logger
{public void Log(string userId){Console.WriteLine("更新数据库,用户'{0}'查询次数加1!", userId);}
}

代理主题类:

public class ProxySearcher : ISearcher
{private readonly RealSearcher _searcher;private readonly AccessValidator _validator;private readonly Logger _logger;public ProxySearcher(){_searcher = new RealSearcher();_validator = new AccessValidator();_logger = new Logger();}public bool Validate(string userId){return _validator.Validate(userId);}public void Log(string userId){_logger.Log(userId);}    public string DoSearch(string userId, string keyword){if (Validate(userId)){string result = _searcher.DoSearch(userId, keyword);Log(userId);return result;}return string.Empty;}
}

配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration><appSettings><add key="proxy" value="SunnyProxy.ProxySearcher"/></appSettings>
</configuration>

客户端:

using System.Configuration;
using System.Reflection;
class Program
{static void Main(string[] args){string className = ConfigurationManager.AppSettings["proxy"];Assembly assembly = Assembly.Load("SunnyProxy");ISearcher searcher = assembly.CreateInstance(className) as ISearcher;if (searcher != null){string result = searcher.DoSearch("杨过", "helloworld");Console.WriteLine(result);}}
}

图22 运行结果

在代理类ProxySearcher中实现对真实主题类的权限控制和引用计数,如果需要在访问真实主题时增加新的访问控制机制和新功能,只需增加一个新的代理类,再修改配置文件,在客户端代码中使用新增代理类即可,源代码无须修改,符合开闭原则

当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口

代理模式Proxy Pattern):
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

代理模式 结构如下图所示:


图23 代理模式结构图

public abstract class Subject
{public abstract void Request();
}

Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。

public class RealSubject : Subject
{public override void Request(){Console.WriteLine("真实的请求!");}
}

RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

public class Proxy : Subject
{private readonly RealSubject _realSubject;public Proxy(){_realSubject = new RealSubject();}public void PreRequest(){Console.WriteLine("前置条件");}public void PostRequest(){Console.WriteLine("后置条件");}public override void Request(){PreRequest();   _realSubject.Request();PostRequest();}
}

Proxy(代理主题角色):它是抽象主题类的子类,维持一个对真实主题对象的引用,调用在真实主题中实现的业务方法。调用时可以在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束。

class Program
{static void Main(string[] args){Subject proxy = new Proxy();proxy.Request();}
}

图24 运行结果

由于 代理模式 的使用目不同,可将 代理模式 分为多种类型,应用于不同的场合,满足用户的不同需求,例如:

(1)远程代理Remote Proxy):

  • 远程代理使得客户端程序可以访问在远程主机上的对象。
  • 远程代理可以将网络的细节隐藏起来,客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而 远程代理对象 承担了大部分的网络通信工作,并负责对远程业务方法的调用。

图25 远程代理示意图

(2)虚拟代理Virtual Proxy):

用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,可以在一定程度上提高系统的性能。

  • 当一个对象由于网络等原因需要较长的加载时间时,可以结合多线程技术,用一个线程显示加载时间相对较短的虚拟代理对象,其他线程用于加载真实对象。
  • 当一个对象的加载十分耗费系统资源时,虚拟代理可以让那些占用大量内存或处理起来非常复杂的对象推迟到使用它们的时候才创建,而在此之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象。

(3)缓冲代理Cache Proxy):

为某一个被频繁访问的操作结果提供一个临时存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。

  • 如果需要远程访问的数据在缓存中已经存在,则无须再重复执行获取数据的方法,直接返回存储在缓存中的数据即可。

(4)保护代理Protect Proxy):

需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时使用。

(5)智能引用代理Smart Reference Proxy):

需要为一个对象的访问提供一些额外的操作时使用,例如将对象被调用的次数记录下来等。


相关文章:

程序员必知8大排序3大查找(三)

前两篇 《程序员必知8大排序3大查找&#xff08;一&#xff09;》 《程序员必知8大排序3大查找&#xff08;二&#xff09;》 三种查找算法:顺序查找&#xff0c;二分法查找&#xff08;折半查找&#xff09;&#xff0c;分块查找&#xff0c;散列表&#xff08;以后谈&#xf…

MongoDB给数据库创建用户

转自http://www.imooc.com/article/18439 一.先以非授权的模式启动MongoDB非授权&#xff1a; linux/Mac : mongod -f /mongodb/etc/mongo.confwindows : mongod --config c:\mongodb\etc\mongo.conf 或者 net start mongodb &#xff08;前提是mongo安装到了服务里面&#xff…

如何挑选一家好的软件测试培训机构

随着智能时代的发展&#xff0c;我们的手机APP等各种软件都变得越来越复杂化、规模化&#xff0c;软件测试这一步骤是必不可少的&#xff0c;这也造就了这个行业的兴起&#xff0c;越来越多的人想要学习软件测试技术&#xff0c;想要知道如何挑选一家好的软件测试培训机构?来看…

POJ 3177 判决素数个数

时间限制: 1000ms内存限制:65536kB描述输入两个整数X和Y&#xff0c;输出两者之间的素数个数&#xff08;包括X和Y&#xff09;。输入两个整数X和Y&#xff0c;X和Y的大小任意。输出输出一个整数&#xff0c;结果可以是0&#xff0c;或大于0的整数。样例输入1 100样例输出25&am…

数据结构与算法:22 精选练习50

精选练习50 马上就要期末考试或者考研了。为了大家复习的方便&#xff0c;我精选了有关数据结构与算法的50道选择题&#xff0c;大家可以抽空练习一下。公众号后台回复“答案”可以获取该50道题目的答案。 01、数据在计算机中的表示称为数据的______。 &#xff08;A&#x…

极速理解设计模式系列:11.单例模式(Singleton Pattern)

单例模式&#xff1a;确保某一个类只有一个实例&#xff0c;而且自行实例化并向整个系统提供这个实例。这个类称为单例类。 三要点&#xff1a; 一、单例类只能有一个实例 二、单例类必须自行创建自身实例 三、单例类自行向整个系统提供实例 类图&#xff1a; 应用场景&#xf…

参加web前端培训要学哪些知识

IT行业&#xff0c;web前端技术是比较吃香的&#xff0c;也是工资待遇非常高的行业之一&#xff0c;如果想要做一名合格的web前端工程师&#xff0c;系统学习是非常重要的&#xff0c;那么参加web前端培训要学哪些知识呢?来看看下面的详细介绍。 参加web前端培训要学哪些知识?…

数据结构与算法:19 排序

19 排序 知识结构&#xff1a; 1. 排序的基本概念与术语 假设含有nnn个记录的序列为{r1,r2,⋯,rn}\lbrace r_1,r_2,\cdots,r_n \rbrace{r1​,r2​,⋯,rn​}&#xff0c;其相应的关键字分别为{k1,k2,⋯,kn}\lbrace k_1,k_2,\cdots,k_n \rbrace{k1​,k2​,⋯,kn​}&#xff0c;…

Objective-C 什么是类

Objective-C 什么是类 转自http://www.189works.com/article-31219-1.html 之前一直做C开发&#xff0c;最近2个多月转 Objective-C&#xff0c; 入门的时候&#xff0c;遇到了很多的困惑。现在过节&#xff0c;正是解决他们的好时机。 主要参考来自http://www.sealiesoftware.…

APP之红点提醒三个阶段

下面这个页面就是我们进入APP后的主界面。客户选项的红点上数字就是显示我们没有查看的客户总数量。 当我们切换到客户这个fragment时&#xff0c;会显示贷款客户数量与保险客户数量。 当我们随便点击入一个选项&#xff0c;假如进入到保险客户的这个activity里面&#xff0c;L…

零基础参加java培训的系统学习路线

​ 零基础想要学习java技术&#xff0c;那么最好的选择就是参加java培训&#xff0c;进行系统的学习&#xff0c;以下就是小编为大家整理的零基础参加java培训的系统学习路线&#xff0c;希望能够帮助到正在学习java技术的零基础同学。 零基础参加java培训的系统学习路线&#…

在ASP.NET中跟踪和恢复大文件下载

在Web应用程序中处理大文件下载的问题一直出了名的困难&#xff0c;因此对于大多数站点来说&#xff0c;如果用户的下载被中断了&#xff0c;它们只能说悲哀降临到用户的身上了。但是我们现在不必这样了&#xff0c;因为你可以使自己的ASP.NET应用程序有能力支持可恢复&#xf…

ZeroMQ实例-使用ZeroMQ进行windows与linux之间的通信

1、本文包括 1&#xff09;在windows下使用ZMQ 2&#xff09;在windows环境下与Linux环境下进行网络通信 2、在Linux下使用ZMQ 之前写过一篇如何在Linux环境下使用ZMQ的文章 《ZeroMQ实例-使用ZMQ(ZeroMQ)进行局域网内网络通信》&#xff0c;这里就不再赘述。 3、在Windows环境…

线性代数:03 向量空间 -- 基本概念

本讲义是自己上课所用幻灯片&#xff0c;里面没有详细的推导过程&#xff08;笔者板书推导&#xff09;只以大纲的方式来展示课上的内容&#xff0c;以方便大家下来复习。 本章主要介绍向量空间的知识&#xff0c;与前两章一样本章也可以通过研究解线性方程组的解把所有知识点…

如何获得PMP认证证书

​ pmp证书是一项由美国项目管理协会发起的项目管理专业人士认证证书&#xff0c;它属于国际认证类证书&#xff0c;含金量是非常高的&#xff0c;那么如何获得PMP认证证书呢?来看看下面的详细介绍。 如何获得PMP证书? PMP证书的获取是需要参加PMP考试的。我国自1999年引进PM…

UITextField的详细使用

UItextField通常用于外部数据输入&#xff0c;以实现人机交互。下面以一个简单的登陆界面来讲解UItextField的详细使用。&#xff0f;&#xff0f;用来显示“用户名”的labelUILabel* label1 [[UILabelalloc] initWithFrame:CGRectMake(15, 65, 70, 30)];label1.backgroundCol…

06-hibernate注解-一对多单向外键关联

一对多单向外键 1&#xff0c;一方持有多方的集合&#xff0c;一个班级有多个学生&#xff08;一对多&#xff09;。 2&#xff0c;OneToMany(cascade{CascadeType.ALL}, fetchFetchType.LAZY )  //级联关系&#xff0c;抓取策略&#xff1a;懒加载。 JoinColumn(name"c…

线性代数:03 向量空间 -- 矩阵的零空间,列空间,线性方程组解的结构

本讲义是自己上课所用幻灯片&#xff0c;里面没有详细的推导过程&#xff08;笔者板书推导&#xff09;只以大纲的方式来展示课上的内容&#xff0c;以方便大家下来复习。 本章主要介绍向量空间的知识&#xff0c;与前两章一样本章也可以通过研究解线性方程组的解把所有知识点…

学Python培训有什么用

​ Python在近几年的发展非常迅速&#xff0c;在互联网行业Python的薪资也越来越高&#xff0c;不少人开始准备学习Python技术&#xff0c;那么到底学Python培训有什么用呢?来看看下面的详细介绍。 学Python培训有什么用? 学习python可以提高工作效率&#xff0c;使用python&…

SQL压力测试用的语句和相关计数器

将数据库中所有表的所有的内容选一遍: IF object_id(tempdb..#temp) is not null BEGIN DROP TABLE #temp END DECLARE index int DECLARE count int DECLARE schemaname varchar(50) DECLARE tablename varchar(50) set index1 set count(select count(*) from s…

线性代数:04 特征值与特征向量 -- 特征值与特征向量

本讲义是自己上课所用幻灯片&#xff0c;里面没有详细的推导过程&#xff08;笔者板书推导&#xff09;只以大纲的方式来展示课上的内容&#xff0c;以方便大家下来复习。 本章主要介绍特征值与特征向量的知识&#xff0c;前一章我们介绍了线性变换可以把一个向量映射到另一个…

使用Silverlight2的WebClient下载远程图片

在Silverlight 2之前有一个Downloader对象&#xff0c;开发者一般使用Downloader下载图片和文体文件&#xff0c;这个对象在Silverlight 2中作为了一个特性被集成到WebClient类之中&#xff0c;你可以直接使用WebClient的OpenReadAsync方法加载远程图片的URI&#xff0c;然后使…

学习Web前端需要避免哪些错误

很多初学web前端的同学&#xff0c;在学习web前端的时候都会遇到一些错误&#xff0c;虽然有些错误与某一个具体的行为相关&#xff0c;但有些错误却是所有Web开发人员都需要面对的挑战。下面小编就整理一下学习Web前端需要避免哪些错误&#xff0c;希望能够给同学们带来帮助。…

【2012百度之星/资格赛】H:用户请求中的品牌 [后缀数组]

时间限制:1000ms内存限制:65536kB描述馅饼同学是一个在百度工作&#xff0c;做用户请求&#xff08;query&#xff09;分析的同学&#xff0c;他在用户请求中经常会遇到一些很奇葩的词汇。在比方说“johnsonjohnson”、“duckduck”&#xff0c;这些词汇虽然看起来是一些词汇的…

实战:使用Telnet排除网络故障

使用Telnet排除网络故障 如果员工告诉你&#xff0c;他的计算机不能访问网站。你需要断定是他的计算机系统出了问题还是IE浏览器中了恶意插件&#xff0c;或者是网络层面的问题。 如图2-108所示&#xff0c;通过Telnet 服务器的某个端口&#xff0c;就能断定是否访问该服务器的…

线性代数:04 特征值与特征向量 -- 矩阵的相似对角化

本讲义是自己上课所用幻灯片&#xff0c;里面没有详细的推导过程&#xff08;笔者板书推导&#xff09;只以大纲的方式来展示课上的内容&#xff0c;以方便大家下来复习。 本章主要介绍特征值与特征向量的知识&#xff0c;前一章我们介绍了线性变换可以把一个向量映射到另一个…

UI设计培训完之后可以去哪些公司工作

UI设计培训完之后可以去哪些公司工作?这是目前很多学习UI设计或者准备学习UI设计的同学比较关注的一个问题&#xff0c;虽然都知道UI设计的发展前景不错&#xff0c;但是具体学完之后该去哪里工作大家却比较迷茫&#xff0c;来看看下面的详细介绍吧。 UI设计培训完之后可以去哪…

Tomcat详解(下)

配置监听端口 1、编辑配置文件 1234[rootplinuxos ~]# vim /usr/local/tomcat/conf/server.xml <Connector port"80" protocol"HTTP/1.1" ##改成80端口 connectionTimeout"20000" redirectPort"8443" /> 2、重启服务 123456…

线性代数:05 实对称矩阵与二次型

本讲义是自己上课所用幻灯片&#xff0c;里面没有详细的推导过程&#xff08;笔者板书推导&#xff09;只以大纲的方式来展示课上的内容&#xff0c;以方便大家下来复习。 本章是特征值与特征向量知识的延续&#xff0c;根据谱定理可知实对称矩阵可以正交对角化&#xff0c;对…

HDU 2717 Catch That Cow(BFS)

题目链接 好裸&#xff0c;BFS。杭电多组。。2A。。 1 #include <stdio.h>2 #include <string.h>3 int p[100001],o[100001];4 int main()5 {6 int n,k,i,j,start0,end0,num0;7 while(scanf("%d%d",&n,&k)!EOF)8 {9 memset(…