首页 > 其他分享 >设计模式-结构型模式

设计模式-结构型模式

时间:2022-12-09 21:01:05浏览次数:33  
标签:String 对象 void 模式 class new 设计模式 public 结构型

结构型模式:将对象和类组装成较⼤的结构,并同时 保持结构的灵活和⾼效。

PS:博客根据it老齐大话设计模式课程课件进行整理,IT老齐 视频学习网站: https://www.itlaoqi.com

包含的设计模式:

  适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

门面模式

  提供了⼀个供客户端统⼀调⽤的⻔⾯(Facade),这个⻔⾯屏蔽了下游系统的复杂性,使得客户端对下游系统的调⽤变得更简单。

代码背景

启动mysql应用集群

public class DatabaseManager {
  public void startDbCluster(){
   System.out.println("正在远程启动MySQL应⽤集群");
  }
}

预热redis缓存

public class CacheManager {
  public void initRedis(){
    System.out.println("正在启动并预热Redis应⽤缓存");
  }
}

启动web应用服务器

public class WebServerManager {
  public void loadWebApp(){
    System.out.println("正在加载并启动Web应⽤服务器");
  }
}

如果没有额外操作,客户端必须⼿动运⾏这些组件,这极⼤提⾼了客户端的使⽤⻔槛

public class Client {
  public static void main(String[] args) {
    new DatabaseManager().startDbCluster();
    new CacheManager().initRedis();
    new WebServerManager().loadWebApp();
  }
}

引入门面Facade,让应⽤开发⽅提供操作,为客户端提供最简单的操作⼊⼝即可

public class ApplicationFacade {
  private CacheManager cacheManager = new CacheManager();
  private DatabaseManager dbManager = new DatabaseManager();
  private WebServerManager webManager = new 
  WebServerManager();
  public void initSystem(){
    dbManager.startDbCluster();
    cacheManager.initRedis();
    webManager.loadWebApp();
  }
}

优化后的Client只需要⾯对ApplicationFacade⻔⾯,不需要再关注具体细节

public class Client {
  public static void main(String[] args) {
    ApplicationFacade application = new ApplicationFacade();
    application.initSystem();
  }
}

优缺点

优点:1、松散耦合: ⻔⾯模式松散了客户端与⼦系统的耦合关系,让⼦系统内部的模块能更容易扩展和维护

   2、简单易⽤: ⻔⾯模式让⼦系统更加易⽤,客户端不再需要了解⼦系统内部的实现,也不需要跟众多⼦系统内部的模块进⾏交互,只需要跟⻔⾯类交互就可以了

   3、更好的划分访问层次: 通过合理使⽤Facade,可以帮助我们更好地划分访问的层次。有些⽅法是对系统外的,有些⽅法是系统内部使⽤的。把需要暴露给外部的功能集中到⻔⾯中,这样既⽅便客户端使⽤,也很好地隐藏了内部的细节。

缺点:不符合开闭原则,如果要修改某⼀个⼦系统的功能,通常外观类也要⼀起修改;

 

适配器模式

  作为两个不兼容的接⼝之间的桥梁,对现有接⼝的实现类进⾏扩展,使其实现客户期望的⽬标接⼝。

两种⽅式实现适配器模式:类适配器,对象适配器

适配器的组成要素

  Target:客户期望获得的功能接⼝。

  Cilent:客户,期望访问Target接⼝。

  Adaptee:现有接⼝,这个接⼝需要被适配。

  Adapter:适配器类,适配现有接⼝使其符合客户需求接⼝。

代码背景

  将数据库返回的结构数据适配为json数据

Adaptee

import java.util.List;
import java.util.Map;
public interface UserService {
  public Map findById();
  public List<Map> findUsers();
}

Adaptee实现类

