史上最全 结构型模式之 代理 适配器 装饰者 模式-CSDN博客
现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系:

我们可以发现有很多的类,假如我们再增加一个形状或再增加一种颜色,就需要创建更多的类。
试想,在一个有多种可能会变化的维度 系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。
定义:
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接(Bridge)模式包含以下主要角色:
【例】视频播放器
需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。
类图如下:

代码如下:
- //视频文件
- public interface VideoFile {
- void decode(String fileName);
- }
-
- //avi文件
- public class AVIFile implements VideoFile {
- public void decode(String fileName) {
- System.out.println("avi视频文件:"+ fileName);
- }
- }
-
- //rmvb文件
- public class REVBBFile implements VideoFile {
-
- public void decode(String fileName) {
- System.out.println("rmvb文件:" + fileName);
- }
- }
-
- //操作系统版本
- public abstract class OperatingSystemVersion {
-
- protected VideoFile videoFile;
-
- public OperatingSystemVersion(VideoFile videoFile) {
- this.videoFile = videoFile;
- }
-
- public abstract void play(String fileName);
- }
-
- //Windows版本
- public class Windows extends OperatingSystem {
-
- public Windows(VideoFile videoFile) {
- super(videoFile);
- }
-
- public void play(String fileName) {
- videoFile.decode(fileName);
- }
- }
-
- //mac版本
- public class Mac extends OperatingSystemVersion {
-
- public Mac(VideoFile videoFile) {
- super(videoFile);
- }
-
- public void play(String fileName) {
- videoFile.decode(fileName);
- }
- }
-
- //测试类
- public class Client {
- public static void main(String[] args) {
- OperatingSystem os = new Windows(new AVIFile());
- os.play("战狼3");
- }
- }
好处:
如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。
坏处:
适配器模式容易类爆炸 , 每个维度都要创建多个类
装饰是小的包裹大的,桥接是大的包裹小的
有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
定义:
又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口(基金),外部应用程序不用关心内部子系统的具体的细节(投资哪些),这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是“迪米特法则”的典型应用

外观(Facade)模式包含以下主要角色:
【例】智能家电控制
小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:

代码如下:
- //灯类
- public class Light {
- public void on() {
- System.out.println("打开了灯....");
- }
-
- public void off() {
- System.out.println("关闭了灯....");
- }
- }
-
- //电视类
- public class TV {
- public void on() {
- System.out.println("打开了电视....");
- }
-
- public void off() {
- System.out.println("关闭了电视....");
- }
- }
-
- //控制类
- public class AirCondition {
- public void on() {
- System.out.println("打开了空调....");
- }
-
- public void off() {
- System.out.println("关闭了空调....");
- }
- }
-
- //智能音箱 外观类 和用户交互
- public class SmartAppliancesFacade {
- //聚合三个电器
- private Light light;
- private TV tv;
- private AirCondition airCondition;
-
- public SmartAppliancesFacade() {
- light = new Light();
- tv = new TV();
- airCondition = new AirCondition();
- }
-
- public void say(String message) {
- if(message.contains("打开")) {
- on();
- } else if(message.contains("关闭")) {
- off();
- } else {
- System.out.println("我还听不懂你说的!!!");
- }
- }
-
- //起床后一键开电器
- private void on() {
- System.out.println("起床了");
- light.on();
- tv.on();
- airCondition.on();
- }
-
- //睡觉一键关电器
- private void off() {
- System.out.println("睡觉了");
- light.off();
- tv.off();
- airCondition.off();
- }
- }
-
- //测试类
- public class Client {
- public static void main(String[] args) {
- //创建外观对象
- SmartAppliancesFacade facade = new SmartAppliancesFacade();
- //客户端直接与外观对象进行交互
- facade.say("打开家电");
- facade.say("关闭家电");
- }
- }
好处:
缺点:
使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成Servlet Request对象,如下图①处对象。
但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象和ServletRequest共同的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。

RequestFacade类就使用了外观模式。先看结构图:

为什么在此处使用外观模式呢?
定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。

对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象既可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象。
定义:
又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
组合模式主要包含三种角色:
【例】软件菜单
如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。
一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。

要实现该案例,我们先画出类图:

