JAVA设计模式第三讲:结构型设计模式(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: JAVA设计模式第三讲:结构型设计模式(下)
Demo3 装饰者模式在 Java IO中的使用

Java IO 类库如下图所示:

InputStream 是一个抽象类,FileInputStream 是专门用来读取文件流的子类。BufferedInputStream 是一个支持带缓存功能的数据读取类,可以提高数据读取效率。

现在有一个需求,读取文件 test.txt。

InputStream in = new FileInputStream("/user/1202/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1) {
  //...
}

如果是基于继承的设计,设计成 BufferedFileInputStream,需要附加更多的增强功能,那就会导致组合爆炸,类继承结构变得无比复杂代码既不好扩展,也不好维护

InputStream bin = new BufferedFileInputStream("/user/1202/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {
  //...
}

基于装饰器模式的设计模式的 Java IO 源码核心思想:组合优于继承,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。

public abstract class InputStream {
  //...
  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  } 
  public int read(byte b[], int off, int len) throws IOException {
    //...
  } 
  public long skip(long n) throws IOException {
    //...
  } 
  public int available() throws IOException {
    return 0;
  } 
  public void close() throws IOException {}
  public synchronized void mark(int readlimit) {}
  public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
  } 
  public boolean markSupported() {
    return false;
  }
}
public class BufferedInputStream extends InputStream {
  protected volatile InputStream in;
  protected BufferedInputStream(InputStream in) {
    this.in = in;
  } 
  //...实现基于缓存的读数据接口...
} 
public class DataInputStream extends InputStream {
  protected volatile InputStream in;
  protected DataInputStream(InputStream in) {
    this.in = in;
  } 
  //...实现读取基本类型数据的接口
}

9.4、适配器设计模式 Adapter

概念适配器模式用来将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作用于消除接口不匹配所造成的类的兼容性问题

生活中的使用案例:插座,为了适应各种插头,然后上面有两个孔的,三个空的,基本都能适应。还有万能充电器、USB接口等。

代理模式、装饰器模式提供的都是跟原始类相同的接口,而适配器提供跟原始类不同的接口。

主要分为两类

1、类的适配器模式 使用继承关系来实现

2、对象的适配器模式 使用组合关系来实现

Demo1:ITarget 表示要转化成的接口定义。Adaptee 是一组不兼容 ITarget 接口定义的接口,Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口
  • 类适配器: 基于继承
// 适配接口
public interface ITarget {
  void f1();
  void f2();
  void fc();
} 
//被适配者
public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
} 
//适配者  继承被适配者,实现适配接口
public class Adaptor extends Adaptee implements ITarget {
  public void f1() {
    super.fa();
  } 
  public void f2() {
    //...重新实现f2()...
  } 
  // 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}
缺点:需要继承被适配者
  • 对象适配器:基于组合
// 最终需要的输出
public interface ITarget {
  void f1();
  void f2();
  void fc();
} 
//被适配者
public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
} 
//适配者 组合被适配者,实现适配接口
public class Adaptor implements ITarget {
  private Adaptee adaptee;
  public Adaptor(Adaptee adaptee) {
    this.adaptee = adaptee;
  } 
  public void f1() {
    //委托给Adaptee
    adaptee.fa(); 
  } 
  public void f2() {
    //...重新实现f2()...
  } 
  public void fc() {
    adaptee.fc();
  }
}
使用组合替代继承,解决了类适配器必须继承 被是适配者的问题

在开发中,到底该如何选择使用哪一种呢?

①一个是 Adaptee 接口的个数

②另一个是 Adaptee 和 ITarget 的契合程度

怎么简便怎么来。相对而言:更加推荐使用对象适配器,因为组合结构相对于继承更加灵活

  • 符合“合成复用原则”

③ 接口适配

  • 当不需要全部实现某接口提供的方法时,可以先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求

使用场景:

  • ①封装有缺陷的接口设计, 对外部系统提供的接口进行二次封装
  • ②替换外部系统
  • 兼容老版本接口重点
