设计模式之结构型模式(适配器、外观、组合、桥接、装饰、代理、享元模式)

设计模式之结构型模式

结构型模式:
如何将类与类或者对象与对象或者类与对象结合在一起形成更大的结构
因此可以分为:
类结构型模式: 由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系

对象结构型模式: 关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法
并且符合合成复用原则,减少继承关系的使用

因此大部分结构型模式为 对象结构型模式

结构型模式分类:
1 适配器模式
2 外观模式
3 组合模式
4 桥接模式
5 装饰模式
6 代理模式
7 享元模式


适配器模式

定义: 当客户类想获取目标类的某些服务,但是目标类提供的接口可能客户类并不能使用,在这种情况下,就要通过适配器,定义一个包装类,包装不兼容接口的对象。
简单的说就是;使接口不兼容的那些类可以一起工作
封装插头适配插座
过程: 当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。


适配器模式UML类图示
实例:
设计一个可以模拟各种动物行为的机器人,在机器人中定义了一系列方法,如机器人叫喊方法cry()、机器人移动方法move()等。如果希望在不修改已有代码的基础上使得机器人能够像狗一样叫,像狗一样跑
这里写图片描述

优点:
1 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无需修改原有代码。
2 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
3 灵活性和扩展性好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”

类适配器模式还具有如下优点
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强
对象适配器模式还具有如下优点:
一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口

缺点:
类适配器模式的缺点:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口

对象适配器模式的缺点如下:
与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配

UML类图表示:
类适配器

这里写图片描述

对象适配器
这里写图片描述

适配器模式适用场景:
1 系统需要使用现有的类,而这些类的接口不符合系统的需要。
2 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作


组合模式

定义:
组合模式(Composite Pattern):组合多个对象形成树形结构以表示“整体—部分”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性
直白定义: 即文件目录原理 文件目录可以有文件夹(容器)和文件(叶子)

组合模式适用场景
让客户能够忽略不同对象层次的变化,可以通过一种方式忽略整体与部分的差异,一致地对待它们
常用如 文件树,java的component组件

接口定义:

abstract class Component {
    public abstract void operation();
    public void add(Component c) {
	throw new UnsupportedOperationException();
    }
    public void remove(Component c) {
	throw new UnsupportedOperationException();
    }
    public Component getChild(int i) {
	throw new UnsupportedOperationException();
    }
}

模式动机:
组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无需对它们进行区分,可以一致地对待容器对象和叶子对象

UML图示
这里写图片描述
Component: 抽象构件 (一般为抽象类)
Leaf: 叶子构件
Composite: 容器构件
Client: 客户类
定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无需知道它到底表示的是叶子还是容器,可以对其进行统一处理。
同时容器对象与抽象构件类之间还建立一个聚合关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构

组合模式的优点:
1 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
2 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
3 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码

组合模式缺点:
1 设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联
2 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则


外观模式(对象结构型模式)

让外部与一个子系统的通信通过一个统一的外观对象进行,该对象再去调用各个子系统。并不改变原有逻辑,只是封装出一些接口让调用者更清晰

**模式动机:**降低系统的复杂程度,将客户与复杂的系统分隔开,提高客户使用的便捷性

UML图示
这里写图片描述
结构:
Facade: 外观角色
SubSystem:子系统角色

外观模式优点
1 降低了子系统与客户的耦合度。客户不需要处理太多的子系统对象。子系统的改变也不会影响客户端,只需要改动外观对象即可
2 满足单一职责原则。将一个系统划分为若干个子系统有利于降低整个系统的复杂性,使每个类都只负责自己的职责
3 满足迪米特法则。通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度
外观模式缺点:
1 如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
2 增加新的子系统可能需要修改外观类的源代码,违背了“开闭原则”


代理模式

主要角色:
代理类(代理对象) 委托类(委托对象、目标对象、真实对象) 接口(抽象对象)