代码实现:
不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。
- //菜单组件 不管是菜单还是菜单项,都应该继承该类
- public abstract class MenuComponent {
-
- protected String name;
- protected int level;
-
- //添加菜单
- public void add(MenuComponent menuComponent){
- throw new UnsupportedOperationException();
- }
-
- //移除菜单
- public void remove(MenuComponent menuComponent){
- throw new UnsupportedOperationException();
- }
-
- //获取指定的子菜单
- public MenuComponent getChild(int i){
- throw new UnsupportedOperationException();
- }
-
- //获取菜单名称
- public String getName(){
- return name;
- }
-
- public void print(){
- throw new UnsupportedOperationException();
- }
- }
这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。
- public class Menu extends MenuComponent {
- //菜单可以有 多个子菜单 或者 子菜单项
- private List
menuComponentList; -
- public Menu(String name,int level){
- this.level = level;
- this.name = name;
- menuComponentList = new ArrayList
(); - }
-
- @Override
- public void add(MenuComponent menuComponent) {
- menuComponentList.add(menuComponent);
- }
-
- @Override
- public void remove(MenuComponent menuComponent) {
- menuComponentList.remove(menuComponent);
- }
-
- @Override
- public MenuComponent getChild(int i) {
- return menuComponentList.get(i);
- }
-
- @Override
- public void print() {
-
- for (int i = 1; i < level; i++) {
- System.out.print("--");
- }
- System.out.println(name);
- for (MenuComponent menuComponent : menuComponentList) {
- menuComponent.print();
- }
- }
- }
Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。
- public class MenuItem extends MenuComponent {
-
- public MenuItem(String name,int level) {
- this.name = name;
- this.level = level;
- }
-
- @Override
- public void print() {
- for (int i = 1; i < level; i++) {
- System.out.print("--");
- }
- System.out.println(name);
- }
- }
MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。
在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 add、remove 、getChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。
安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作。
定义:
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
享元(Flyweight )模式中存在以下两种状态:
享元模式的主要有以下角色:
【例】俄罗斯方块
下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。

先来看类图:

代码如下:
俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
- public abstract class AbstractBox {
- public abstract String getShape();
-
- public void display(String color) {
- System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
- }
- }
接下来就是定义不同的形状了,IBox类、LBox类、OBox类等。
- public class IBox extends AbstractBox {
-
- @Override
- public String getShape() {
- return "I";
- }
- }
-
- public class LBox extends AbstractBox {
-
- @Override
- public String getShape() {
- return "L";
- }
- }
-
- public class OBox extends AbstractBox {
-
- @Override
- public String getShape() {
- return "O";
- }
- }
提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。
- public class BoxFactory {
-
- private static HashMap
map; -
- private BoxFactory() {
- map = new HashMap
(); - AbstractBox iBox = new IBox();
- AbstractBox lBox = new LBox();
- AbstractBox oBox = new OBox();
- map.put("I", iBox);
- map.put("L", lBox);
- map.put("O", oBox);
- }
-
- public static final BoxFactory getInstance() {
- return SingletonHolder.INSTANCE;
- }
-
- private static class SingletonHolder {
- private static final BoxFactory INSTANCE = new BoxFactory();
- }
-
- public AbstractBox getBox(String key) {
- return map.get(key);
- }
- }
1,优点
2,缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
3,使用场景:
Integer类使用了享元模式。我们先看下面的例子:
- public class Demo {
- public static void main(String[] args) {
- Integer i1 = 127;
- Integer i2 = 127;
-
- System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));
-
- Integer i3 = 128;
- Integer i4 = 128;
-
- System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));
- }
- }
运行上面代码,结果如下:

为什么第一个输出语句输出的是true,第二个输出语句输出的是false?通过反编译软件进行反编译,代码如下:
- public class Demo {
- public static void main(String[] args) {
- Integer i1 = Integer.valueOf((int)127);
- Integer i2 Integer.valueOf((int)127);
- System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
- Integer i3 = Integer.valueOf((int)128);
- Integer i4 = Integer.valueOf((int)128);
- System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
- }
- }
上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的是 valueOf() ,所以只需要看该方法即可
- public final class Integer extends Number implements Comparable<Integer> {
-
- public static Integer valueOf(int i) {
- if (i >= IntegerCache.low && i <= IntegerCache.high)
- return IntegerCache.cache[i + (-IntegerCache.low)];
- return new Integer(i);
- }
-
- private static class IntegerCache {
- static final int low = -128;
- static final int high;
- static final Integer cache[];
-
- static {
- int h = 127;
- String integerCacheHighPropValue =
- sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
- if (integerCacheHighPropValue != null) {
- try {
- int i = parseInt(integerCacheHighPropValue);
- i = Math.max(i, 127);
- // Maximum array size is Integer.MAX_VALUE
- h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
- } catch( NumberFormatException nfe) {
- }
- }
- high = h;
- cache = new Integer[(high - low) + 1];
- int j = low;
- for(int k = 0; k < cache.length; k++)
- cache[k] = new Integer(j++);
- // range [-128, 127] must be interned (JLS7 5.1.7)
- assert IntegerCache.high >= 127;
- }
-
- private IntegerCache() {}
- }
- }
可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。
非常感谢您阅读到这里,创作不易!如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 收藏 💕评论💬感谢支持!!!
听说 三连能够给人 带来好运!更有可能年入百w,进入大厂,上岸
