设计模式之结构型模式

整个代码和文档是跟着Bilibili up主:黑手书生 的系列视频——设计模式总结的
学习地址:https://space.bilibili.com/505571900?from=search&seid=10016231603740656139
视频中的代码地址:https://github.com/OAyUliko/JAVA_Design_pattern

总述

  1. 包括适配器模式桥接模式组合模式装饰模式外观模式享元模式代理模式
  2. 描述了如何将类或对象结合在一起形成更大的结构
  3. 实例:
例子模式
每一个数据库引擎的JDBC驱动都是介于JDBC接口和数据库接口的适配器

①适配器模式 Adapter Pattern

  1. 动机:将一个类的 接口 转换成客户希望的 另一个接口,使得原本由于接口 不兼容 而不能一起工作的类可以一起工作(绝对是 针对接口 设计)
  2. 类结构型的适配器:①适配器 继承 老系统,在其中 调用 老系统的 方法 供新系统接口使用
  3. 对象结构型的适配器:①适配器 关联 老系统,老系统对象 作为 类对象 放入,由类对象去 调用 老系统的方法
                         ②优点:适配的范围更广(因为是关联,非继承,继承的话只能继承一个类)
    类结构性
    在这里插入图片描述
  4. 适配器包装的是 适配者Adaptee(被适配的类)
  5. 适配器 是没有实际工作的,只是转换作用,整合运算
  6. 优点:①将目标类和适配器类 解耦 ,通过适配器来 重用
          ②增加了类的 透明性复用性,对客户端是透明的,灵活性扩展性
          ③通过 配置文件 可以更改适配器,符合“开闭原则”符合“合成复用原则”
  7. 缺点:①过多使用适配器会让系统零乱,不易进行整体把握。明明调用的 A 接口,内部被适配成了 B 接口,因此如果不是很有必要,不使用适配器,而是对系统进行重构
  8. 适用场景:①系统需要使用现有的类,但发现接口不符合系统需要
              ②想要建立一个重复使用的类,用于与彼此之间无太大关联的类工作
  9. 类图:在这里插入图片描述

②桥接模式 Bridge Pattern

  1. 动机:把 抽象实现 解耦,二者可以独立变化,用 组合关系 代替了继承关系
  2. 抽象接口和抽象类,抽象接口类中有抽象的 功能方法抽象类中有 protected 的 接口对象 ,用这个接口对象去调用与接口关联的类内部的 详细功能 方法
    在这里插入图片描述
  3. 注意是扩展维度而非扩展功能!!
//客户端代码
//这里和下一行最好是用配置文件XMLUtil,实现客户端的透明
Color color= new Red();    //找颜料
Pen pen=new BigPen();        //找笔
pen.setColor(color);        //笔沾上颜料
pen.draw("Meiko");        //笔画什么

//抽象化角色Pen类
public void setColor(Color color) {this.color = color;}
protected Color color;    //一定要有抽象接口的类对象
public abstract void draw(String Name);
  1. 优点:①抽象和实现的分离
          ②扩展力强,支持“开闭原则”,“合成复用原则”
          ③实现细节对客户 透明,通过配置文件改,完全不用改内部代码
  2. 缺点:①增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计
  3. 适用场景:①有 两个维度 在同时变化,但又想糅合在一起
  4. 类图:在这里插入图片描述

③组合模式 Composite Pattern

  1. 动机:把一组相似的对象当作一个单一的对象。依据 树形 结构来组合对象,用来表示 “整体-部分” 层次。使得用户对 单个对象(叶子对象)和 组合对象(容器对象)的使用具有一致性
  2. 角色:抽象构件(描述叶子和容器的共同特征)、叶子构件、容器构件(可以叶子套叶子,也可以容器套容器)
    在这里插入图片描述
    在这里插入图片描述
//客户端代码
MyElement e1, e2, e3;
Plate p1, p2;
e1 = new Apple();
e2 = new Orange();
p1 = new Plate();
p1.Add(e1);	//大盘子里装 一个苹果
p1.Add(e2);	//一个橘子
p2 = new Plate();
p1.Add(p2);	//一个盘子
e3 = new Apple();
p2.Add(e3);	//盘子里再装一个苹果
p1.eat();	//吃整个盘子可以吃到???   苹果 橘子 苹果