public class UserServiceImpl implements UserService {
  public Map findById(){
  Map map = new LinkedHashMap();
    map.put("user_id", 1234);
    map.put("username", "zhangsan");
    return map;
  }
  public List<Map> findUsers(){
    List<Map> list = new ArrayList<>();
    for(int i = 0 ; i<=10 ; i++){
      Map map = new LinkedHashMap();
      map.put("user_id", new Random().nextInt(100000));
      map.put("username", "user"+i);
      list.add(map);
    }
    return list;
  }
}

Target

public interface SpecUserService {
  public String findByJId();
  public String findJUsers();
}

对象适配器

Adapter

  实现Target,内部持有Adaptee对象通过构造⽅法传⼊,然后实现 Target的各种⽅法完成转换

public class SpecUserServiceAdapter implements SpecUserService {
  private UserService userService;
  public SpecUserServiceAdapter(UserService userService){
    this.userService = userService;
  }
  public String findByJId() {
    Map user = userService.findById();
    String json = new Gson().toJson(user);
    return json;
  }
  public String findJUsers() {
    List<Map> users = userService.findUsers();
    String json = new Gson().toJson(users);
    return json;
  }
}

Client

  客户端负责先实例化Adaptee对象,传给Adapter构造⽅法,然后调⽤ Target接⼝即可

public class Client {
  public static void main(String[] args) {
    UserService userService = new UserServiceImpl();
    SpecUserService specUserService = new 
    SpecUserServiceAdapter(userService);
    System.out.println(specUserService.findJUsers());
  }
}

类适配器

Adapter

  增加适配器,继承Adaptee实现类,实现Target接⼝,完成每个⽅法的转换

public class SpecUserServiceAdapter extends UserServiceImpl implements SpecUserService{
  public String findByJId() {
    Map user = super.findById();
    String json = new Gson().toJson(user);
    return json;
  }
  public String findJUsers() {
    List<Map> users = super.findUsers();
    String json = new Gson().toJson(users);
    return json;
  }
}

Client

  客户端直接调⽤实例化Adapter即可

public class Client {
  public static void main(String[] args) {
    SpecUserService userService = new SpecUserServiceAdapter();
    System.out.println(userService.findJUsers());
  }
}

优缺点

优点:1、可以让任何两个没有关联的类⼀起运⾏

    2、提⾼了类的复⽤,可以⼀致化多个不同接⼝

       3、将现有接⼝实现类隐藏,增加了类的透明度

     4、灵活性⾼,可⾃由适配

缺点:1、过多地使⽤适配器,会让系统⾮常零乱,不易整体进⾏把握。⽐如,明明看到调⽤的是A接⼝,其实内部被适配成了B接⼝的实现,⼀ 个系统如果太多出现这种情况,⽆异于⼀场灾难。因此如果不是很有 必要,可以不使⽤适配器,⽽是直接对系统进⾏重构

   2、某些适配⼯作可能⾮常困难,例如让房⼦⻜起来

   3、当我们有动机地修改⼀个正常运⾏的系统的接⼝,这时应该考虑使⽤适配器模式

 

桥接模式

  将接口作为贯通上下的桥梁,实现程序的拓展

使用场景

  1、如果⼀个系统需要在构件的抽象化⻆⾊和具体化⻆⾊之间增加更多的 灵活性,避免在两个层次之间建⽴静态的继承联系,通过桥接模式可以使它们在抽象层建⽴⼀个关联关系

  2、对于那些不希望使⽤继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适⽤

  3、⼀个类存在两个独⽴变化的维度,且这两个维度都需要进⾏扩展

代码背景

  数据操作接口和日志的多种实现

日志类接口

public interface Logger {
  public void info(String message);
  public void debug(String debug);
}

log4j日志实现类

public class Log4jLogger implements Logger{
  @Override
  public void info(String message) {
    System.out.println("log4j->[INFO]" + message);
  }
  @Override
  public void debug(String message) {
    System.out.println("log4j->[debug]" + message);
  }
}

Logback日志实现类