Demo2 修改配置中心SDK
public class InstanceServiceClient {
//...
  public Response<List<InstanceRO>> fetchInstanceByIndustry(InstanceSearchDTO param){ //... }
    public Response<InstanceRO> fetchInstanceByCondition(SearchCondition param){ //... }
  public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... }
  //...
}
// 使用适配器模式进行重构
public class ITarget {
  void function1(InstanceSearchDTO param);
  void function2(SearchCondition param);
  void fucntion3(ParamsWrapperDefinition paramsWrapper);
  //...
}
public class InstanceServiceClientAdaptor extends InstanceServiceClient implements ITarget {
  //...
  public void function1() {
    super.fetchInstanceByIndustry(param);
  } 
  public void function2() {
    super.fetchInstanceByCondition();
  }
  public void function3(ParamsWrapperDefinition paramsWrapper) {
    super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...);
  } 
}
Demo3 替换外部系统
// 外部系统A
public interface IA {
  //...
  void fa();
}
public class A implements IA {
  //...
  public void fa() { //... }
}
// 在我们的项目中,外部系统A的使用示例
public class Demo {
  private IA a;
  public Demo(IA a) {
    this.a = a;
  }
  //...
}
Demo d = new Demo(new A());
// 将外部系统A 替换成外部系统B
public class BAdaptor implemnts IA {
  private B b;
  public BAdaptor(B b) {
    this.b= b;
  }
  public void fa() {
    //...
    b.fb();
  }
}
// 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动,
// 只需要将BAdaptor如下注入到Demo即可。
Demo d = new Demo(new BAdaptor(new B()));
Demo4 兼容升级 ☆

在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且
标注为 deprecated,并将内部实现逻辑委托为新的接口实现

好处让项目有个过渡期,而不是强制进行代码修改

场景: 兼容api升级,API治理可以使用这种方案

// Emueration jdk1.0提供  jdk2.0 改为了 iterator
public class Collections {
  public static <T> Emueration<T> emumeration(final Collection<T> c) {
    return new Enumeration<T>() {
      private Iterator<T> i = c.iterator();
      public boolean hasMoreElments() {
        return i.hashNext();
      }
      public T nextElement() {
        return i.next():
      }
    }
  }
}
  • Action:实际使用中,非常实用,避免出现线上故障
  • 例如:原来提供的dubbo接口有4个入参,现在需要补充一个入参,我们能直接修改原来接口的签名吗?风险极大。可以选择的方案是:提供一个新的接口,入参使用dto,方便拓展,然后原来的dubbo接口底层实现时,调用新提供的接口
Demo5 适配器模式在 Java 日志中的应用
  • Slf4j 这个日志框架,它相当于 JDBC 规范,提供了一套打印日志的统一接口规范。不过,它只定义了接口,并没有提供具体的实现,需要配合其他日志框架(log4j、logback……)来使用。
  • 它不仅仅提供了统一的接口定义,还提供了针对不同日志框架的适配器。对不同日志框
    架的接口进行二次封装,适配成统一的 Slf4j 接口定义。
package org.slf4j;
public interface Logger {
  public boolean isTraceEnabled();
  public void trace(String msg);
  public void trace(String format, Object arg);
  public void trace(String format, Object arg1, Object arg2);
  public void trace(String format, Object[] argArray);
  public void trace(String msg, Throwable t);
  public boolean isDebugEnabled();
  public void debug(String msg);
  public void debug(String format, Object arg);
  public void debug(String format, Object arg1, Object arg2)
  public void debug(String format, Object[] argArray)
  public void debug(String msg, Throwable t);
  //...省略info、warn、error等一堆接口
}
// log4j日志框架的适配器
// Log4jLoggerAdapter实现了LocationAwareLogger接口,
// 其中LocationAwareLogger继承自Logger接口,
// 也就相当于Log4jLoggerAdapter实现了Logger接口。
package org.slf4j.impl;
public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements LocationAwareLogger, Serializable {
  final transient org.apache.log4j.Logger logger; // log4j
  public boolean isDebugEnabled() {
    return logger.isDebugEnabled();
  } 
  public void debug(String msg) {
    logger.log(FQCN, Level.DEBUG, msg, null);
  } 
  public void debug(String format, Object arg) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.format(format, arg);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  } 
  public void debug(String format, Object arg1, Object arg2) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  } 
  public void debug(String format, Object[] argArray) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  } 
  public void debug(String msg, Throwable t) {
    logger.log(FQCN, Level.DEBUG, msg, t);
  }
  //...省略一堆接口的实现...
}
Demo6 适配器模式在 SpringMVC 框架应用的源码剖析

