《大话设计模式》——学习笔记之”结构型模式”(适配器&装饰&桥接&组合&亨元&代理&外观)
适配器模式
定义:适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
Target,客户所期望的接口
class Target{
public virtual void Request(){
Console.WriteLine("普通请求!");
}
}
Adaptee,需要适配的类
class Adaptee{
public void SpecificRequest(){
Console.WriteLine("特殊请求!");
}
}
Adapter,通过在内部包装一个Adapter对象,把源接口转换成目标接口
class Adapter : Target{
//建立一个私有的Adaptee对象
private Adaptee adaptee = new Adaptee();
public override void Request(){
//把表面上调用Request()方法变成实际调用SpecificRequest()
adaptee.SpecificRequest();
}
}
客户端代码:
static void Main(string[] args){
Target target = new Adapter();
//对客户端来说,调用的就是Target的Request()
target.Request();
Console.Read();
}
优点:
- 系统的数据和行为都正确,但接口不符时,应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配,适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况
- 两个类所做的事情相同或相似,但是具有不同的接口时要使用它,让接口不同的类通过适配后,协同工作
- 适配器不需要虚构出一个代表者,只需要为应付特定使用目的,将原来的类进行一些组合
- 主要是为了解决已有接口之间不匹配的问题,不需要考虑这些接口是怎样实现的,也不考虑它们各自可能会如何演化,不需要对两个独立设计的类中任一个进行重新设计,就能够使它们协同工作
装饰模式
定义: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活
Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的,至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能
Component类
abstract class Component{
public abstract void Operation();
}
ConcreteComponent类
class ConcreteComponent : Component{
public override void Operation(){
Console.WriteLine("具体对象的操作");
}
}
Decorator类
abstract class Decorator : Component{
protected Component component;
//装饰模式是利用SetComponent来对对象进行包装的,每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关系如何被添加到对象链当中
public void SetComponent(Component component){
this.component = component;//设置Component
}
//重写Operation(),实际执行的是Component的Operation()
public override void Operation(){
if(component != null){
component.Operation();
}
}
}
//如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类
ConcreteDecoratorA 类
class ConcreteDecoratorA : Decorator{
//本类的独有功能,以区别于ConcreteDecoratorB
private string addedState;
public override void Operation(){
//首先运行原Component的Operation(),再执行本类的功能,如addState,相当于对原Component进行了装饰
base.Operation();
addedState = "New State";
Console.WriteLine("具体装饰对象A的操作");
}
}
ConcreteDecoratorB 类
class ConcreteDecoratorB : Decorator{
private string addedState;
public override void Operation(){
//首先运行原Component的Operation(),再执行本类的功能,如addState,相当于对原Component进行了装饰
base.Operation();
AddedBehavior();
Console.WriteLine("具体装饰对象B的操作");
}
private void AddedBehavior(){
//本类独有的方法,以区别于ConcreteDecoratorA
}
}
客户端代码
static void Main(string[] args){
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB()
d1.SetComponent(c);
d2.SetComponent(d1);
//装饰的方法是:首先用ConcreteComponent实例化对象c,然后用ConcreteDecoratorA的实例化对象d1来包装c,再用ConcreteDecoratorB的对象d2来包装d1,最终执行d2的Operation()
d2.Operation();
Console.Read();
}
优点:
- 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更加灵活
- 子类多半只是为了某个对象增加一些职责,此时通过装饰的方式,可以更加灵活、以动态、透明的方式给单个对象添加职责,并在不需要时,撤销相应的职责
- 装饰模式是为已有功能动态地添加更多功能的一种方式
- 当系统需要新功能的时候,是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要时,装饰模式提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了
- 装饰模式的优点就是把类中的装饰功能从类中搬移去除,有效地把类的核心职责和装饰功能区分开了,而且可以去除相关类中重复的装饰逻辑
桥接模式
定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化
Implementor类
abstract class Implementor{
public abstract void Operation();
}
ConcreteImplementorA和ConcreteImplementorB等派生类
class ConcreteImplementorA : Implementor{
public override void Operation(){
Console.WriteLine("具体实现A的方法执行");
}
}
class ConcreteImplementorB : Implementor{
public override void Operation(){
Console.WriteLine("具体实现B的方法执行");
}
}
Abstraction类
class Abstraction{
protected Implementor implementor;
public void SetImplementor(Implementor implementor){
this.implementor = implementor;
}
public virtual void Operation(){
implementor.Operation();
}
}
RefinedAbstraction类
class RefinedAbstraction : Abstraction{
public override void Operation(){
implementor.Operation();
}
}
客户端实现
static void Main(string[] args){
Abstraction ab = new RefinedAbstraction();
ab.SetImplementor(new ConcreteImplementorA());
ab.Operation();
ab.SetImplementor(new ConcreteImplementorB());
ab.Operation();
Console.Read();
}
优点:
- 实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合
- 将抽象部分与它的实现部分分离,使它们都可以独立地变化
- 用聚合来代替继承,在继承体系中,有两个甚至多个方向的变化,就解耦这些不同方向的变化,通过对象组合的方式,把两个角色之间的继承关系改为组合的关系,从而使这两者可以应对各自独立的变化
- 在设计之初,就对抽象接口与它的实现部分进行桥接,让抽象与实现两者可以独立演化
组合模式
定义: 组合模式(Composite),将对象组合成树形结构以表示’部分-整体’的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性
Component为组合中的对象声明接口,在适当情况下,实现所有类共有接口的默认行为,声明一个接口用于访问和管理Component的子部件
abstract class Component{
protected string name;
public Component(string name){
this.name = name;
}
//通常用Add和Remove方法来提供增加或移除树叶或树枝的功能
public abstract void Add(Component c);
public abstract void Remove(Component c);
public abstract void Display(int depth);
}
Leaf在组合中表示叶节点对象,叶节点没有子节点
class Leaf : Component{
public Leaf(string name):base(name){
}
public override void Add(Component c){
Console.WriteLine("Cannot add to a leaf");
}
public override void Remove(Component c){
Console.WriteLine("Cannot remove from a leaf");
}
public override void Display(int depth){
//叶节点的具体方法
Console.WriteLine(new String("-", depth) + name);
}
}
Composite定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关的操作,比如增加Add和删除Remove
class Composite : Component{
//一个子对象集合用来存储其下属的枝节点和叶节点
private List<Component> children = new List<Component>();
public Composite(string name):base(name){
}
public override void Add(Component c){
children.Add(c);
}
public override void Remove(Component c){
children.Remove(c);
}
//显示其枝节点名称,并对其下级进行遍历
public override void Display(int depth){
Console.WriteLine(new String('-', depth) + name);
foreach(Component component in children){
component.Display(depth + 2);
}
}
}
客户端代码,通过Component接口操作组合部件的对象
static void Main(string[] args){
//生成树根root,根上长出两叶LeafA和LeafB
Composite root = new Composite("root");
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));
//根上长出分枝Composite X,分枝上也有两叶Leaf XA和Leaf XB
Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
root.Add(comp);
//根上长出分枝Composite XY,分枝上也有两叶Leaf XYA和Leaf XYB
Composite comp2 = new Composite("Composite XY");
comp.Add(new Leaf("Leaf XYA"));
comp.Add(new Leaf("Leaf XYB"));
root.Add(comp2);
//显示
root.Display(1);
Console.Read();
}
优点:
- 忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式
- 将对象组合成树形结构以表示”部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性
- 客户可以一致地使用组合结构和单个对象,任何用到基本对象的地方都可以使用组合对象(用户使用组合类接口与组合结构中的对象进行交互,如果接收者是一个叶节点,则直接处理请求,如果接收者是组合对象,通常将请求发它的子部件,并在转发请求之前或之后可能执行一些辅助操作)
亨元模式
定义: 运用共享技术有效地支持大量细粒度的对象
Flyweight类,它是所有具体亨元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态
abstract class Flyweight{
public abstract void Operation(int extrinsicstate);
}
ConcreteFlyweight是继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间
class ConcreteFlyweight : Flyweight{
public override void Operation(int extrinsicstate){
Console.WriteLine("具体extrinsicstate:" + extrinsicstate);
}
}
UnsharedConcreteFlyweight是指那些不需要共享的Flyweight子类,因为Flyweight接口共享成为可能,但它并不强制共享
class UnsharedConcreteFlyweight : Flyweight{
public override void Operation(int extrinsicstate){
Console.WriteLine("不共享的具体Flyweight:" + extrinsicstate);
}
}
FlyweightFactory是一个亨元工厂,用来创建并管理Flyweight对象,它主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)
class FlyweightFactory{
private Hashtable flyweights = new Hashtable();
//初始化工厂时,先生成三个实例
public FlyweightFactory(){
flyweights.Add("X", new ConcreteFlyweight());
flyweights.Add("Y", new ConcreteFlyweight());
flyweights.Add("Z", new ConcreteFlyweight());
}
public Flyweight GetFlyweight(string key){
//根据客户端请求,获得已生成的实例
return ((Flyweight)flyweights[key]);
}
}
客户端代码
static void Main(string[] args){
//代码外部状态
int extrinsicstate = 22;
FlyweightFactory f = new FlyweightFactory();
Flyweight fx = f.GetFlyweight("X");
fx.Operation(--extrinsicstate);
Flyweight fy = f.GetFlyweight("Y");
fy.Operation(--extrinsicstate);
Flyweight fz = f.GetFlyweight("Z");
fz.Operation(--extrinsicstate);
UnsharedConcreteFlyweight uf = new UnsharedConcreteFlyweight();
uf.Operation(--extrinsicstate);
Console.Read();
}
优点:
- 如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用亨元模式,大量重复的对象是对资源的极大浪费
- 亨元模式可以避免大量非常相似类的开销,在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够大幅度地减少需要实例化的类的数量,如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目
- 亨元模式Flyweight执行时所需的状态是有内部的,也可能有外部的,内部状态存储于ConcreteFlyweight对象中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它
- 当对象的大多数状态是外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用亨元模式
代理模式
定义: 为其他对象提供一种代理以控制对这个对象的访问
Subject类,定义了RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy
abstract class Subject{
public abstract void Request();
}
RealSubject类,定义Proxy所代表的真实实体
class RealSubject : Subject{
public override void Request(){
Console.WriteLine("真实的请求");
}
}
Proxy类,保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体
class Proxy : Subject{
RealSubject realSubject;
public override void Request(){
if(realSubject == null){
realSubject = new RealSubject();
}
realSubject.Request();
}
}
客户端代码
static void Main(string[] args){
Proxy proxy = new Proxy();
proxy.Request();
Console.Read();
}
优点:
- 为其他对象提供一种代理以控制对这个对象的访问
- 代理对象代表一个单一对象,代理的客户对象无法直接访问目标对象,由代理提供对单独的目标对象的访问控制
- 远程代理:为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实
- 虚拟代理:根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象(如HTML网页通过虚拟代理来替代了真实的图片,此时代理存储了真实图片的路径和尺寸)
- 安全代理:用来控制真实对象访问时的权限
- 智能指引:指当调用真实的对象时,代理处理另外一些事(如计算真实对象的引用次数)
外观模式
定义: 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
子系统类
class SubSystemOne{
public void MethodOne(){
Console.WriteLine("子系统方法一");
}
}
class SubSystemTwo{
public void MethodTwo(){
Console.WriteLine("子系统方法二");
}
}
...
外观类
class Facade{
SubSystemOne one;
SubSystemTwo two;
public Facade(){
one = new SubSystemOne();
two = new SubSystemTwo();
}
public void MethodA(){
Console.WriteLine("\n方法组A() --- ");
one.MethodOne();
two.MethodTwo();
}
public void MethodB(){
Console.WriteLine("\n方法组B() --- ");
two.MethodTwo();
}
}
客户端调用
static void Main(string[] args){
Facade facade = new Facade();
//由于Facade的作用,客户端可以根本不知三个子系统类的存在
facade.MethodA();
facade.MethodB();
Console.Read();
}
优点:
- 如果两个类不必彼此直接通信,那么就不要让这两个类发生直接的相互作用,如果实在需要调用,可以通过第三者来转发调用,类之间的耦合越弱,越有利于复用
- 引入一个外观对象,为子系统间提供了一个单一而简单的屏障,可以让子系统间的通信和相互依赖关系达到最小(软件架构中,层与层之间地分离其实就是外观模式的体现)
- 外观对象代表一个子系统,提供对子系统各元件功能的简化的共同层次的调用接口
- 在设计初期阶段,应该要有意识的将不同的两个层分离,层与层之间建立外观,为复杂的子系统提供一个简单的接口,使得耦合大大降低
- 其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂(很多很小的类),增加外观Facade可以提供一个简单的接口,减少它们之间的依赖
- 第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作