备忘录模式
这是一个有着明确使用场景的模式,要保存,恢复一个类的状态时使用,例如游戏存档,编辑器undo,redo等等。
涉及到三个类:
Originator 原发器:主类,需要保存并恢复其状态的类
Memento 备忘录:用于记录发起者某一时刻的状态
Caretaker 管理者:用于保存备忘录
发起者里面会有保存
方法,返回一个Memento
,还有一个恢复
方法,根据参数Memento
来恢复自身状态,这两个类很好理解。书里没有对Caretaker
提及太多,而且给的示例中只用Caretaker
存储一个Memento
,让我觉得这个类没啥作用,反而违反了迪米特法则让客户端多认识一个类,查阅资料后有一篇文章说的让我理解了:
忘录模式中并不一定需要一个负责人对象。广义来说,调用发起人角色获得备忘录对象后,备忘录放在哪里,那个对象就是管理者对象。
负责人对象并不是只管理一个备忘录对象,它可以管理多个备忘录对象。
狭义的负责人只管理同一类的备忘录对象,但广义的管理者可以管理不同类型的备忘录对象。
负责人对象需要实现的基本功能是:存入备忘录对象和从中获取备忘录对象。从功能上看,就是一个缓存功能或一个简单的对象实例池。
负责人角色虽然能存取备忘录对象,但是不能访问备忘录对象的内部数据。
很多地方强调了Memento
类的内容只能被Originator
访问,即原发器与备忘录对象之间建立的是一个宽接口。原发器能够看到一个宽接口,允许它访问所需的所有数据,来返回先前的状态。通常实现成为原发器内的一个私有内部类。管理者只是用来保存备忘录对象,管理者对象并不是只管理一个备忘录对象,它可以管理多个备忘录对象。管理者只能看到备忘录的窄接口,这个接口的实现通常没有任何的方法,只是一个类型标识。窄接口使得管理者只能将备忘录传递给其他对象。
组合模式
用来让客户端无差别地对待树
的每一个节点,方便添加、删除、遍历
节点等操作。将树的节点定义为父类,包含所有节点通用的方法,然后各个类型的节点都继承这个类
迭代器模式
很多主流语言里都把迭代器内置了,很多时候程序猿不用自己实现迭代器,因此主要是理解这个模式的设计思路。
把迭代方法
和被迭代的对象
分离解耦
例如我现在有个Company
类,里面有个List<Employee> employees
,我要遍历里面的员工,没有迭代器模式那我要直接在客户端:
for(int i=0;i<company.employees.size();i++){
company.employees.get(i);
}
怎么说呢,看起来一点也不高大上,而且不符合迪米特法则,把company内部的employees对象暴露了,而且如果现在要改成逆序遍历,改动也不简洁,如果多个地方要改,就更崩溃。
所以创建一个迭代器接口Iterator
,里面包括了迭代的通用方法:boolean hasNext()
以及Object next()
再创建实现类ConcreteIterator
class ConcreteIterator implements Iterator{
private List list = new ArrayList();
private int cursor =0;
public ConcreteIterator(List list){
this.list = list;
}
public boolean hasNext() {
if(cursor==list.size()){
return false;
}
return true;
}
public Object next() {
Object obj = null;
if(this.hasNext()){
obj = this.list.get(cursor++);
}
return obj;
}
}
可以再来一个逆序迭代器:
class DescConcreteIterator implements Iterator{
private List list = new ArrayList();
private int cursor =0;
public ConcreteIterator(List list){
this.list = list;
cursor = list.size()-1;
}
public boolean hasNext() {
if(cursor==0){
return false;
}
return true;
}
public Object next() {
Object obj = null;
if(this.hasNext()){
obj = this.list.get(cursor--);
}
return obj;
}
}
然后是需要迭代的类的接口了:
interface Aggregate {
public void add(Object obj);
public void remove(Object obj);
public Iterator iterator();
}
实现类:
class ConcreteAggregate implements Aggregate {
private List list = new ArrayList();
public void add(Object obj) {
list.add(obj);
}
public Iterator iterator() {
return new ConcreteIterator(list);
}
public Iterator descIterator() {
return new DescConcreteIterator(list);
}
public void remove(Object obj) {
list.remove(obj);
}
}
客户端:
public static void main(String[] args){
Aggregate ag = new ConcreteAggregate();
ag.add("1");
ag.add("2");
ag.add("3");
Iterator it = ag.iterator();
while(it.hasNext()){
String str = (String)it.next();
System.out.println(str);
}
}
想要变成逆排序?Iterator it = ag.descIterator();
单例模式
目标是整个运行过程中某各类只能产生一个实例,以下方法中除了枚举
以外都是不能防止反射的
饿汉
程序一启动就实例化对象,浪费资源
public class SingleClass {
private SingleClass single = new SingleClass();
private SingleClass() {
}
public SingleClass instance() {
return single;
}
}
懒汉
在第一次使用时才会实例化对象
public class SingleClass {
private SingleClass single = null;
private SingleClass() {
}
public SingleClass instance() {
if (single == null)
single = new SingleClass();
return single;
}
}
高并发时,两个线程同时执行instance()
方法并判断single为null,就会创建两个实例
双重锁判断
public class SingleClass {
private SingleClass single = null;
private SingleClass() {
}
public SingleClass instance() {
// 第一次判断
if (single == null) {
synchronized (single) {
// 第二次判断
if (single == null)
single = new SingleClass();
}
}
return single;
}
}
把整个方法变成同步的可以确保单例,但是性能太差,所以出现这种方式,第一个if判断确保只要single对象实例完成,后面就不用进行锁判断了。第二个if作用是,当一开始single没有实例的时候,多个线程同时通过了第一层判断,虽然第二层加了锁,但是如果锁里面不加判断,那么这几个线程依然会依次完成实例化,也就是产生多个实例了。
枚举
静态内部类
public class SingleClass {
private static class SingleClassHolder {
public static final SingleClass INSTANCE = new SingleClass();
}
private SingleClass() {
}
public SingleClass instance() {
return SingleClassHolder.INSTANCE;
}
}
同样也是延迟加载,当第一次使用SingleClassHolder时才会加载内部类并初始化静态变量,并由虚拟机来保证它的线程安全性
容器
public class SingletonManager {
private static Map<String,Object> map=new HashMap<String, Object>();
private SingletonManager(){}
public static void registerService(String key,Object instance){
if (!map.containsKey(key)){
map.put(key,instance);
}
}
public static Object getService(String key){
return map.get(key);
}
}
简单工厂,网上搜索到的容器实现感觉没有考虑到多线程情况
桥接模式
优先考虑聚合
、组成
关系,再考虑继承
聚合:生命周期不一致,例如:雁群和大雁
组成:生命周期一致,强依赖,例如:大雁和翅膀
职责链模式
此模式很好理解也很常用
例如办公场景,一个申请要一层一层向上传递
或者是tomcat的过滤器链
每个处理者保留下一级别的引用,当请求发到自己这里时,要么处理,要么推给下一级
中介者模式
比如一个多人聊天场景,ABCDE每个人都是一个实例,A要跟BCDE聊天,他得维护自己的好友列表,然后发送消息时自己找到那个人直接发给他,这样是一种网状关系,随着好友人数变多,网变得越来越复杂,每个实例知道的信息越来越多,不符合迪米特法则。
此时我们引入中介者,所有的关系维护在中介者类中,任何消息都发送给中介者,告诉他消息内容和消息接受者即可。
此模式最大的好处是减少引用数量。
享元模式
关键词:享元工厂 内部状态 外部状态 减少对象数量(还记得不,中介者模式可以减少引用数量)
使用此模式,首先将对象里的属性分为内部状态和外部状态,例如扑克牌
对象,有花色
和数值
两个属性,有Show()
方法打印此张牌的花色和数值信息,如果不做区分,那么要达到展示一整副牌的目的,就要new出52(花色数量*数值数量)个对象,遍历调用Show()
方法
如果把花色
保留原样,作为内部状态,数值改为外部状态,作为参数传入Show
方法,那就只需要4个对象即可,分别调用12次Show方法
有人会问了,如果把两个属性都作为外部参数呢,那只需要一个对象了不是更省内存?
那咱还要啥面向对象呢是吧,而且这个只是最简单的例子,卡牌类
中没有别的方法,如果有其他方法要用到花色
属性呢?所以如何划分内部
和外部
属性是需要考虑的
享元工厂
是做啥的呢?就是用来生产对象的,它维护了Map<内部属性,对象>,创建对象时给工厂内部属性,已经存在的直接返回旧的对象,否则新建一个对象