SpringMVC 处理用户请求的流程

SpringMVC 中的 HandlerAdapter, 就使用了适配器模式

//多种Controller实现  
public interface Controller {
}
class HttpController implements Controller {
  public void doHttpHandler() {
    System.out.println("http...");
  }
}
class SimpleController implements Controller {
  public void doSimpleHandler() {
    System.out.println("simple...");
  }
}
class AnnotationController implements Controller {
  public void doAnnotationHandler() {
    System.out.println("annotation...");
  }
}
public class DispatchServlet extends FrameworkServlet {
  public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
  public DispatchServlet() {
    handlerAdapters.add(new AnnotationHandlerAdapter());
    handlerAdapters.add(new HttpHandlerAdapter());
    handlerAdapters.add(new SimpleHandlerAdapter());
  }
  public void doDispatch() {
    // 此处模拟 SpringMVC 从 request 取 handler 的对象,适配器可以获取到想要的Controller
    HttpController controller = new HttpController();
    //AnnotationController controller = new AnnotationController();
    //SimpleController controller = new SimpleController();
    // 得到对应适配器
    HandlerAdapter adapter = getHandler(controller);
    // 通过适配器执行对应的controller对应方法
    adapter.handle(controller);
  }
  public HandlerAdapter getHandler(Controller controller) {
    //遍历:根据得到的controller(handler), 返回对应适配器
    for (HandlerAdapter adapter : this.handlerAdapters) {
      if (adapter.supports(controller)) {
        return adapter;
      }
    }
    return null;
  }
  public static void main(String[] args) {
    new DispatchServlet().doDispatch(); // http...
  }
}
///定义一个Adapter接口 
public interface HandlerAdapter {
  public boolean supports(Object handler);
  public void handle(Object handler);
}
// 多种适配器类
class SimpleHandlerAdapter implements HandlerAdapter {
  public boolean supports(Object handler) {
    return (handler instanceof SimpleController);
  }
  public void handle(Object handler) {
    ((SimpleController) handler).doSimplerHandler();
  }
}
class HttpHandlerAdapter implements HandlerAdapter {
  public boolean supports(Object handler) {
    return (handler instanceof HttpController);
  }
  public void handle(Object handler) {
    ((HttpController) handler).doHttpHandler();
  }
}
class AnnotationHandlerAdapter implements HandlerAdapter {
  public boolean supports(Object handler) {
    return (handler instanceof AnnotationController);
  }
  public void handle(Object handler) {
    ((AnnotationController) handler).doAnnotationHandler();
  }
}

使用HandlerAdapter 的原因:

可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则。

SpringMVC使用适配器设计模式的好处

• Spring 定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类;

• 适配器代替 Controller 执行相应的方法;

• 扩展 Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了


代理、桥接、装饰器、适配器 4 种设计模式的区别

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,控制访问,而非加强功能

桥接模式桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用

适配器模式:一种补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

20211203 done


9.5、你在编码时最常用的设计模式有哪些?在什么场景下用?在业务代码中,经常发现大量XXFacade,门面模式是解决什么问题?适用于什么场景?

