DesignPattern(四)结构型模式(下)

上篇链接   https://www.cnblogs.com/qixinbo/p/9023764.html   

继续介绍最后三种结构型模式


外观模式

       外观模式,也称作 ”门面“模式,在系统中,客户端经常需要与多个子系统进行交互,这样导致客户端会随着子系统的变化而变化,此时可以使用外观模式把客户端与各个子系统解耦。外观模式指的是为子系统中的一组接口提供一个一致的门面,它提供了一个高层接口,这个接口使子系统更加容易使用。如电信的客户专员,你可以让客户专员来完成冲话费,修改套餐等业务,而不需要自己去与各个子系统进行交互。具体类结构图如下所示:

 

在上面的对象图中有两个角色:

门面(Facade)角色:客户端调用这个角色的方法。该角色知道相关的一个或多个子系统的功能和责任,该角色会将从客户端发来的请求委派带相应的子系统中去。

子系统(subsystem)角色:可以同时包含一个或多个子系统。每个子系统都不是一个单独的类,而是一个类的集合。每个子系统都可以被客户端直接调用或被门面角色调用。对于子系统而言,门面仅仅是另外一个客户端,子系统并不知道门面的存在。

  使用了外观模式之后,客户端只依赖与外观类,从而将客户端与子系统的依赖解耦了,如果子系统发生改变,此时客户端的代码并不需要去改变。外观模式的实现核心主要是——由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。然而这样的实现方式非常类似适配器模式,然而外观模式与适配器模式不同的是:适配器模式是将一个对象包装起来以改变其接口,而外观是将一群对象 ”包装“起来以简化其接口。它们的意图是不一样的,适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口。

外观模式示例代码

/// <summary>
/// 以学生选课系统为例子演示外观模式的使用
/// 学生选课模块包括功能有:
/// 验证选课的人数是否已满
/// 通知用户课程选择成功与否
/// 客户端代码
/// </summary>
class Student
{
    private static RegistrationFacade facade = new RegistrationFacade();
    static void Main(string[] args)
    {
        if (facade.RegisterCourse("设计模式", "Learning Hard"))
        {
            Console.WriteLine("选课成功");
        }
        else
        {
            Console.WriteLine("选课失败");
        }
        Console.Read();
    }
}

// 外观类
public class RegistrationFacade
{
    private RegisterCourse registerCourse;
    private NotifyStudent notifyStu;
    public RegistrationFacade()
    {
        registerCourse = new RegisterCourse();
        notifyStu = new NotifyStudent();
    }
    public bool RegisterCourse(string courseName, string studentName)
    {
        if (!registerCourse.CheckAvailable(courseName))
        {
            return false;
        }
        return notifyStu.Notify(studentName);
    }
}

#region 子系统
// 相当于子系统A
public class RegisterCourse
{
    public bool CheckAvailable(string courseName)
    {
        Console.WriteLine("正在验证课程 {0}是否人数已满", courseName);
        return true;
    }
}

// 相当于子系统B
public class NotifyStudent
{
    public bool Notify(string studentName)
    {
        Console.WriteLine("正在向{0}发生通知", studentName);
        return true;
    }
}
#endregion
外观模式示例代码

对应类图:

  

外观模式的优缺点

优点:

A外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。

B外观模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件是紧耦合的。松耦合使得子系统的组件变化不会影响到它的客户。

缺点:

如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。

使用场景

在以下情况下可以考虑使用外观模式:

A为一个复杂的子系统提供一个简单的接口

B提供子系统的独立性

C在层次化结构中,可以使用外观模式定义系统中每一层的入口。其中三层架构就是这样的一个例子。


 

享元模式

       在系统中,如何我们需要重复使用某个对象时,此时如果重复地使用new操作符来创建这个对象的话,这对系统资源是一个极大的浪费,既然每次使用的都是同一个对象,为什么不能对其共享呢?这也是享元模式出现的原因。

       享元模式运用共享的技术有效地支持细粒度的对象,使其进行共享。在.NET类库中,String类的实现就使用了享元模式,String类采用字符串驻留池的来使字符串进行共享。享元模式的具体结构图如下所示。

享元模式示例代码

