结构型模式描述如何组织类和对象以组成更大的结构。
结构型类模式采用继承机制来组合接口和实现,
结构型对象模式则采用组合聚合来组合对象以实现新功能,可以在运行时刻改变对象组合关系,具有更大灵活性,故这里只关注结构型对象模式。
一般常见的结构型模式有7种:ABCDFFP(Adapter,Bridge,Composite,Decorator,Façade,Flyweight,Proxy)
1.适配器模式(Adapter):使由于接口不兼容而不能一起工作的类可以一起工作。
有类或对象适配器模式,解决已存在目标客户接口和已存在功能类接口不兼容的问题。
对象适配器模式实现上是适配器对象包含一个被适配器对象来进行接口转换;
一般是项目改造或扩充时才用到。适配器模式使得一个接口和其它接口兼容,从而给出了多个不同接口的统一抽象。
协作:Client在Adapter的实例上调用一些操作,适配器调用含有的Adaptee对象实现这个操作。
2.桥接模式(Bridge):将抽象和实现分离,使他们可以独立的变化。
分离接口和实现,从而使抽象类的实现可以在运行时刻配置,在运行时刻改变其对象的实现;
接口实现分离有利于系统分层,从而产生跟好的结构化系统,可以独立的对Abstractation和Implementor层次结构进行扩充。
封装多维变化,抽象角色内含一个抽象实现角色,精确抽象角色调用父类中的抽象实现角色对象实现,具体实现角色继承抽象实现角色,如果再增加一维抽象,可以当抽象实现角色,在原有的抽象实现角色中包含新增的抽象实现角色,也可以当成新的抽象角色,原有的抽象角色作为一个抽象实现角色;这样说该模式实现时从抽象实现角色这维变化最好。
实例:不同人开不同车在不同路上行驶;不同名牌手机有不同系统不同软件功能;
协作:Abstraction将Client的请求转发给它的Implementor对象。
3.组合模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
即组合模式定义树状结构,可以将子对象当成整体对象一样处理;抽象构件对象,树枝构件对象,树叶构件对象,根据叶子是否当成枝干来看待可分为透明性和安全性两种模式。
组合模式必须提供管理子对象的方法,但管理对象的方法是在Component中就声明还是在Composite中声明呢?如下图在Component中声明所有用来管理子类对象的方法,以达到Component接口的最大化,从而使客户在接口层次上看树叶和树枝没有区别——透明性,但树叶是不存在子类的,可能有安全问题。
下图只在Composite中声明所有管理子类的方法,避免了上面的安全性问题,但是树枝和叶子又不同的接口,失去了透明性。
协作:用户使用Component类接口与组合结构中的对象交互,如果接收者是一个叶节点,则直接处理请求,如果接受者是Composite,它通常将请求发送给它的子部件,再转发请求之前或之后可能执行一些辅助操作。
实例:java单元测试框架Junit定义了两个概念:TestCase和TestSuite,前者是一个编写测试的类,后者是一个不同TestCase的集合,集合里也可以再包含TestCase,这样运行一个TestSuite会将其包含的TestCase全部运行。Junit采用了组合模式将TestCase和TestSuite统一起来,创建一个Test接口来扮演Component角色,TestCase扮演Leaf角色,Testsuite扮演Composite角色。Junit是一个偏重安全性的组合模式,因此用TestCase和Testsuite时不能直接用Test代替。
4.装饰者模式(Decorator):动态的给对象增加一些职责,比生成子类更灵活。装饰者模式描述了如何动态的给对象增加职责,采用递归方式组合对象从而允许你添加任意多的职责。
参与者:a抽象构件角色(Component):定义抽象接口,以规范准备接收附加职责的对象。
b具体构件角色(ConcreteComponent):被装饰者。
c装饰角色(Decorator):持有一个构件对象实例,并定义了抽象构件定义的接口。
d具体装饰角色(ConcreteDecorator):负责给构件增加功能。
协作:Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。
实例:Junit采用了装饰模式,在java.extensions包中,TestDecorator、RepeatedTest便是对TestCase的装饰模式的扩展。使用方法如:TestDecorator test=new RepeatedTest(new TestXX(),3);。
5.外观模式(Facade):为子系统中的一组接口提供一个一致的界面,使这一子系统更加容易使用。
外观模式用于给多个子系统定义一套统一的外观,描述了如何用单个对象表示整个子系统。Façade被客户角色调用,熟悉子系统的功能,内部根据客户角色已有的需求预定了几种功能组合。
协作:客户程序通过发送请求给Façade的方式与子系统通信,Façade将这些消息转发给适当的子系统对象;使用Façade的客户程序不需要直接访问子系统对象。
6.亨元模式:运用共享技术有效的支持大量细粒度对象。
java中string的实现,细化对象共享粒度;
参与者:a.Flyweight:描述一个接口可以接受并作用于外部状态。
b.ConcreteFlyweight:实现Flyweight并为内部状态增加存储空间。ConcreteFlyweight对象必须是可共享的,它所存储的状态必须是内部的。
c.unsharedConcreteFlyweight:并非所有Flyweight子类都需要被共享,unsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。
d.FlyweightFactory:创建并管理flyweight对象,确保合理的共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(不存在的话)。
e.维持一个对flyweight的引用,计算或存储flyweight的外部状态。
协作:flyweight执行所需的状态必定是内部的或外部的,内部的状态存储与ConcreteFlyweight对象之中,而外部对象由Client对象存储或计算。当用户调用flyweight操作时将该状态传递给它。用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这样可以保证它们适当地进行共享。
7.代理模式(Proxy):为其它对象提供一种代理以控制对这种对象的访问。
代理模式提供了对对象的一些特有性质的一定程度上的间接访问,从而限制、增强或修改这些性质。
协作:代理根据其种类,在适当的时候向RealSubsject转发请求。
常见使用代理模式的情况:a远程代理:为一个对象在不同的地址空间提供局部代表。
b.虚代理:根据需要创建开销很大的对象。
c.保护代理:控制对原始对象的访问权限。
d.取代了简单的指针,它在访问对象时执行一些附加操作。
结构模式(Structural Pattern)
描述如何将类或者对象结合在一起形成更大的结构。结构模式描述两种不同的东西:类与类的实例。根据这一点,结构模式可以分为类的结构模式和对象的结构模式。
具体的集中结构模式如下所示:
Structural Patterns | |
Adapter | Match interfaces of different classes |
Bridge | Separates an object’s interface from its implementation |
Composite | A tree structure of simple and composite objects |
Decorator | Add responsibilities to objects dynamically |
Facade | A single class that represents an entire subsystem |
Flyweight | A fine-grained instance used for efficient sharing |
Proxy | An object representing another object |
概述:
结构型模式,顾名思义讨论的是类和对象的结构,它采用继承机制来组合接口或实现(类结构型模式),或者通过组合一些对象,从而实现新的功能(对象结构型模式)。这些结构型模式,它们在某些方面具有很大的相似性,仔细推敲,侧重点却各有不同。
Adapter模式通过类的继承或者对象的组合侧重于转换已有的接口;
Bridge模式通过将抽象和实现相分离,让它们可以分别独立的变化,它强调的是系统沿着多个方向的变化;
Decorator模式采用对象组合而非继承的手法,实现了在运行时动态的扩展对象功能的能力,它强调的是扩展接口;
Composite模式模糊了简单元素和复杂元素的概念,它强调的是一种类层次式的结构;Fa?ade 模式将复杂系统的内部子系统与客户程序之间的依赖解耦,它侧重于简化接口,更多的是一种架构模式;
Flyweight模式解决的是由于大量的细粒度对象所造成的内存开销的问题,它与Fa?ade模式恰好相反,关注的重点是细小的对象;
Proxy模式为其他对象提供一种代理以控制对这个对象的访问,它注重于增加间接层来简化复杂的问题。
结构型模式的区别与比较
1.桥接模式与装饰模式
这两个模式在一定程度上都是为了减少子类的数目,避免出现复杂的继承关系。但是它们解决的方法却各有不同,装饰模式把子类中比基类中多出来的部分放到单独的类里面,以适应新功能增加的需要,当我们把描述新功能的类封装到基类的对象里面时,就得到了所需要的子类对象,这些描述新功能的类通过组合可以实现很多的功能组合,装饰模式的简略图如下:
桥接模式则把原来的基类的实现化细节抽象出来,在构造到一个实现化的结构中,然后再把原来的基类改造成一个抽象化的等级结构,这样就可以实现系统在多个维度上的独立变化,桥接模式的简略图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/24144c794b3cf1c70fc26498e6f06add.jpeg)
2.外观模式和代理模式
外观模式和代理模式解决问题的侧重点不同,但是它们解决问题的手法却是一样的,即都是引入了间接层的手法,这也是我们软件系统中经常用的一种手法。外观模式虽然侧重于简化接口,但是在某些情况下,外观模式也可以兼任代理模式的责任,例如外观对象有可能是另一个位于另一个地址空间对象的远程代理,这时候我们可以叫做外观代理模式,或者代理外观模式。它们的类简略图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/c7c92c90928122dd67d01298bc6674b2.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/467742c66169d2194aae5cfa7a484484.jpeg)
3.适配器模式
适配器模式重在转换接口,它能够使原本不能在一起工作的两个类一起工作,所以经常用在类库复用,代码迁移等方面,有一种亡羊补牢的味道。类适配器和对象适配器可以根据具体实际情况来选用,但一般情况建议使用对象适配器模式,如下图所示,左边是类适配器模式,右边是对象适配器模式:
![](https://i-blog.csdnimg.cn/blog_migrate/a3a2ce560f1b551de7ab0aa517bd6852.jpeg)
对变化的封装
如何应对变化,是软件开发的一个永恒的主题,也许我们不能够杜绝变化的发生,但至少我们可以通过一些手段让变化降到最低。“找到系统可变的因素,将之封装起来”,通常就叫做对变化的封装。关于这个问题的解释在《Java与模式》中讲的很清晰,抽象化与实现化的简单实现,也就是“开-闭”原则在类层次上的最简单实现,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/a5895d5486460167bd01ffb21d4f4a0c.jpeg)
在这个继承结构中,第一层是抽象化,它封装了抽象的业务逻辑,这是系统中不变的部分;第二层是实现化,它是具体的业务逻辑的实现,封装了系统中变化的部分,这个实现允许实现化角色多态性的变化:
![](https://i-blog.csdnimg.cn/blog_migrate/775d59b8970d1d690ffd193def6af284.jpeg)
也就是说,客户端依赖的是业务逻辑的抽象化类型的对象,而与抽象化的具体实现无关,不在乎它到底是“实现化”,“实现化2”还是“实现化3”,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/3ce0b20a4931ce0b25dbc7b8ce53a93a.jpeg)
每一种继承关系都封装了一个变化因素,而一个继承关系不应当处理两个变化因素,换言之,这种简单继承关系不能处理抽象化与实现化都变化的情况,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/a6876939c447c6930e901bc4a028d75b.jpeg)
上图中的两个变化因素应当是独立的,可以在不影响另一者的情况下独立的变化,如下面这两个等级结构分别封装了自己的变化因素,由于每一个变化因素都是可以通过静态关系表达的,因此分别使用继承关系实现,如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/e1236cfaafca99b68e7d4bf216976be9.jpeg)
在抽象化和实现化之间的联系怎么办呢?好的设计只有一个,不好的设计却有很多中,下面这种设计就是继续使用继承进行静态关系设计的类图:
![](https://i-blog.csdnimg.cn/blog_migrate/019da581f94e90424cfc5dfd6e0ee38d.jpeg)
这样的设计其实存在着很多的问题,首先出现的是多重的继承关系,随着具体实现化的增多,子类的继承关系会变得异常复杂;其次如果出现新的抽象化修正或者新的具体实现角色,就只好重新修改现有系统中的静态关系,以适应新的角色,这就违背了开放-封闭原则。正确是设计应该是使用两个独立的等级结构封装两个独立的变化因素,并在它们之间使用聚合关系,以达到功能复用的目的,这就回到了我们的桥接模式上,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/3ab917fb42d5791b5bf52e0f778ffd2d.jpeg)
从另一个角度讲,一个好的设计通常没有多于两层的继承等级结构,或者说,如果出现两个以上的变化因素,就需要找出哪一个因素是静态的,可以使用静态关系,哪一个是动态的,必须使用聚合关系。