9.5.1、门面模式定义
  • 门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用,合理的使用门面模式,可以帮我们更好的划分访问层次。

使用场景:

  • ① 解决易用性问题 封装底层的复杂实现;
  • ② 解决性能问题 将多个接口组装为一个大而全接口,减少网络开销;
  • ③维护遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互, 提高复用性。

门面模式在商品中心使用广泛,但是有些 facade 类并不是出于封装底层子系统复杂实现的目的编写的这些业务逻辑不复杂的接口去除 facade 后缀为好

Demo1 facade模式的使用姿势

例如:业务方需要在一个接口中返回商品信息(配置项、类目、属性、价格、库存)

public class ItemReadFacade {
  //定义各个子系统对象
  private ItemConfig itemConfig;
  private Category category;
  private Attributes attributes;
  private Price price;
  private Stock stock;
  //构造器
  public ItemReadFacade() {
    super();
    this.itemConfig = ItemConfig.getInstance();
    this.category = Category.getInstance();
    this.attributes = Attributes.getInstance();
    this.price = Price.getInstance();
    this.stock = Stock.getInstance();
  }
  //操作分成 4 步
  public ItemWrapperDTO read() {
    itemConfig.read();
    category.read();
    attributes.read();
    price.read();
    stock.read();
  }
}
public class ItemConfig {
  //使用单例模式, 使用饿汉式
  private static ItemConfig instance = new ItemConfig();
  public static ItemConfig getInstanc() {
    return instance;
  }
  public ItemConfig read() {
    System.out.println(" ItemConfig ");
  }
}
...
public static void main(String[] args) {
  ItemReadFacade itemReadFacade = new ItemReadFacade();
  itemReadFacade.read();
}
Demo2 门面模式在 MyBatis 框架应用的分析

MyBatis 中的Configuration 去创建MetaObject 对象使用到门面模式

public class Configuration {
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  public MetaObject newMetaObject(Object object) {
    // 封装了子系统的复杂性
    return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
  }
}
public class MetaObject{
  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        this.originalObject = object;
        this.objectFactory = objectFactory;
        this.objectWrapperFactory = objectWrapperFactory;
        this.reflectorFactory = reflectorFactory;
        if (object instanceof ObjectWrapper) {
            this.objectWrapper = (ObjectWrapper)object;
        } else if (objectWrapperFactory.hasWrapperFor(object)) {
            this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
        } else if (object instanceof Map) {
            this.objectWrapper = new MapWrapper(this, (Map)object);
        } else if (object instanceof Collection) {
            this.objectWrapper = new CollectionWrapper(this, (Collection)object);
        } else {
            this.objectWrapper = new BeanWrapper(this, object);
        }
    }
    public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        return object == null ? SystemMetaObject.NULL_META_OBJECT : new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }
}

9.6、组合模式(不常用)

概念:将对象组合成树状结构以表示 “整体-部分”的层次关系

使用场景:用来处理树状结构数据。

  • 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式;业务需求可以通过在树上的递归遍历算法来实现
  • 如果节点和叶子有很多差异性的话,比如很多方法和属性不一样, 不适合使用组合模式

组合模式的角色及职责

  1. Component:这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口
  2. Leaf:在组合中表示叶子节点,叶子结点没有子节点;
    3)Composite:非叶子节点,用于存储子部件,在 Component 接口实现子部件的相关操作,比如增加、删除。
Demo1 需求:设计一个“类目”类**,能方便地实现下面这些功能:
  • 动态地添加、删除某个类目下的父类目或子类目;
  • 统计指定类目下的类目个数;
  • 统计指定类目下的标签个数;

代码如下所示,把父类目和子类目统一用 CategoryNode 类来表示,并通过hasChildren 属性来区分父子节点。

