Java笔记20 - 设计模式 - 结构型模式
- 如何组合各种对象, 以便获得更好更灵活的对象.
- 继承是基本的子类扩展父类的功能
- 结构型模式更多的使用组合和运动期的动态组合来实现更灵活的功能.
适配器
- 将一个类的接口转换成客户希望的另一个接口, 使得原本不兼容不能一起工作的类, 可以通过适配器一起工作
// Task.java
public class Task implements Callable<Long>{
private long num;
public Task(long num) {
this.num = num;
}
@Override
public Long call() throws Exception {
long r = 0;
for (long n = 1; n <= this.num; n++) {
r = r + n;
}
System.out.println("Result: " + r);
return null;
}
}
// RunnableAdapter
public class RunnableAdapter implements Runnable {
// 引用待转换接口
private Callable<?> callable;
public RunnableAdapter(Callable<?> callable) {
this.callable = callable;
}
// 实现指定接口
@Override
public void run() {
// 将指定接口调用位图给转换接口调用
try {
callable.call();
} catch (Exception e) {
throw new RuntimeException();
}
}
}
Callable<Long> task = new Task(10);
Thread thread = new Thread(new RunnableAdapter(task));
thread.start();
- 实现Adapter的步骤如下:
- 实现目标接口 这里是
Runnable
. - 内部持有一个待转换接口的引用, 这里是通过字段持有
Callable
接口. - 在目标接口的实现方法内部, 调用待转换接口的方法.
- 实现目标接口 这里是
InputStream inputStream = Files.newInputStream(Paths.get("/pom.xml"));
Reader reader = new InputStreamReader(inputStream);
FileReader reader = new InputStreamReader(inputStream, "UTF-8");
- 面向抽象编程: 持有高层接口不但代码更灵活, 而且把各种组合起来也更容易
- 一旦持有某个具体类型的子类类型, 想要做一些感动就非常困难.
桥接
- 将抽象部分与它的实现部分分离, 使他们都可以独立的变化
- 桥接主要是避免, 集成带来的子类爆炸.
┌───────────┐
│ Car │
└───────────┘
▲
│
┌───────────┐ ┌─────────┐
│RefinedCar │ ─ ─ ─>│ Engine │
└───────────┘ └─────────┘
▲ ▲
┌────────┼────────┐ │ ┌──────────────┐
│ │ │ ├─│ FuelEngine │
┌───────┐┌───────┐┌───────┐ │ └──────────────┘
│BigCar ││TinyCar││BossCar│ │ ┌──────────────┐
└───────┘└───────┘└───────┘ ├─│ElectricEngine│
│ └──────────────┘
│ ┌──────────────┐
└─│ HybridEngine │
└──────────────┘
- 这样的话, 汽车可以在两个维度自由变化
组合
- 将对象组合成树形结构, 以表示"部分-整体"结构, 使得用户对单个对象和组合对象的使用保持一致.
-
组合模式(Composite)经常用于树形结构, 为了简化代码, 使用composite可以把一个叶子节点和父节点一起处理
-
使用组合模式时, 需要先统一单个节点以及容器节点的入口
public interface Node {
// 添加一个节点作为子节点
Node add(Node node);
// 获取子节点
List<Node> children();
// 输出为xml
String toXML();
}
public class ElementNode implements Node {
private String name;
private List<Node> list = new ArrayList<>();
public ElementNode(String name) {
this.name = name;
}
@Override
public Node add(Node node) {
list.add(node);
return this;
}
@Override
public List<Node> children() {
return list;
}
@Override
public String toXML() {
String start = "<" + name + ">\n";
String end = "</" + name + ">";
StringJoiner sj = new StringJoiner("", start , end);
list.forEach(node -> {
sj.add(node.toXML() + "\n");
});
return sj.toString();
}
}
public class TextNode implements Node {
private String text;
public TextNode(String text) {
this.text = text;
}
@Override
public Node add(Node node) {
throw new UnsupportedOperationException();
}
@Override
public List<Node> children() {
return List.of();
}
@Override
public String toXML() {
return text;
}
}
装饰器
- 动态的给一个对象添加一些额外的职责. 相比功能来说, 相比生成子类更灵活.
-
装饰器模式(Decorator)就是在运行期间动态给某个对象的实例增加功能的方法
-
一个个的附加功能, 用
Decorator
的方式给一层一层地累加到原始数据源上. 最终, 通过组合获得我们想要的功能. -
实际上把核心功能和附加功能区分开了.
// 定义顶层接口
public interface TextNode {
// 设置text:
void setText(String text);
// 获取text
String getText();
}
// 核心节点<span>从等层接口直接继承
public class SpanNode implements TextNode {
private String text;
@Override
public void setText(String text) {
this.text = text;
}
@Override
public String getText() {
return "<span>" + text + "</span>";
}
}
// 顶级装饰器, 只有子类可以访问构造函数
public abstract class NodeDecorator implements TextNode {
protected final TextNode target;
// 核心功能, 缓冲target, 并把剩下的功能附加到target上
protected NodeDecorator(TextNode target) {
this.target = target;
}
public void setText(String text) {
target.setText(text);
}
}
// 真正的装饰器
public class ItalicDecorator extends NodeDecorator {
public ItalicDecorator(TextNode target) {
super(target);
}
@Override
public String getText() {
return "<i>" + target.getText() + "</i>";
}
}
public class BoldDecorator extends NodeDecorator{
public BoldDecorator(TextNode target) {
super(target);
}
@Override
public String getText() {
return "<b>" + target.getText() + "</b>";
}
}
外观
- 为子系统的一组接口提供一个一致的界面. Facade模式定义了一个高层接口, 这个接口使得这一子系统更加容易使用.
-
很多Web程序内部, 内部有多个子系统提供服务, 经常使用一个统一的网关入口来自动转发到不同的Web服务.
-
这种统一提供入口的网关GateWay, 本质上就是一个Facade. 可以附加一些用户认证, 限流限速服务.
-
给客户端提供一个统一的入口, 对外部屏蔽调用细节.
享元
- 运用共享技术有效的支持大量细粒度的对象
- 核心思想: 有些实例一经创建就不会改变, 那么反复创建对象就是一种浪费. 直接返回共享一个实例, 既节省空间, 又提高了运行效率.
Byte
,Integer
都是享元模式.
int i1 = Integer.valueOf(1);
int i2 = Integer.valueOf(1);
assertTrue(i1 == i2);
- 总使用工厂方法创建对象, 可以获得享元模式的好处
- 实际中, 享元模式总是用于缓存. 客户端如果重复请求某些对象, 不用重复查询数据库或者读取文件. 而是直接返回内存中的数据.
public class Student {
// 持有缓存
private static final Map<String, Student> cache = new HashMap<>();
// 静态工厂方法
public static Student create(int id, String name) {
String key = id + "\n" + name;
// 先查找缓冲
Student std = cache.get(key);
if (std == null) {
std = new Student(id, name);
cache.put(key, std);
}
return std;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
private final int id;
private final String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
代理
- 为其他对象提供一种代理以控制对这个对象的访问.
代理的应用范围
-
远程代理
本地的调用者持有的接口实际上是一个代理, 这个代理负责把对接口方法访问转换成远程调用, 然后返回结果.
-
虚代理
让调用者先持有一个虚拟对象, 如果没有必要, 真正的对象永远不会被创建. 直到客户端真正调用时, 才会创建.
-
保护代理
如果有很多客户端进行访问, 可以通过内部计数器, 在都不使用时进行释放.
-
Decorator模式让调用者自己创建核心类, 然后组合各种功能
-
Proxy模式是让调用者以为获取到的是核心类接口, 其实是代理类.