//盘子里的递归调用 针对arrrylist里的每个元素 叶子结点就“吃”,盘子就递归调用eat
void eat() {
    for(Object object:arrayList)
        ((MyElement)object).eat();
}
  1. 透明组合模式:①程序员不用去关心对叶子还是容器操作,达到透明
  2. 对象结构型 的适配器:①不会发生误调用
    在这里插入图片描述
    在这里插入图片描述
  3. 优点:①清楚定义分层次的复杂对象,增加新构件更容易了,结点自由增加
          ②客户端调用简单,一致地使用叶子或容器
  4. 缺点:①设计变的抽象,不是所有的方法都与叶子有关联
          ②增加新构件可能会产生问题,很难对容器中的构件类型进行限制
          ③递归要是不当,会造成时间和空间的消耗
          ④叶子和容器是实现类,而不是接口,违反了“依赖倒置原则”
  5. 适用场景:①可以忽略整体与部分的差异,一致对待,无需关心层次细节
              ②对象的结构是动态的但复杂程度不一样

④装饰模式 Decorator Pattern

  1. 动机:以 透明 的方式 动态地 给一个对象增加 额外的职责 ,又不改变其结构,这比生成它的子类 更灵活
  2. 这种模式创建了一个装饰类来 包裹住 原有的类,并在保持类方法签名完整性的前提下,提供额外的功能
//客户端代码
BirthdayCake birthdayCake = new Cake();    //创建一个蛋糕胚
birthdayCake.show();
Cream cream=new Cream(birthdayCake);    //蛋糕上加奶油
cream.PutCream();                     
Fruit fruit=new Fruit(cream);    //在 已经加上奶油的蛋糕胚 上加水果
fruit.PutFruit();

//抽象装饰类中的重要代码
private BirthdayCake birthdayCake;    //然后在构造函数里加入进去
//加工类中要继承父类的接口类对象  
public Fruit(BirthdayCake birthdayCake) {    super(birthdayCake);    }
  1. 角色:抽象构件、具体构件(被包装者)、抽象装饰、具体装饰(谁来包装,里面有具体的加工方法,加工次序可以更改)
    在这里插入图片描述
  2. 优点:①装饰模式可以提供比继承更多的 灵活性,而且可以通过 配置文件 选择不同的 装饰器符合“合成复用原则”
          ②装饰类和构件类可以独立发展,不相互影响,可以创造不同行为的组合,符合“开闭原则”
  3. 缺点:①产生很多小对象,很多具体装饰类(比如加水果时要用到奶油对象),系统 复杂度 增加
           ②排错困难,需要逐级排查,难度增加
  4. 适用场景:①动态,透明 增加 单个对象的功能,动态 撤销
              ②不能采用继承方式或继承不利于扩展和维护时
  5. 类图:在这里插入图片描述

⑤外观模式 Facade Pattern

  1. 动机:把子系统中的一组接口统一的外观对象 包裹,它定义了一个 高层接口 来简化使用
  2. 外观对象本身是不提供子系统中要完成的 功能 ,只提供 交互,即用户只需跟外观角色交互,降低 系统 耦合
    在这里插入图片描述
//客户端代码
GeneralSwitchFacade gsf =new GeneralSwitchFacade();//相当于总面板
gsf.on();
System.out.println("----------------------");
gsf.off();

//外观器代码
private Light light[]=new Light[4];
private AirConditioner ac;
private Television tv;

public GeneralSwitchFacade() {    ...全部对象加入     }  
public void on()    {    light[0].on();......    } 
 //其实是通过小对象去调用对象内部的具体功能方法
}
  1. 角色:外观角色(沟通通道)、子系统角色(提供实际功能)
  2. 优点:①将系统划分为若干个子系统来 降低复杂度,引入外观对象作为访问子系统的入口符合“单一职责原则”
    ②引入外观类降低客户类和子系统类的耦合,屏蔽子系统 组件,打破了二者的直接连接,符合“迪米特原则”
    ③子系统 组件变化 不会影响到客户类,只需调整外观类
    ④降低了大型软件系统中的 编译依赖性 ,简化了不同平台的移植过程
  3. 缺点:①不能很好限制客户使用子类(可以new一个子类来调用子类功能)
    增加 新的 子系统 要修改外观类和客户类代码,不符合“开闭原则”(可通过引入抽象外观类解决)
  4. 适用场景:①为一个复杂系统提供接口,该接口可以满足多数用户需求,用户也可以跨过外观类直接访问子系统
    ②将客户程序和子系统解耦,需要提高子系统的独立性和可移植性
  5. 一般将外观对象设为 单例模式 ,只能有一个;不要企图继承外观类扩展它的功能,重点在让各子系统完成工作
  6. 类图:在这里插入图片描述