public class CategoryNode {
  //类目名称
  private String name;
  //是否有叶子节点
  private boolean hasChildren;
  //子节点
  private List<CategoryNode> subNodes = new ArrayList<>();
  public CategoryNode(String name, boolean hasChildren) {
    this.name = name;
    this.hasChildren = hasChildren;
  }
  public int countNumOfCategories() {
    if (!hasChildren) {
      return 1;
    }
    int numOfCategories = 0;
    for (CategoryNode categoryNode : subNodes) {
      numOfCategories += categoryNode.countNumOfCategories();
    }
  }
  public String getName() {
    return name;
  }
  public void addSubNode(CategoryNode categoryNode) {
    subNodes.add(categoryNode);
  }
  public void removeSubNode(CategoryNode categoryNode) {
    int size = subNodes.size();
    int i = 0;
    for (; i < size; ++i) {
      if (subNodes.get(i).getName().equals(categoryNode.getName())){
        break;
      }
    }
    if (i < size) {
      subNodes.remove(i);
    }
  }
}
public class Demo {
  public static void main(String[] args) {
    CategoryNode pCategoryNode = new CategoryNode("名贵花木");
    CategoryNode node_1 = new CategoryNode("笔记本电脑");
    CategoryNode node_2 = new CategoryNode("台式整机");
    pCategoryNode.addSubNode(node_1);
    pCategoryNode.addSubNode(node_2);
    System.out.println("category num:" + pCategoryNode.countNumOfCategories());
  }
}
Demo2 构建整个公司的人员架构图(部门、子部门、员工的隶属关系)
  • 提供接口计算出部门的薪资成本(隶属于这个部门的所有员工的薪资和),部门包含子部门和员工,这是一种嵌套结构,可以表示成树这种数据结构。计算每个部门的薪资开支这样一个需求,也可以通过在树上的遍历算法来实现。从这个角度来看,该场景可以使用组合模式来设计和实现。
  • HumanResource 是部门类(Department)和员工类(Employee)抽象出来的父类,提供统一薪资的处理逻辑。
public abstract class HumanResource {
  protected long id;
  protected double salary;
  public HumanResource(long id) {
    this.id = id;
  } 
  public long getId() {
    return id;
  } 
  public abstract double calculateSalary();
} 
// 员工
public class Employee extends HumanResource {
  public Employee(long id, double salary) {
    super(id);
    this.salary = salary;
  } 
  @Override
  public double calculateSalary() {
    return salary;
  }
} 
// 部门
public class Department extends HumanResource {
  private List<HumanResource> subNodes = new ArrayList<>();
  public Department(long id) {
    super(id);
  } 
  @Override
  public double calculateSalary() {
    double totalSalary = 0;
    for (HumanResource hr : subNodes) {
      totalSalary += hr.calculateSalary();
      }
      this.salary = totalSalary;
      return totalSalary;
    }
  }
  public void addSubNode(HumanResource hr) {
    subNodes.add(hr);
  }
}
// 构建组织架构的代码
public class Demo {
  private static final long ORGANIZATION_ROOT_ID = 1001;
  // 依赖注入
  private DepartmentRepo departmentRepo; 
  // 依赖注入
  private EmployeeRepo employeeRepo; 
  public void buildOrganization() {
    Department rootDepartment = new Department(ORGANIZATION_ROOT_ID);
    buildOrganization(rootDepartment);
  } 
  private void buildOrganization(Department department) {
    List<Long> subDepartmentIds = departmentRepo.getSubDepartmentIds(department);
    for (Long subDepartmentId : subDepartmentIds) {
      Department subDepartment = new Department(subDepartmentId);
      department.addSubNode(subDepartment);
      buildOrganization(subDepartment);
    }
    List<Long> employeeIds = employeeRepo.getDepartmentEmployeeIds(department.getId());
    for (Long employeeId : employeeIds) {
      double salary = employeeRepo.getEmployeeSalary(employeeId);
      department.addSubNode(new Employee(employeeId, salary));
    }
  }
}
Demo3 组合模式在 HashMap 的源码分析

职责:

①Map起 Component 的作用,提供通用的能力;