public class LogbackLogger implements Logger{
  @Override
  public void info(String message) {
    System.out.println("logback->[INFO]" + message);
  }
  @Override
  public void debug(String message) {
    System.out.println("logback->[debug]" + message);
  }
}

数据操作接口

public interface DbOperator {
  public void insert(Object obj);
}

mongdb实现类

public class MongoOperator implements DbOperator {
  @Override
  public void insert(Object obj) {
    System.out.println(obj + "已写⼊MongoDB");
  }
}

mysql实现类

public class MysqlOperator implements DbOperator {
  @Override
  public void insert(Object obj) {
  System.out.println(obj + "已写⼊Redis");
  }
}

业务接口类

public interface Service {
 public void init();
}

员工实现类

import logger.Logger;
import DbOperator;
public class EmployeeService implements Service {
  private DbOperator dbOperator = null;
  private Logger logger = null;
  public EmployeeService(DbOperator dbOperator, Logger logger) {
    this.dbOperator = dbOperator;
    this.logger = logger;
  }
  public void update(){
    dbOperator.insert("{员⼯A数据}");
    logger.info("数据更新成功");
  }
  @Override
  public void init() {
    logger.info("EmployeeService已初始化完毕");
  }
}

调用类

public class Client {
  public static void main(String[] args) {
    EmployeeService employeeService = new EmployeeService(new MongoOperator(),new Log4jLogger());
    employeeService.init();
    employeeService.update();
  }
}

优缺点

优点:1、分离抽象接⼝及其实现部分。提⾼了⽐继承更好的解决⽅案

   2、桥接模式提⾼了系统的可扩充性,在两个变化维度中任意扩展⼀个维度,都不需要修改原有系统

   3、实现细节对客户透明,可以对⽤户隐藏实现细节

   4、实现了抽象化与实现化的脱耦。他们两个互相独⽴,不会影响到对⽅

缺点:1、桥接模式的引⼊会增加系统的理解与设计难度,由于聚合关联关系建⽴在抽象层,要求开发者针对抽象进⾏设计与编程

   2、桥接模式要求正确识别出系统中两个独⽴变化的维度,因此其使⽤范围具有⼀定的局限性

 

装饰者模式

  通过将对象放⼊包含⾏为的特殊封装对象中来为原对象绑定新的⾏为

装饰者的组成要素

  部件 (component):声明封装器和被封装对象的公⽤接⼝

  具体部件 (Concrete Component):类是被封装对象所属的类。 定义了基础⾏为,但装饰类可以改变这些⾏为

  基础装饰 (Base Decorator):类拥有⼀个指向被封装对象的引⽤成员变量。 该变量的类型应当被声明为通⽤部件接⼝, 这样它就可以引⽤具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。

  具体装饰类 (Concrete Decorators): 定义了可动态添加到部件的额外⾏为。 具体装饰类会重写装饰基类的⽅法, 并在调⽤⽗类⽅法之前或之后进⾏额外的⾏为。

  客户端 (Client):可以使⽤多层装饰来封装部件, 只要它能使⽤通⽤接⼝与所有对象互动即可。

实现方式

  装饰器Decorator需要实现顶层接⼝,且内部通过构造⽅法接收某个顶层实现类对象,这个顶层实现对象可以是最终的顶层实现对象,也可以是其他装饰器对象

应用场景

  1、如果希望在⽆需修改代码的情况下即可使⽤对象, 且希望在运⾏时为对象新增额外的⾏为

  2、装饰能将业务逻辑组织为层次结构,可为各层创建⼀个装饰, 在 运⾏时将各种不同逻辑组合成对象。 由于这些对象都遵循通⽤接口, 客户端代码能以相同的⽅式使⽤这些对象

  3、如果⽤继承来扩展对象⾏为的⽅案难以实现或者根本不可⾏,可以使⽤该模式

  4、许多编程语⾔使⽤ final最终关键字来限制对某个类的进⼀步扩展。 复⽤最终类已有⾏为的唯⼀⽅法是使⽤装饰模式:⽤封装器对其进⾏封装