⑥享元模式 Flyweight Pattern

  1. 动机:运用 共享 技术有效地支持 大量细粒度 的对象(大多为相似的,状态变化很小的对象)的 复用 ,实现相同或相似对象的重用
  2. 内部状态:可以共享的相同内容(连接池的关键字)
    外部状态:需要外部环境来设置的,不能共享的内容(连接的ip,端口)
//客户端代码 
Random rm = new Random();
PieceFactroy pieceFactroy = new PieceFactroy();//生产棋子的工厂
Piece piece;//棋子对象
for(int i=0;i<19;i++)//按棋盘大小随机落子
    for(int j=0;i<19;i++)
    {
        if(rm.nextInt()%2==0)
            piece=pieceFactroy.GetPiece("黑");//随机数产生黑白棋子
            ...
        piece.Play(rm.nextInt(19),rm.nextInt(19));//随机放置
    }
System.out.println(pieceFactroy.GetPieceCount());//其实系统中只有两个对象,黑子和白子,都是共享这两个对象

//关键代码 PieceFactroy类
public Piece GetPiece(String key) {
    if (hashMap.containsKey(key))    //是否有黑白子对象,有的话在map里按key返回,没有的话创建一个再返回,其实都是返回的已有的对象
        return (Piece) hashMap.get(key);
    else {
        Piece piece = new OnePiece(key);
        hashMap.put(key, piece);
        return piece;
}}
  1. 角色:抽象享元类、具体享元类(被享元工厂管着,如果没有就new一个具体享元对象)、非共享具体享元类(不适合状态交换的东西,类似账号和密码)、享元工厂类
  2. 享元模式通常会出现 工厂模式 ,需要创建一个 享元工厂 来维护享元池,用于存储具有相同内部状态的对象
  3. 优点:① 减少 对象的创建,节约内存占用,提高效率
  4. 缺点:①需要分离内外部状态,提高了 复杂度
  5. 适用场景:①系统有大量相似对象,对象消耗大量内存时
              ②需要缓冲池的场景
  6. 类图:在这里插入图片描述
  7. 与原型模式的区别:原型模式返回的是克隆出的对象,享元模式直接返回了对象本身

⑦代理模式 Proxy Pattern

  1. 动机:给某一个对象提供一个代理,用代理来 实现 对真实对象的 操作 或将代理作为原对象的 替身。因为有时直接访问对象会带来麻烦
  2. 静态代理:代理类和被代理类都 继承同一接口,用户在使用时,以为来自于被代理类(远程的类);靠代理类去show
    在这里插入图片描述
//静态代理的客户端
LocalPic localPic=new LocalPic();    //新建本地对象
localPic.show("Meiko照片");    //看似是调用本地图片,其实本地对象在背后调用远程图片类,加载图片

//关键代码   LocalPic类
iShowPic=new RemotePic();    //通过接口对象去真正完成远程代理(因为远程图片继承了接口)
System.out.println("准备载入图片"+picName);
Thread thread=new Thread(this);    //把自己放入线程
thread.start();//启动线程
...
iShowPic.show(picName);
  1. 动态代理:代理类不知道被代理类是什么形态,需要 动态创建 代理类,得益于反射机制;靠接口类对象去show
    在这里插入图片描述
//动态代理的客户端 
IShowPic iShowPic=new RemotePic();    //得到远程的对象
LocalPic localPic= new LocalPic(iShowPic);    //得到本地的对象,把远程对象放进来
IShowPic LP= (IShowPic) localPic.GetProxyInstance();    //把远程对象实例化,通过实例化了的远程对象来加载图片
LP.show("Meiko");    //接口对象去show!!

//关键代码   LocalPic类  
public Object GetProxyInstance()
{                            //参数:被代理类的名字列表,被代理的方法,方法的参数数组
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { 
@Override
//调用了被代理类里面的方法, Method method是接口方法 , Object[] args是参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     System.out.println("准备载入图片"+args[0]);//args是名字,只有一个参数
     Thread thread=new Thread(new Runnable() {
     @Override
     public void run() {
         method.invoke(target,args);    //远程图片调用,Method method就是远程的方法
                }});
     thread.start();
}});}
  1. 优点:①协调代理类和被代理类,降低耦合度
          ②远程代理:客户端可以访问在远程机器上的对象,可以快速响应处理客户端对象
            虚拟代理:减少系统资源消耗,优化提升
  2. 缺点:①有些类型的代理模式可能会造成请求的处理速度变慢
          ②有些代理模式的实现非常复杂
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值