②Node 起 leaf 的作用,在组合中表示叶子节点;

②HashMap起 Composite 的作用,继承 Map接口,借助 Node 实现 Map 的功能

public interface Map<K,V> {
  V put(K key, V value);
  V remove(Object key);
  ...
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
   public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
  public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
    // 调用Node节点的put实现功能
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                  boolean evict) {
       Node<K,V>[] tab; Node<K,V> p; int n, i;
       if ((tab = table) == null || (n = tab.length) == 0)
           n = (tab = resize()).length;
       if ((p = tab[i = (n - 1) & hash]) == null)
           tab[i] = newNode(hash, key, value, null);
       else {
           Node<K,V> e; K k;
           if (p.hash == hash &&
               ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
           else if (p instanceof TreeNode)
               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
           else {
               for (int binCount = 0; ; ++binCount) {
                   if ((e = p.next) == null) {
                       p.next = newNode(hash, key, value, null);
                       if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                           treeifyBin(tab, hash);
                       break;
                   }
                   if (e.hash == hash &&
                       ((k = e.key) == key || (key != null && key.equals(k))))
                       break;
                   p = e;
               }
           }
           if (e != null) { // existing mapping for key
               V oldValue = e.value;
               if (!onlyIfAbsent || oldValue == null)
                   e.value = value;
               afterNodeAccess(e);
               return oldValue;
           }
       }
       ++modCount;
       if (++size > threshold)
           resize();
       afterNodeInsertion(evict);
       return null;
   }
   final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }
}
static class Node<K,V> implements Map.Entry<K,V> {
   final int hash;
     final K key;
     V value;
     Node<K,V> next;
}

9.7、享元模式(被共享的单元)

9.7.1、享元模式定义
  • 运用共享技术有效地支持大量细粒度的对象。
  • 当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。

享元模式可以分为以下两种角色

  • 享元角色:存储在享元对象内部且不会随环境而改变
  • 不可共享角色:随环境改变而改变、不可共享的状态。

使用场景各类池技术:String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。

享元模式的缺点:享元模式对 JVM 的垃圾回收并不友好。因为享元工厂类一直保存了对享元对象的引用,这就导致享元对象在没有任何代码使用的情况下,也并不会被 JVM 垃圾回收机制自动回收掉。因此不要过度使用这个模式。

Demo1 棋牌游戏
  • 一个游戏厅中有成千上万个“房间”,每个房间对应一个棋局。棋局要保存每个棋子的数据,比如:棋子类型(将、相、士、炮等)、棋子颜色(红方、黑方)、棋子在棋局中的位置。利用这些数据,我们就能显示一个完整的棋盘给玩家。
  • 我们可以将棋子的 id、text、color 属性拆分出来,设计成独立的类,并且作为享元供多个棋盘复用。
// 享元角色
public class ChessPieceUnit {
  private int id;
  private String text;
  private Color color;
  public ChessPieceUnit(int id, String text, Color color) {
    this.id = id;
    this.text = text;
    this.color = color;
  } 
  public static enum Color {
    RED, BLACK
  } 
  // ...省略其他属性和getter方法...
} 
// 享元工厂,通过一个 Map 来缓存已经创建过的享元对象,来达到复用的目的
public class ChessPieceUnitFactory {
  private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>();
  static {
    pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK));
    pieces.put(2, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));
    //...省略摆放其他棋子的代码...
  } 
  public static ChessPieceUnit getChessPiece(int chessPieceId) {
    return pieces.get(chessPieceId);
  }
}
// 不可共享的角色
public class ChessPiece {
  private ChessPieceUnit chessPieceUnit;
  private int positionX;
  private int positionY;
  public ChessPiece(ChessPieceUnit unit, int positionX, int positionY) {
    this.chessPieceUnit = unit;
    this.positionX = positionX;
    this.positionY = positionY;
  }
  // 省略getter、setter方法
}
// Client
public class ChessBoard {
  private Map<Integer, ChessPiece> chessPieces = new HashMap<>();
  public ChessBoard() {
    init();
  } 
  private void init() {
    chessPieces.put(1, new ChessPiece(
    ChessPieceUnitFactory.getChessPiece(1), 0,0));
    chessPieces.put(1, new ChessPiece(
    ChessPieceUnitFactory.getChessPiece(2), 1,0));
    //...省略摆放其他棋子的代码...
  }
  public void move(int chessPieceId, int toPositionX, int toPositionY) {
    //...省略...
  }
}
Demo2 剖析一下享元模式在 Java Integer、String 中的应用。