代码背景

  将数据源接口进行装饰

顶层抽象数据源接口

public interface DataSource {
  void writeData(String data);
  String readData();
}

具体实现类

import java.io.*;
public class FileDataSource implements DataSource {
  private String name;
  public FileDataSource(String name) {
    this.name = name;
  }
  @Override
  public void writeData(String data) {
    File file = new File(name);
    try (OutputStream fos = new FileOutputStream(file)) {
      fos.write(data.getBytes(), 0, data.length());
    } catch (IOException ex) {
      System.out.println(ex.getMessage());
    }
  }
  @Override
  public String readData() {
    char[] buffer = null;
    File file = new File(name);
    try (FileReader reader = new FileReader(file)) {
      buffer = new char[(int) file.length()];
      reader.read(buffer);
    } catch (IOException ex) {
      System.out.println(ex.getMessage());
    }
    return new String(buffer);
  }
}

BASE64编码装饰器

import java.util.Base64;
public class EncryptionDecorator implements DataSource {
  private DataSource datasource;
  public EncryptionDecorator(DataSource dataSource) {
    this.datasource = dataSource;
  }
  @Override
  public void writeData(String data) {
    datasource.writeData(encode(data));
  }
  @Override
  public String readData() {
    return decode(datasource.readData());
  }
  private String encode(String data) {
    byte[] result = data.getBytes();
    for (int i = 0; i < result.length; i++) {
      result[i] += (byte) 1;
    }
    return Base64.getEncoder().encodeToString(result);
  }
  private String decode(String data) {
    byte[] result = Base64.getDecoder().decode(data);
    for (int i = 0; i < result.length; i++) {
      result[i] -= (byte) 1;
    }
    return new String(result);
  }
}

JSON序列化装饰器

JSON POM配置

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.9.1</version>
</dependency>
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.util.HashMap;
import java.util.Map;
public class JsonDecorator implements DataSource {
  private DataSource dataSource;
  public JsonDecorator(DataSource dataSource) {
    this.dataSource = dataSource;
  }
  @Override
  public void writeData(String data) {
    dataSource.writeData(toJson(data));
  }
  @Override
  public String readData() {
    return fromJson(dataSource.readData());
  }
  private String toJson(String stringData) {
    Map map = new HashMap();
    map.put("content", stringData);
    return new Gson().toJson(map);
  }
  private String fromJson(String json) {
    Map map = new Gson().fromJson(json,new TypeToken<Map<String,String>>(){}.getType());
    return map.get("content").toString();
  }
}

调用

public class Client {
  public static void main(String[] args) {
    String salaryRecords = "Name,Salary / John Smith,100000 / Steven Jobs";
    DataSource dataSource = new EncryptionDecorator(new JsonDecorator(new FileDataSource("d:/code/OutputDemo.txt")));
    dataSource.writeData(salaryRecords);
    DataSource plain = new FileDataSource("d:/code/OutputDemo.txt");
    System.out.println("- Data ----------------");
    System.out.println(salaryRecords);
    System.out.println("- EncodedContent --------------");
    System.out.println(plain.readData());
    System.out.println("- DecodedContent --------------");
    System.out.println(dataSource.readData());
  }
}

优缺点

优点:1、⽆需创建新⼦类即可扩展对象的⾏为

   2、可以在运⾏时添加或删除对象的功能

   3、可以⽤多个装饰封装对象来组合⼏种⾏为

   4、单⼀职责原则,可以将实现了许多不同⾏为的⼀个⼤类拆分为多个较⼩的类

缺点:1、在封装器栈中删除特定封装器⽐较困难

   2、实现⾏为不受装饰栈顺序影响的装饰⽐较困难

   3、各层的初始化配置代码看上去可能会很糟糕

 

享元模式

  以共享的⽅法⾼效地⽀持⼤量的细粒度对象,享元对象能做到共享的关键是区分内蕴状态和外蕴状态