静态代理

1 接口:定义了代理对象和委托对象的共同接口方法
2 委托类(委托对象、目标对象、真实对象): 实现接口,并且做实际的操作
3 代理类(代理对象):实现接口,构造方法接收真实对象的对象
然后在实现的接口方法中做操作,并且调用真实对象的方法
4 外部调用者创建代理对象,调用相应方法,而不能直接创建/访问委托对象

静态代理优点: 保持业务逻辑,可以不用修改委托类的代码
静态代理缺点:
1 要增加方法,则要改接口,改委托类和代理类
2 要委托多个事情 得创建相应的接口和代理类,而动态代理不需要根据接口提前定义代理类,它把代理类的创建推迟到代码运行时来完成

静态代理场景:
1 当某个对象不想被直接访问,或者访问这个对象困难时,可以通过代理类来访问
2 当某个对象存在针对不同调用方有不同的访问权限时 可以通过代理类来访问
3 在访问原始对象时执行一些自己的附加操作
为什么不直接去创建真实类调用方法前后进行操作:
因为这样随着业务庞大,代码容易冗余 混乱,实现proxy类对真实类的封装对于粒度的控制有着重要的意义

动态代理

jdk方式 利用java提供的 < InvocationHandler >和 < Proxy > 方法
运用了反射的原理
动态创建代理对象 不用代码写死 更灵活

动态代理应用场景:
1、定义代理对象和真实对象的公共接口(与静态代理步骤相同)
2、真实对象实现公共接口中的方法(与静态代理步骤相同)
3、定义一个实现了InvocationHandler接口的动态代理类

// 动态代理接口类
public interface InvocationHandler {
  Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

// 动态代理壳 动态代理壳对象实现invoke方法 利用反射原理去调用真实对象的方法
// 也是类似静态代理 持有真实对象的引用 只是通过外部传入 并且是反射的方法(这样就可以代理不同的真实对象了)
public class DynamicProxy implements InvocationHandler {
    private Object mObject;
    
    // 真实对象通过构造函数传入
    public DynamicProxy(Object object){
        this.mObject = object;
    }

   /**
   * 代理对象proxy要调用真实对象的method
   * @param proxy 代理对象
   * @param method 真实对象被调用的方法
   * @param args 真实对象被调用的方法的参数
   */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // do sth
        //通过反射调用真实对象的方法
        Object result = method.invoke(mObject, args);
         // do sth
        return result;
    }
}

4、通过Proxy类的newProxyInstance方法创建代理对象,调用代理对象的方法

// 由具体子类继承该类 主要用来动态地创建代理对象
public class Proxy implements Serializable {

    //持有一个InvocationHandler的引用
    protected InvocationHandler invocationHandler;
    
    // 在构造函数中传入InvocationHandler对象
    protected Proxy(InvocationHandler h) {
        this.invocationHandler = h;
    }

    //根据指定的类加载器和接口来动态获取代理对象的Class对象 (而普通的静态方式是自己去impl)
    public static Class<?> getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException {
        //...
    }


    /** 
    * 根据指定的类加载器和接口动态创建代理对象(JDK提供的动态创建接口对象的方式,就叫动态代理)
    * 利用传入的InvocationHandler对象去invoke实际对象的相应的方法
    * ClassLoader 委托类的类加载器
    * Class 委托类和代理类的接口
    * InvocationHandler 动态代理壳对象
    * 最终生成一个代理对象实例
    * 动态的点在于:
    * 反射的方式 调用相应的函数 不需要手动创建具体的代理对象
    */
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        //...
    }
}

public class ProxySubject extends Proxy {
	
}