通过自动装箱,也就是调用 valueOf() 来创建 Integer 对象时,如果要创建的 Integer 对象的值在 -128 到 127 之间,会从 IntegerCache 类中直接返回,否则才调用 new 方法创建。

  • JDK库中的Integer cache(-128~127)
Integer i1 = 56;   //自动装箱 Integer i = Integer.valueOf(59);
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);  // true
System.out.println(i3 == i4);  //false
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 {
    // high value may be configured by property
    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) {
        // If the property cannot be parsed into an int, ignore it.
      }
    }
    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() {}
}

可以用如下方式,将缓存的最大值从 127 调整到 255

//方法一:
-Djava.lang.Integer.IntegerCache.high=255
//方法二:
-XX:AutoBoxCacheMax=255

使用建议:在日常开发中,对于下面这样三种创建整型对象的方式,我们优先使用后两种

Integer a = new Integer(123); // 并不会使用到 IntegerCache
Integer a = 123;
Integer a = Integer.valueOf(123);
享元模式在 Java String 中的应用

JVM 会专门开辟一块存储区来存储字符串常量,这块存储区叫作“字符串常量池”。对于字符串来说,没法事先知道要共享哪些字符串常量,只能在某个字符串常量第一次被用到时,存储到常量池中,当之后再用到时,直接引用常量池中已经存在的即可,就不需要再重新创建了。

String s1 = "设计模式";
String s2 = "设计模式";
String s3 = new String("设计模式");
System.out.println(s1 == s2);  //true
System.out.println(s1 == s3);  //false
享元模式和单例模式的区别?

单例模式是创建型模式,重在只能有一个对象。而享元模式是结构型模式,重在节约内存使用,提升程序性能。

享元模式:把一个或者多可对象缓存起来,用的时候,直接从缓存里获取。也就是说享元模式不一定只有一个对象

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
17天前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
27天前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
32 4
|
2月前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
49 0
[Java]23种设计模式
|
1月前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
2月前
|
设计模式 监控 算法
Java设计模式梳理:行为型模式(策略,观察者等)
本文详细介绍了Java设计模式中的行为型模式,包括策略模式、观察者模式、责任链模式、模板方法模式和状态模式。通过具体示例代码,深入浅出地讲解了每种模式的应用场景与实现方式。例如,策略模式通过定义一系列算法让客户端在运行时选择所需算法;观察者模式则让多个观察者对象同时监听某一个主题对象,实现松耦合的消息传递机制。此外,还探讨了这些模式与实际开发中的联系,帮助读者更好地理解和应用设计模式,提升代码质量。
Java设计模式梳理:行为型模式(策略,观察者等)
|
3月前
|
存储 设计模式 安全
Java设计模式-备忘录模式(23)
Java设计模式-备忘录模式(23)
|
3月前
|
设计模式 存储 算法
Java设计模式-命令模式(16)
Java设计模式-命令模式(16)
|
3月前
|
设计模式 存储 缓存
Java设计模式 - 解释器模式(24)
Java设计模式 - 解释器模式(24)
|
3月前
|
设计模式 安全 Java
Java设计模式-迭代器模式(21)
Java设计模式-迭代器模式(21)
|
3月前
|
设计模式 缓存 监控
Java设计模式-责任链模式(17)
Java设计模式-责任链模式(17)