内蕴状态

  存储在享元对象内部的,并且是不会随环境改变⽽有所 不同的,因此⼀个享元可以具有内蕴状态并可以共享

外蕴状态

  随环境的改变⽽改变的,不可以共享的状态,享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使⽤的时候各⽅⾯传⼊到享元对象内部

总结

  外蕴状态不可以影响享元对象的内蕴状态,它们是相互独⽴的

应用场景

  1、⼀个系统有⼤量的对象

  2、这些对象耗费⼤量的内存

  3、这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中删除时,每⼀个组都可以仅⽤⼀个对象代替

  4、使⽤享元模式需要维护⼀个记录了系统已有的所有享元的表,⽽这需要耗费资源,因此应当在有⾜够多的享元实现可供共享时才值的使⽤享元模式.

代码背景

  创建100台不同设备ID的笔记本电脑

电脑类

import java.util.List;
/*电脑*/
public class Computer {
  //商品唯⼀编码
  private String sn;
  //商品名
  private String title;
  //品牌
  private String brand;
  //商品图⽂描述
  private String description;
  public String getSn() {
    return sn;
  }
  public void setSn(String sn) {
    this.sn = sn;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public String getBrand() {
    return brand;
  }
  public void setBrand(String brand) {
    this.brand = brand;
  }
  public String getDescription() {
    return description;
  }
  public void setDescription(String description) {
    this.description = description;
  }
}

调用类

import java.util.*;
public class Vendor {
  public void purchase(){
    List<Computer> warehouse = new ArrayList();
    for(int i = 0 ; i < 100 ; i++){
      Computer c = new Computer();
      c.setSn(UUID.randomUUID().toString());
      c.setBrand("华为");
      c.setTitle("MateBook 14s");
      c.setDescription("英特尔Evo 12代酷睿标压i7 16G 1T/14.2英⼨90Hz触控");
      warehouse.add(c);
    }
    for(int i = 0 ; i < 1000 ; i++){
      Computer c = new Computer();
      c.setSn(UUID.randomUUID().toString());
      c.setBrand("⼩⽶");
      c.setTitle("Xiaomi Book Pro 14");
      c.setDescription("2.8K超清⼤师屏 R7-6800H标压 16G 512G");
      warehouse.add(c);
    }
  }
}

分析

  除了设备的SN之外,其他⼤量的基础信息(元数据)都是不变的,这造成的⼤量的重复代码与内存指针(String是引⽤类型),如果重复代 码中包含了值对象,如int、long、float、double等,还会浪费⼤量的内存空间

优化代码

电脑规格类

/*具体商品规格*/
public class ComputerSpec {
  private String title;
  //品牌
  private String brand;
  //商品图⽂描述
  private String description;
  public ComputerSpec(String title, String brand, String description) {
    this.title = title;
    this.brand = brand;
    this.description = description;
  }
  public String getTitle() {
    return title;
  }
  public String getBrand() {
    return brand;
  }
  public String getDescription() {
    return description;
  }
}

电脑类

public class Computer {
  //商品唯⼀编码
  private String sn;
  //规格对象
  private ComputerSpec spec;
  public String getSn() {
   return sn;
  }
  public void setSn(String sn) {
   this.sn = sn;
  }
  public ComputerSpec getSpec() {
   return spec;
  }
  public void setSpec(ComputerSpec spec) {
   this.spec = spec;
  }
}

调用类

import java.util.*;
public class Vendor {
  private Map<String , ComputerSpec> specMap = new HashMap<>();
  public Vendor(){
    specMap.put("macbook14s" , new ComputerSpec("MateBook 14s","华为","英特尔Evo 12代酷睿标压i7 16G 1T/14.2英⼨90Hz触控"));
    specMap.put("bookpro14", new ComputerSpec("Xiaomi Book Pro 14","⼩⽶", "2.8K超清⼤师屏 R7-6800H标压 16G 512G"));
  }
  public void purchase(){
    List<Computer> warehouse = new ArrayList();
    for(int i = 0 ; i < 100 ; i++){
      Computer c = new Computer();
      c.setSn(UUID.randomUUID().toString());
      c.setSpec(specMap.get("macbook14s"));
      warehouse.add(c);
    }
    for(int i = 0 ; i < 1000 ; i++){
      Computer c = new Computer();
      c.setSn(UUID.randomUUID().toString());
      c.setSpec(specMap.get("bookpro14"));
      warehouse.add(c);
    }
  }
}

优缺点

优点:1、⼤⼤减少对象的创建,降低系统的内存,使效率提⾼

缺点:1、提⾼了系统的复杂度,需要分离出外部状态和内部状态,⽽且外部状态 具有固有化的性质,不应该随着内部状态的变化⽽变化,否则会造成系统的混乱

 

组合模式