5、动态调用

 public static void main(String[] args) {
 	// 构造一个真实对象
 	IXX xxRealSubject = new XXRealSubject();
 	// 构造动态代理壳
 	InvocationHandler dynamicProxy = new DynamicProxy(xxRealSubject);
 	// 直接调用接口的相应方法
 	IXX XXAgency = (IXX) Proxy.newProxyInstance(xxRealSubject.getClass().getClassLoader(), new Class[]{IXX.class}, dynamicProxy);
 	// 当生成的动态代理对象实例调用相应方法时  会通过InvocationHandler的引用去invoke相应的方法
    xxAgency.xxMethod(params);

动态代理优点:
1 比静态方式的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法(委托类很多的应用场景 可以代理多个委托类)


装饰模式

在原有对象功能基础上,增加或修改一些功能,而不改变原有对象的功能
类似一个Wrapper

基本角色:
Component: 抽象构件 接口,用以声明原对象和装饰对象的所有接口
ConcreteComponent: 具体构件 原有对象
Decorator: 抽象类 持有一个构件对象(ConcreteComponent)的实例 在构造函数中传入真实对象
ConcreteDecorator: 具体装饰类 继承Decorator 实现不同的装饰方法

UML图
在这里插入图片描述

装饰模式场景:
1 java的I/O模型用到了装饰模式

装饰模式的调用:

 public static void main(String[] args) {
        ISubject sourceSubject = new SourceSubject();
        // 要传入原始对象 并且可以自由转换不同装饰对象
        DecoratorSubject1 decorSubject1 = new DecoratorSubject1(sourceSubject );
        DecoratorSubject2 decorSubject2 = new DecoratorSubject2 (decorSubject1);
        decorSubject2.method();
    }

装饰模式和静态代理区别
1 从语意上讲,代理模式是为控制对被代理对象的访问,而装饰模式是为了增加被装饰对象的功能 (一个是通过别人做事, 一个是装饰自身)
2 代码层面:被代理对象由代理对象内部创建,客户端完全不需要知道被代理类的存在;而被装饰对象由客户端创建并传给装饰对象,可以更自由转换不同装饰对象

装饰模式与继承的区别
其实还是用到了继承,是继承的一种补充。
1 比直接继承好处就是更好的解耦,继承比较死板,只能固定一种顺序,而存在多种搭配的情况下,继承会遇到子类暴增的情况,而装饰模式只需增加基本功能的子类即可 (如变形金刚需要能够自由变换汽车、飞机和潜艇 若继承方式得3种排列组合,而装饰模式只用3个类即可)
2 并且满足父类是final不允许被继承的情况

优点:
1 满足开闭原则
2 比继承好处就是更好的解耦 并且满足父类是final不允许被继承的情况 继承比较死板 只能固定一种顺序
缺点:
若装饰层级过多 则会导致太复杂 不利于排查问题


享元模式

定义: 享元模式运用共享技术复用现有的同类对象,如果未找到匹配的对象,才去创建新对象
优点: 大大减少对象的创建,降低系统的内存,使效率提高。
缺点: 提高了系统和代码的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱,会牺牲一些的执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据
内部状态和外部状态 内部状态指对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。比如具有黑白颜色(内部状态)的棋子,但是他们的位置可能不一样(外部状态)

使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。 如常见的String常量池、缓存池都是运用了享元模式

核心是在享元工厂中 使用HashMap、ConcurrentHashMap等数据结构来进行对象的存储和复用

public class FlyweightPatternDemo {
   private static final String colors[] = 
      { "Red", "Green", "Blue", "White", "Black" };
   public static void main(String[] args) {
 
      for(int i=0; i < 20; ++i) {
         Circle circle = 
            (Circle)ShapeFactory.getCircle(getRandomColor());
         circle.setX(getRandomX());
         circle.setY(getRandomY());
         circle.setRadius(100);
         circle.draw();
      }
   }
   private static String getRandomColor() {
      return colors[(int)(Math.random()*colors.length)];
   }
   private static int getRandomX() {
      return (int)(Math.random()*100 );
   }
   private static int getRandomY() {
      return (int)(Math.random()*100);
   }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值