/// <summary>
/// 客户端调用
/// </summary>
class Client
{
    static void Main(string[] args)
    {
        // 定义外部状态,例如字母的位置等信息
        int externalstate = 10;
        // 初始化享元工厂
        FlyweightFactory factory = new FlyweightFactory();

        // 判断是否已经创建了字母A,如果已经创建就直接使用创建的对象A
        Flyweight fa = factory.GetFlyweight("A");
        if (fa != null)
        {
            // 把外部状态作为享元对象的方法调用参数
            fa.Operation(--externalstate);
        }
        // 判断是否已经创建了字母B
        Flyweight fb = factory.GetFlyweight("B");
        if (fb != null)
        {
            fb.Operation(--externalstate);
        }
        // 判断是否已经创建了字母C
        Flyweight fc = factory.GetFlyweight("C");
        if (fc != null)
        {
            fc.Operation(--externalstate);
        }
        // 判断是否已经创建了字母D
        Flyweight fd= factory.GetFlyweight("D");
        if (fd != null)
        {
            fd.Operation(--externalstate);
        }
        else
        {
            Console.WriteLine("驻留池中不存在字符串D");
            // 这时候就需要创建一个对象并放入驻留池中
            ConcreteFlyweight d = new ConcreteFlyweight("D");
            factory.flyweights.Add("D", d);
        }

        Console.Read();
    }
}

/// <summary>
/// 享元工厂,负责创建和管理享元对象
/// </summary>
public class FlyweightFactory
{
    // 最好使用泛型Dictionary<string,Flyweighy>
    //public Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
    public Hashtable flyweights = new Hashtable();

    public FlyweightFactory()
    {
        flyweights.Add("A", new ConcreteFlyweight("A"));
        flyweights.Add("B", new ConcreteFlyweight("B"));
        flyweights.Add("C", new ConcreteFlyweight("C"));
    }

    public Flyweight GetFlyweight(string key)
    {
// 更好的实现如下
//Flyweight flyweight = flyweights[key] as Flyweight;
//if (flyweight == null)
//{
// Console.WriteLine("驻留池中不存在字符串" + key);
// flyweight = new ConcreteFlyweight(key);
//}
//return flyweight;
return flyweights[key] as Flyweight;
    }
}

/// <summary>
///  抽象享元类,提供具体享元类具有的方法
/// </summary>
public abstract class Flyweight
{
    public abstract void Operation(int extrinsicstate);
}

//具体的享元对象,这样我们不把每个字母设计成一个单独的类了,而是作为把共享的字母作为享元对象的内部状态
public class ConcreteFlyweight : Flyweight
{
    // 内部状态
    private string intrinsicstate ;

    // 构造函数
    public ConcreteFlyweight(string innerState)
    {
        this.intrinsicstate = innerState;
    }

    /// <summary>
    /// 享元类的实例方法
    /// </summary>
    /// <param name="extrinsicstate">外部状态</param>
    public override void Operation(int extrinsicstate)
    {
        Console.WriteLine("具体实现类: intrinsicstate {0}, extrinsicstate {1}", intrinsicstate, extrinsicstate);
    }
}
享元模式示例代码

享元模式的优缺点

享元模式主要用来解决由于大量的细粒度对象所造成的内存开销的问题,它在实际的开发中并不常用,可以作为底层的提升性能的一种手段。

优点:

降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点:

为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑更复杂,使系统复杂化。

享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

使用场景

在下面所有条件都满足时,可以考虑使用享元模式:

A一个系统中有大量的对象;

B这些对象耗费大量的内存;

C这些对象中的状态大部分都可以被外部化

D这些对象可以按照内部状态分成很多的组,当把外部对象从对象中剔除时,每一个组都可以仅用一个对象代替

E软件系统不依赖这些对象的身份,

       满足上面的条件的系统可以使用享元模式。但是使用享元模式需要额外维护一个记录子系统已有的所有享元的表,而这也需要耗费资源,所以,应当在有足够多的享元实例可共享时才值得使用享元模式。

应用举例

在.NET类库中,string类的实现就使用了享元模式


代理模式

       在系统开发中,有些对象由于网络或其他的障碍,以至于不能直接对其访问,此时可以通过一个代理对象来实现对目标对象的访问。如.NET中的调用Web服务等操作。

       代理模式指的是给某一个对象提供一个代理,并由代理对象控制对原对象的访问。具体的结构图如下所示。

 

代理模式代码示例