  将⼀组对象组织(Compose)成树形结构,以表示⼀种“部分 - 整体”的层次结 构。组合让客户端(在很多设计模式书籍中,“客户端”代指代码的使⽤者。)可以统⼀单个对象和组合对象的处理逻辑。

代码背景

  文件目录

node抽象节点类

public interface OssNode {
 public String getPath();
}

文件夹类

import java.util.ArrayList;
import java.util.List;
/**
 * OSS⽬录类
 */
public class OssDirectory implements OssNode {
  private List<OssNode> subNodes = new ArrayList<>();
  String path = null;
  /**
  * 指定绝对路径
  * @param path
  */
  public OssDirectory(String path){
    this.path = path;
  }
  /**
  * 指定⽗级⽬录,采⽤相对路径
  * @param parent 上级⽬录对象
  * @param dirName ⽬录名
  */
  public OssDirectory(OssDirectory parent , String dirName){
    parent.addChild(this);
    this.path = parent.getPath() + dirName;
  }
  /**
  * 得到当前⽬录路径
  * @return
  */
  @Override
  public String getPath() {
    return path;
  }
  /**
  * 增加⼦节点
  * @param node
  */
  public void addChild(OssNode node){
    this.subNodes.add(node);
  }
 /**
  * 移除⼦节点
  * @param node
  */
  public void removeChild(OssNode node){
    this.subNodes.remove(node);
  }
  /**
  * 返回所有⼦节点
  * @return
  */
  public List<OssNode> getChildren(){
    return this.subNodes;
  }
}

文件类

/**
 * Oss⽂件对象
 */
public class OssFile implements OssNode{
  private String filename;
  private OssDirectory ossDirectory;
  /**
  * 构造⽅法
  * @param dir 指定⽬录
  * @param filename ⽂件名
  */
  public OssFile(OssDirectory dir ,String filename) {
    this.ossDirectory = dir;
    //添加到指定节点
    dir.addChild(this);
    this.filename = filename;
  }
  /**
  * 得到⽂件路径
  * @return
  */
  @Override
  public String getPath() {
    return ossDirectory.getPath() + filename;
  }
}

调用类

import java.util.List;
public class Client {
  public static void main(String[] args) {
    //组织⽬录结构
    OssDirectory root = new OssDirectory("/root");
    OssDirectory dir1 = new OssDirectory(dir1,"/s1");
    OssDirectory dir14 = new OssDirectory(dir14,"/s4");
    OssDirectory dir145 = new OssDirectory(dir145,"/s5");
    OssDirectory dir2 = new OssDirectory(root,"/s2");
    OssDirectory dir3 = new OssDirectory(root,"/s3");
    //组织⽂件存放
    OssFile fil1 = new OssFile(dir1, "/f1.txt");
    OssFile fil2 = new OssFile(dir2, "/f2.png");
    OssFile fil3 = new OssFile(dir3, "/f3.gif");
    OssFile fil14 = new OssFile(dir14, "/f14.txt");
    OssFile fil145 = new OssFile(dir145, "/f145.svg");
    //实例化客户端递归打印所有节点路径
    Client client = new Client();
    client.printNodes(root);
  }
  /**
  * 递归打印所有节点路径
  * @param dir
  */
  public void printNodes(OssDirectory dir){
    List<OssNode> children = dir.getChildren();
    for (OssNode node : children) {
      System.out.println(node.getPath());
      if(node instanceof OssDirectory){
        this.printNodes((OssDirectory) node);
      }
    }
  }
}

 

代理模式

  代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进⾏⼀些处理

实现方式

  1、如果没有现成的服务接⼝, 你就需要创建⼀个接⼝来实现代理和服务对象的可交换性。 从服务类中抽取接⼝并⾮总是可⾏的, 因为你需要对服务的所有客户端进⾏修改, 让它们使⽤接⼝。 备选计划是将代理作为服务类的⼦类, 这样代理就能继承服务的所有接⼝了。

  2、创建代理类, 其中必须包含⼀个存储指向服务的引⽤的成员变量。 通常情况下, 代理负责创建服务并对其整个⽣命周期进⾏管理。 在⼀些特殊情况下, 客户端会通过构造函数将服务传递给代理。

  3、根据需求实现代理⽅法。 在⼤部分情况下, 代理在完成⼀些任务后应将⼯作委派给服务对象。

代码背景

  在youtube上下载视频

下载类抽象接口

public interface VideoDownloader {
  public byte[] download(String url);
}

目标实现

public class YoutubeDownloader implements VideoDownloader{
  @Override
  public byte[] download(String url) {
  System.out.println("正在从Youtube视频下载完毕:" + url);
    return new byte[1024];
  }
}

代理实现缓存功能,减少远程交互

注意:代理类内部负责实例化⽬标对象,⽽不是通过构造⽅法对外暴露

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
public class YoutubeDownloaderProxy implements VideoDownloader{
  private VideoDownloader downloader ;
  private Map<String,String> cache = new LinkedHashMap();
  public YoutubeDownloaderProxy() {
    this.downloader = new YoutubeDownloader();
  }
  @Override
  public byte[] download(String url) {
    synchronized (YoutubeDownloaderProxy.class){
      if(!cache.containsKey(url)){
        byte[] bs = downloader.download(url);
        String cacheFile = "/usr/home/youtube/cache/" + new Random().nextInt(10000) + ".mp4";
        System.out.println("正在将" + url + "视频缓存到本地:" + cacheFile);
        cache.put(url, cacheFile);
        return bs;
      }else{
        String cacheFile = cache.get(url);
        System.out.println("发现" + url + "已被缓存,直接从本地⽂件" +cacheFile + "提取");
        return new byte[1024];
      }
    }
  }
}

调用类

public class Client {
  public static void main(String[] args) {
    VideoDownloader downloader = new YoutubeDownloaderProxy();
    downloader.download("http://www.youtube.com/xxx");
    downloader.download("http://www.youtube.com/yyy");
    downloader.download("http://www.youtube.com/xxx");
  }
}

应用场景

  1、延迟初始化 (虚拟代理),如果你有⼀个偶尔使⽤的重量级服务对象, ⼀直保持该对象运⾏会消耗系统资源时, 可使⽤代理模式。 你⽆需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候

  2、访问控制 (保护代理),如果你只希望特定客户端使⽤服务对象, 这⾥的对象可以是操作系统中⾮常重要的部分, ⽽客户端则是各种已启动的程序(包括恶意程序), 此时可使⽤代理模式。代理可仅在客户端凭据满⾜要求时将请求传递给服务对象

  3、本地执⾏远程服务(远程代理),适⽤于服务对象位于远程服务器上的情形。在这种情形中, 代理通过⽹络传递客户端请求, 负责处理所有与⽹络相关的复杂细节

  4、记录⽇志请求 (⽇志记录代理),适⽤于当你需要保存对于服务对象的请求历史记录时。 代理可以在向服务传递请求前进⾏记录