//以海外代购为例
// 客户端调用
class Client
{
    static void Main(string[] args)
    {
        // 创建一个代理对象并发出请求
        Person proxy = new Friend();
        proxy.BuyProduct();
        Console.Read();
    }
}
// 抽象主题角色
public abstract class Person
{
    public abstract void BuyProduct();
}
//真实主题角色
public class RealBuyPerson : Person
{
    public override void BuyProduct()
    {
        Console.WriteLine("帮我买一个IPhone和一台苹果电脑");
    }
}
// 代理角色
public class Friend:Person
{
    // 引用真实主题实例
    RealBuyPerson realSubject;
    public override void BuyProduct()
    {
        Console.WriteLine("通过代理类访问真实实体对象的方法");
        if (realSubject == null)
        {
            realSubject = new RealBuyPerson();
        }
        this.PreBuyProduct();
        // 调用真实主题方法
        realSubject.BuyProduct();
        this.PostBuyProduct();
    }

    // 代理角色执行的一些操作
    public void PreBuyProduct()
    {
        // 可能不知一个朋友叫这位朋友带东西,首先这位出国的朋友要对每一位朋友要带的东西列一个清单等
        Console.WriteLine("我怕弄糊涂了,需要列一张清单,张三:要带相机,李四:要带Iphone...........");
    }
    
    // 买完东西之后,代理角色需要针对每位朋友需要的对买来的东西进行分类
    public void PostBuyProduct()
    {
        Console.WriteLine("终于买完了,现在要对东西分一下,相机是张三的;Iphone是李四的..........");
    }
}
代理模式代码示例

类图

 

在上面类图中,代理模式所涉及的角色有三个:

抽象主题角色(Person):声明了真实主题和代理主题的公共接口,这样一来在使用真实主题的任何地方都可以使用代理主题。

代理主题角色(Friend):代理主题角色内部含有对真实主题的引用,从而可以操作真实主题对象;代理主题角色负责在需要的时候创建真实主题对象;代理角色通常在将客户端调用传递到真实主题之前或之后,都要执行一些其他的操作,而不是单纯地将调用传递给真实主题对象。例如这里的PreBuyProduct和PostBuyProduct方法就是代理主题角色所执行的其他操作。

真实主题角色(RealBuyPerson):定义了代理角色所代表的真是对象。

附:在实际开发过程中,我们在客户端添加服务引用的时候,在客户程序中会添加一些额外的类,在客户端生成的类扮演着代理主题角色,我们客户端也是直接调用这些代理角色来访问远程服务提供的操作。这个是远程代理的一个典型例子。

代理模式的优缺点

全面分析完代理模式之后,让我们看看这个模式的优缺点:

优点:

代理模式能够将调用用于真正被调用的对象隔离,在一定程度上降低了系统的耦合度;

代理对象在客户端和目标对象之间起到一个中介的作用,这样可以起到对目标对象的保护。代理对象可以在对目标对象发出请求之前进行一个额外的操作,例如权限检查等。

缺点:

由于在客户端和真实主题之间增加了一个代理对象,所以会造成请求的处理速度变慢

实现代理类也需要额外的工作,从而增加了系统的实现复杂度。

注:外观模式、适配器模式和代理模式区别?

       解答:这三个模式的相同之处是,它们都是作为客户端与真实被使用的类或系统之间的一个中间层,起到让客户端间接调用真实类的作用,不同之处在于,所应用的场合和意图不同。

       代理模式与外观模式主要区别在于,代理对象无法直接访问对象,只能由代理对象提供访问,而外观对象提供对各个子系统简化访问调用接口,而适配器模式则不需要虚构一个代理者,目的是复用原有的接口。外观模式是定义新的接口,而适配器则是复用一个原有的接口。

       另外,它们应用设计的不同阶段,外观模式用于设计的前期,因为系统需要前期就需要依赖于外观,而适配器应用于设计完成之后,当发现设计完成的类无法协同工作时,可以采用适配器模式。然而很多情况下在设计初期就要考虑适配器模式的使用,如涉及到大量第三方应用接口的情况;代理模式是模式完成后,想以服务的方式提供给其他客户端进行调用,此时其他客户端可以使用代理模式来对模块进行访问。

       总之,代理模式提供与真实类一致的接口,旨在用来代理类来访问真实的类,外观模式旨在简化接口,适配器模式旨在转换接口。

原文地址:https://www.cnblogs.com/qixinbo/p/9062303.html