  5、缓存请求结果 (缓存代理),适⽤于需要缓存客户请求结果并对缓存⽣命周期进⾏管理时, 特别是当返回结果的体积⾮常⼤时。代理可对重复请求所需的相同结果进⾏缓存, 还可使⽤请求参数作为索引缓存的键值

  6、智能引⽤,可在没有客户端使⽤某个重量级对象时⽴即销毁该对象。 代理会将 所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运⾏。 如果相应的客户端列表为空,代理就会销毁该服务对象,释放底层系统资源

优缺点

优点:1、在客户端毫⽆察觉的情况下控制服务对象

   2、如果客户端对服务对象的⽣命周期没有特殊要求, 可以对⽣命周期进⾏管理

   3、即使服务对象还未准备好或不存在, 代理也可以正常⼯作

   4、开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理

缺点:1、代码可能会变得复杂, 因为需要新建许多类,可以考虑使⽤Java动态代理或者CGlib代理实现(Spring)

   2、服务响应可能会延迟。

如何区分装饰器与代理模式

  装饰和代理有着相似的结构, 但是其意图却⾮常不同。 这两个模式的构建都基于组合原则, 也就是说⼀个对象应该将部分⼯作委派给另⼀个对象。 两者之间的不同之处在于代理通常⾃⾏管理其服务对象的⽣命周 期,⽽装饰的⽣成则总是由客户端进⾏控制

 

标签:String,对象,void,模式,class,new,设计模式,public,结构型
From: https://www.cnblogs.com/caixiaozi/p/16950412.html

相关文章

  • 静态和动态代理模式
    代理模式,也称委托模式,是结构型设计模式之一,何为代理呢?在日常生活中就比如叫朋友替你拿个快递,叫朋友替你做一下作业,叫朋友替你买点东西等等,这个朋友就是你的代理,你把事情委......
  • redis数据库—主从复制、哨兵模式、集群
    一、Redis的三种高可用方案主从复制:主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份(和同步),以及对于读......
  • 创建型设计模式
    简单工厂方法定义为:简单工厂模式又称静态工厂方法模型,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂专门定义一个类来负责创建其他......
  • vscode 在debug模式给被调试程序传递环境变量
      https://blog.csdn.net/jinxiaonian11/article/details/127965187 C/C++"environment":[{"name":"ENV_TEST","value":"1"},{"name":"T......
  • SQL Server2008恢复模式、简单恢复模式、完整恢复模式、大容量日志恢复模式
    SQLServer2008支持三种恢复模式,即简单恢复模式、完整恢复模式和大容量日志恢复模式。简单恢复:无日志备份。自动回收日志空间以减少空间需求,实际上不再需要管理事务日志......
  • 迭代器模式
    需求案例使用传统的设计方案传统解决方案存在的问题迭代器模式的基本介绍迭代器模式的原理类图   迭代器模式应用实例   代码实现packagecom.s......
  • stm32f407VET6 同一组IO的不同io口设置不同的模式(GPIOA 或 GPIOB 或 GPIOC、等)
    1、使能这组GPIO外设的时钟2、定义GPIO口初始化结构体(不同模式的io口,设置不同的结构体),设置Pin_x的模式 ......
  • Redis主从复制,哨兵模式和集群模式
    一、主从复制1.1主从复制-哨兵-集群主从复制:主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份,以及对于读操......
  • TIMx_ETR外部时钟源模式2配置方法
    由于stm32中的时钟源对应的GPIO口都是ST原厂已经分配好的,可以查看数据手册了解,所以我们要将外部触发信号连接到对应的时钟源GPIOpin口,这样才能起到外部时钟源的作用。例......
  • Web应用模式
    (1)Web应用模式在Web开发中,有两种模式:前后端分离#前后端分离:只专注于后端,返回json格式数据前后端不分离#前后端混合开发(前后端不分离)返回的是HTML的......