Java的二十三种设计模式
概述
设计模式是针对某一类问题的最优解决方案,是从许多优秀的软件系统中总结出的。
Java中设计模式(java design patterns)通常有23种。
创建型模式
创建型模式涉及对象的实例化,特点是不让用户代码依赖于对象的创建或排列方式,避免用户直接使用new创建对象。
工厂方法模式、抽象工厂方法模式、生成器模式、原型模式和单例模式。
行为型模式
行为型模式涉及怎样合理的设计对象之间的交互通信,以及怎样合理为对象分配职责,让设计富有弹性,易维护,易复用。
责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
结构型模式
结构型模式涉及如何组合类和对象以形成更大的结构,和类有关的结构型模式涉及如何合理使用继承机制;和对象有关的结构型模式涉及如何合理的使用对象组合机制。
适配器模式、组合模式、代理模式、享元模式、外观模式、桥接模式和装饰模式。
模式中涉及的重要角色,会在描述中(加粗字体)介绍出来。下面就逐一介绍。
1、单例模式(Singleton Pattern)
Ensure a class only has one instance,and provide a global point of access to it.
在使用这个模式的时候,我们要考虑是否会在多线程中使用,如果不会应用于多线程,那写法就足够简单:
public class SimpleSingleton {
private static SimpleSingleton instance;
private SimpleSingleton(){}
public static SimpleSingleton getIntance(){
if(instance == null)
instance = new SimpleSingleton();
return instance;
}
}
上例就是一个简单的单例模式实现,使用了懒加载模式。但是多线程中可能会创建多个实例。下面就介绍多线程中的使用。
如果直接将上面例子应用到多线程中,可以直接把getInstance()设置为同步的(synchronized),但是并不高效,任一之后,只能有一个线程可以调用这个方法,其余的会排队等待。
所以整个方法做同步不是优解,那就只同步代码块就好了。这就引出了双重检验锁,即在同步块外检查一次null,然后再在同步块内检查一次。但是最终这种方式也是会有问题的,使用静态内部类是一种比较好的方式。
单例模式使用很频繁,也很简单,但不一定都能写对,详细的写法请参考:如何正确地写出单例模式。里面详细的分析的单例模式的各种写法。
其他模式中的示例代码,有很多时候用到了单例模式,此处就不额外添加例子了。
2、工厂方法模式(Factory Method Pattern)
别名:虚拟构造(Another Name:Virtual Constructor)。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
介绍工厂方法模式前,先介绍一下简单工厂模式,简单工厂模式也是一种工厂方法模式。
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
如果一个一些对象(产品),已经确定了并不易改变和添加新的产品,那么久可以使用简单工厂模式。下面就是简单工厂的例子:
//演示简单工厂
public class SimpleFactory {
public static void main(String args[]) throws Exception{
Factory factory = new Factory();
factory.produce("PRO5").run();
factory.produce("PRO6").run();
}
}
//抽象产品
interface MeizuPhone{
void run();
}
//具体产品X2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO5");
}
}
class PRO6 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO6");
}
}
//工厂
class Factory{
MeizuPhone produce(String product) throws Exception{
if(product.equals("PRO5"))
return new PRO5();
else if(product.equals("PRO6"))
return new PRO6();
throw new Exception("No Such Class");
}
}
而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以实现起来是比较麻烦的,那么工厂方法模式正式解决这个问题的,下面就介绍工厂方法模式。
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
针对上面的例子,如果使用工厂方法模式,即将工厂定义为一个接口,然后由具体的工厂来确定需要生成什么样的产品,为了与简单工厂比较,这里还是贴上代码:
- //工厂方法模式
- public class FactoryMethod {
- public static void main(String args[]){
- IFactory bigfactory;
- bigfactory = new SmallFactory();
- bigfactory.produce().run();
- bigfactory = new BigFactory();
- bigfactory.produce().run();
- }
- }
- //抽象产品
- interface MeizuPhone{
- void run();
- }
- //具体产品*2
- class PRO5 implements MeizuPhone{
- @Override
- public void run() {
- System.out.println("我是一台PRO5");
- }
- }
- class MX5 implements MeizuPhone{
- @Override
- public void run() {
- System.out.println("我是一台MX5");
- }
- }
- interface IFactory{//抽象的工厂
- MeizuPhone produce();
- }
- //工厂*2
- class BigFactory implements IFactory{
- @Override
- public MeizuPhone produce() {
- return new PRO5();
- }
- }
- class SmallFactory implements IFactory{
- @Override
- public MeizuPhone produce() {
- return new MX5();
- }
- }
如果对Java容器不熟悉,下面再提供一个例子(模仿Iterator,其实顺便也介绍了Iterator):
如果有多种数据结构要遍历,我们就需要一种用于遍历不同结构的工具,首先我们就需要为这个工具定义一个接口(抽象产品),用于描述如何来遍历:
//只是需要遍历一堆数据,那么只需要2个方法就可以了
public interface Iterator<T> {
boolean hasNext(); //是否还有下一个元素
T next(); //得到下一个元素
}
然后就是我们要遍历的目标,而这些目标此处我们暂定为列表,这就是构造者:
- //便于介绍,不做多的操作
- public interface List<T> {
- Iterator<T> iterator(); //返回一个遍历器
- boolean add(T t); //添加元素到列表
- }
package com.anxpp.designpattern.factorymethod;
//方便演示而实现的简陋的数组list
public class ArrayList<T> implements List<T>{
private int size; //存放的元素个数,会默认初始化为0
private Object[] defaultList; //使用数组存放元素
private static final int defaultLength = 10;//默认长度
public ArrayList(){ //默认构造函数
defaultList = new Object[defaultLength];
}
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
//添加元素
@Override
public boolean add(T t) {
if(size<=defaultLength){
defaultList[size++] = t;
return true;
}
return false;
}
//遍历器(具体产品)
private class MyIterator implements Iterator<T>{
private int next;
@Override
public boolean hasNext() {
return next<size;
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) defaultList[next++];
}
}
}
- //方便演示而实现的简陋的单向链表list
- public class LinkList<T> implements List<T>{
- private int size; //存放的元素个数,会默认初始化为0
- private Node<T> first; //首节点,默认初始化为null
- @Override
- public Iterator<T> iterator() {
- return new MyIterator();
- }
- @Override
- public boolean add(T t) {
- if(size==0){
- first = new Node<T>(t,null);
- size++;
- return true;
- }
- Node<T> node = first;
- while(node.next!=null)
- node = node.next;
- node.next = new Node<T>(t,null);
- size++;
- return true;
- }
- //链表节点
- private static class Node<T>{
- T data;
- Node<T> next;
- Node(T data,Node<T> next){
- this.data = data;
- this.next = next;
- }
- }
- //遍历器
- private class MyIterator implements Iterator<T>{
- private Node<T> next; //下一个节点
- MyIterator(){
- next = first;
- }
- @Override
- public boolean hasNext() {
- return next != null;
- }
- @Override
- public T next() {
- T data = next.data;
- next = next.next;
- return data;
- }
- }
- }
package com.anxpp.designpattern.factorymethod;
public class TestUse {
public static void main(String args[]){
//分别定义两种结构
List<Integer> array = new ArrayList<Integer>();
List<Integer> link = new LinkList<Integer>();
//添加数据
for(int i = 1;i < 8; i++){
array.add(i);
link.add(i);
}
//获得迭代器
Iterator<Integer> ai = array.iterator();
Iterator<Integer> li = link.iterator();
//遍历并输出
while(ai.hasNext())
System.out.print(ai.next());
System.out.println();
while(li.hasNext())
System.out.print(li.next());
}
}
可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
3、抽象工厂方法模式(Abstract Factory Pattern)
提供一个创建一系列或相互依赖对象的接口,而无须指定他们的具体的类。
上述生成魅族产品的例子中,我们只生产了手机,但是它不止有手机一种产品,可能还有其他的,比如耳机,为了还可以生成耳机,我们需要对上例进行扩展。
我们先给出上面生成手机的例子的扩展后的抽象工厂模式代码,以比较这几种模式:
//抽象工厂模式
public class AbstractFactory {
public static void main(String args[]){
IFactory bigfactory = new BigFactory();
IFactory smallfactory = new BigFactory();
bigfactory.producePhone().run();
bigfactory.produceHeadset().play();
smallfactory.producePhone().run();
smallfactory.produceHeadset().play();
}
}
//抽象产品*2
interface Headset{
void play();
}
//抽象产品
interface MeizuPhone{
void run();
}
//具体产品*2*2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO5");
}
}
class MX5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台MX5");
}
}
class EP21 implements Headset{
@Override
public void play() {
System.out.println("我是一副EP21");
}
}
class EP30 implements Headset{
@Override
public void play() {
System.out.println("我是一台EP30");
}
}
//抽象工厂
interface IFactory{
MeizuPhone producePhone();
Headset produceHeadset();
}
//具体工厂*2
class BigFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new PRO5();
}
@Override
public Headset produceHeadset() {
return new EP30();
}
}
//具体工厂*2
class SmallFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new MX5();
}
@Override
public Headset produceHeadset() {
return new EP21();
}
}
这样举例子其实很空洞,这里只是为了比较三种模式,给出抽象的例子才更容易看出区别。
为了演示我们如果实现这个同时能生产Map和Collection的迭代器,我会将例子一步步贴出来:
//抽象工厂
public interface IIteratorFactory<T> {
IIterator<T> iteratorMap(Map<T,Object> m);
IIterator<T> iteratorCollection(Collection<T> c);
}
- //具体产品,Collection迭代器(用到了代理模式)
- public class IteratorCollection<T> implements IIterator<T>{
- Iterator<T> iterator;
- public IteratorCollection(Collection<T> c){
- iterator = c.iterator();
- }
- @Override
- public boolean hasNext() {
- return iterator.hasNext();
- }
- @Override
- public T next() {
- return iterator.next();
- }
- }
//具体产品,Map迭代器(用到了代理模式)
public class IteratorMap<T> implements IIterator<T>{
Iterator<Map.Entry<T, Object>> iterator;
public IteratorMap(Map<T,Object> m){
iterator = m.entrySet().iterator();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Object next() {
return iterator.next().getValue();
}
}
- //具体工厂
- public class IteratorFactory<T> implements IIteratorFactory<T>{
- @Override
- public IteratorMap<T> iteratorMap(Map<T,Object> m) {
- return new IteratorMap<T>(m);
- }
- @Override
- public IteratorCollection<T> iteratorCollection(Collection<T> c) {
- return new IteratorCollection<T>(c);
- }
- }
至此,这个小框架就完成了,我们可以使用它来遍历Collection(List,Set,Queue都是集成自它)和Map:
//测试使用
public class TestUse {
public static void main(String args[]){
IIteratorFactory<Integer> factory = new IteratorFactory<>();
Collection<Integer> collection = new ArrayList<Integer>();
Map<Integer, Object> map = new LinkedHashMap<>();
for(int i=0;i<10;i++){
collection.add(i);
map.put(i, i);
}
IIterator<Integer> iteratorCollection = factory.iteratorCollection(collection);
IIterator<Integer> iteratorMap = factory.iteratorMap(map);
while(iteratorCollection.hasNext())
System.out.print(iteratorCollection.next());
System.out.println();
while(iteratorMap.hasNext())
System.out.print(iteratorMap.next());
}
}
实际情况下,我们可能不应该这么做,以为Collection面向一种对象的容器,Map是面向两种对象的关联容器,但是此例使用抽象工厂模式确实实现了不同容器的 统一遍历方式。
如果一个容器持有的大量对象,他们都直接或间接集成自某一个类,使用访问者模式遍历也是一种很好的方式,具体在后面的访问者模式中会详细介绍。
- 简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。
- 工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
- 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。
4、生成器模式(Builder Pattern)
将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。
- 生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
- 生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。
- 可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。
- 生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。
- 当增加新的具体的生成器是,不必修改指挥者的代码,即该模式满足开-闭原则。
模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。
比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。
接下来是具体生成器,一个以“-”分割年月日,另一个使用空格:
//具体生成器
public class DateBuilder1 implements IDateBuilder{
private MyDate myDate;
public DateBuilder1(MyDate myDate){
this.myDate = myDate;
}
@Override
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+"-"+m+"-"+d;
return this;
}
@Override
public String date() {
return myDate.date;
}
}
- //具体生成器
- public class DateBuilder2 implements IDateBuilder{
- private MyDate myDate;
- public DateBuilder2(MyDate myDate){
- this.myDate = myDate;
- }
- @Override
- public IDateBuilder buildDate(int y, int m, int d) {
- myDate.date = y+" "+m+" "+d;
- return this;
- }
- @Override
- public String date() {
- return myDate.date;
- }
- }
//指挥者
public class Derector {
private IDateBuilder builder;
public Derector(IDateBuilder builder){
this.builder = builder;
}
public String getDate(int y,int m,int d){
builder.buildDate(y, m, d);
return builder.date();
}
}
- public class TestUse {
- public static void main(String args[]){
- MyDate date = new MyDate();
- IDateBuilder builder;
- builder = new DateBuilder1(date).buildDate(2066, 3, 5);
- System.out.println(builder.date());
- builder = new DateBuilder2(date).buildDate(2066, 3, 5);
- System.out.println(builder.date());
- }
- }
往往在实际的应用中,生成器要做的工作不会这么简单,而是相对复杂的(因为其产品一般是比较复杂的),原有构建的维护会转移到生成器的维护上。
5、原型模式(Prototype Pattern)
用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
- 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时。
- 当对象的创建需要独立于它的构造过程和表示时。
- 一个类创建实例状态不是很多,那么就可以将这个类的一个实例定义为原型,那么通过该实例复制该原型得到新的实例可能比重新使用类的构造方法创建新实例更方便
- 当创建类的新实例的代价更大时,使用原型模式复制一个已有的实例可以提高创建新实例的效率。
- 可以动态的保存当前对象的状态。在运行时,可以随时使用对象流保存当前对象的一个复制品。
- 可以在运行时创建新的对象,而无须创建一系列类和集成结构。
- 可以动态的添加、删除原型的复制品。
例子中的抽象原型没有使用方法名clone(),其原因下面会介绍。
//具体原型
public class SimplePrototype implements Prototype,Cloneable {
int value;
//clone()实现
@Override
public Object cloneSelf() {
SimplePrototype self = new SimplePrototype();
self.value = value;
return self;
}
//使用
public static void main(String args[]){
SimplePrototype simplePrototype = new SimplePrototype();
simplePrototype.value = 500;
SimplePrototype simplePrototypeClone = (SimplePrototype) simplePrototype.cloneSelf();
System.out.println(simplePrototypeClone.value);
}
}
//抽象原型
interface Prototype{
Object cloneSelf();//克隆自身的方法
}
//客户端使用
class Client{
SimplePrototype prototype;
public Client(SimplePrototype prototype){
this.prototype = prototype;
}
public Object getPrototype(){
return prototype.cloneSelf();
}
}
简单的原型模式就是在clone()实现时,new一个新的实例,然后为成员变量赋值后返回。
- //使用 java 自带的支持
- public class APITestUse {
- public static void main(String args[]) throws CloneNotSupportedException{
- MyObject myObject = new MyObject();
- myObject.i = 500;
- MyObject myObjectClone = (MyObject) myObject.clone();
- System.out.println(myObjectClone.i);
- }
- }
- //一个可以复制的对象
- class MyObject implements Cloneable{
- int i;
- public Object clone() throws CloneNotSupportedException{
- return super.clone();
- }
- }//结果会输出 500
调用这个方法时,成员变量会自动被复制。所以如果需要使用原型模式,Java原生支持就已经很好用了。
除了以上的原生支持,java中还有一种序列化,只需要对象实现Serializable接口。这样,我们可以将对象写入到流中,可以保存到文件,也可以通过网络发送到其他地方:
//使用Serializable支持克隆
public class SerializablePrototype implements Serializable{
private static final long serialVersionUID = 1L;
private int i;
private transient int notClone;//transient关键字的成员不会被序列化
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public int getNotClone() {
return notClone;
}
public void setNotClone(int notClone) {
this.notClone = notClone;
}
public void writeToFile(String path) throws Exception{
FileOutputStream outStream = new FileOutputStream(path);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);
objectOutputStream.writeObject(this);
outStream.close();
}
public SerializablePrototype ReadFromFile(String path) throws Exception{
File file = new File(path);
if(!file.exists())
file.createNewFile();
FileInputStream inStream = new FileInputStream(path);
ObjectInputStream objectOutputStream = new ObjectInputStream(inStream);
Object o= objectOutputStream.readObject();
inStream.close();
return (SerializablePrototype) o;
}
public static void main(String args[]) throws Exception{
String path = "D:/SerializablePrototype.instance";
SerializablePrototype prototype = new SerializablePrototype();
prototype.setI(123);
prototype.setNotClone(456);
prototype.writeToFile(path);
SerializablePrototype prototypeClone = new SerializablePrototype();
prototypeClone = prototype.ReadFromFile(path);
System.out.println(prototypeClone.getI() + " " + prototypeClone.getNotClone());
}
}//输出:123 0
额...这里是介绍模式,好像说得多了点,那原型模式就介绍到这儿了。
6、责任链模式(Chain of Responsibility Pattern)
使很多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
何时使用
有许多对象可以处理用户请求,希望程序在运行期间自动确定处理用户的那个对象。
希望用户不必明确指定接收者的情况下,想多个接受者的一个提交请求
程序希望动态的指定可处理用户请求的对象集合
优点
低耦合
可以动态的添加删除处理者或重新指派处理者的职责
可以动态改变处理者之间的先后顺序
通常来说,一个纯粹的责任链是先传给第一个处理,如果处理过了,这个请求处理就此结束,如果没有处理,再传给下一个处理者。
比如我们有一个数学公式,有一个整数输入,要求小于0时返回绝对值,其次,小于10的时候返回他的二次幂,否则,返回他本身:
首先我们要定义一个接口(处理者),来描述他们共有的行为:
- //处理者
- public interface Handler {
- int handleRequest(int n);
- void setNextHandler(Handler next);
- }
然后是具体的处理者(3个):
//第一个具体处理者,处理小于0的
public class Handler1 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<0) return -n;
else{
if(next==null)
throw new NullPointerException("next 不能为空");
return next.handleRequest(n);
}
}
@Override
public void setNextHandler(Handler next) {
this.next = next;
}
}
- //第二个具体处理者,处理>=0但小于10的
- public class Handler2 implements Handler {
- private Handler next;
- @Override
- public int handleRequest(int n) {
- if(n<10) return n*n;
- else{
- if(next==null)
- throw new NullPointerException("next 不能为空");
- return next.handleRequest(n);
- }
- }
- @Override
- public void setNextHandler(Handler next) {
- this.next = next;
- }
- }
//第三个具体处理者,处理>=0但小于10的
public class Handler3 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<=Integer.MAX_VALUE) return n;
else{
if(next==null)
throw new NullPointerException("next 不能为空");
return next.handleRequest(n);
}
}
@Override
public void setNextHandler(Handler next) {
this.next = next;
}
}
使用:
- public class TestUse {
- public static void main(String args[]){
- Handler h1,h2,h3;
- h1 = new Handler1();
- h2 = new Handler2();
- h3 = new Handler3();
- h1.setNextHandler(h2);
- h2.setNextHandler(h3);
- System.out.println(h1.handleRequest(-1));
- System.out.println(h1.handleRequest(5));
- System.out.println(h1.handleRequest(9999));
- }
- }
此处责任链中的具体处理者的顺序是不能重设的,否则可能会引发错误,但更多的情况是完全可以随意更改他们的位置的,就上例中,只要把if中的条件重新设置(各自独立,不相互依赖),就可以了。
我们写java web程序的时候,通常会编写一些过滤器(Filter),然后配置到web.xml中,这其实就是责任链模式的一种实践。而使用Log4j记录日志,配置级别的时候,也同样用到了责任链模式。
我们使用责任链模式的时候,不一定非得某一处理者处理后就得终止请求的传递,如果有其他需求,我们依然可以继续传递这个请求到下一个具体的处理者。
7、命令模式(Command Pattern)
别名:动作,事物(Another Name:Action,Transaction)
将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
- 在命令模式中,请求者(Invoker)不直接与接受者(Receiver)交互,及请求者(Invoker)不包含接受者(Receiver)的引用,因此彻底消除了彼此间的耦合。
- 命令模式满足“开-闭原则”。如果增加新的具体命令和该命令的接受者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有具体命令和接收者,新增加的调用者就可以使用已有的具体命令。
- 由于请求者的请求被封装到具体的命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此,使用命令模式可以记录日志。
- 使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按一定顺序执行这些具体命令。
一个对象有多种操作,但是我们不希望调用者(请求者)直接使用,我们就额外添加一个对象,然后让调用者通过这个对象来使用那些操作。
比如,我们有一个类可以在磁盘上新建或是删除文件(接收者),但是我们不希望直接提供给别人(请求者)使用,所以我们就为它的各种操作创建对应的命令,下面我们用代码来实现这个需求:
//接收者
public class MakeFile {
//新建文件
public void createFile(String name) throws IOException{
File file = new File(name);
file.createNewFile();
}
//删除文件
public boolean deleteFile(String name){
File file = new File(name);
if(file.exists()&&file.isFile()){
file.delete();
return true;
}
return false;
}
}
//新建文件命令
public class CommandCreate implements Command {
MakeFile makeFile;
public CommandCreate(MakeFile makeFile) {
this.makeFile = makeFile;
}
@Override
public void execute(String name) throws Exception {
makeFile.createFile(name);
}
}
- //删文件命令
- public class CommandDelete implements Command{
- MakeFile makeFile;
- public CommandDelete(MakeFile makeFile) {
- this.makeFile = makeFile;
- }
- @Override
- public void execute(String name) throws Exception {
- makeFile.deleteFile(name);
- }
- }
//请求者
public class Client {
Command command;
public Client setCommand(Command command){
this.command = command;
return this;
}
public void executeCommand(String name) throws Exception{
if(command==null)
throw new Exception("命令不能为空!");
command.execute(name);
}
}
- public class TestUse {
- public static void main(String args[]) throws Exception{
- //接收者
- MakeFile makeFile = new MakeFile();
- //命令
- CommandCreate create = new CommandCreate(makeFile);
- CommandDelete delete = new CommandDelete(makeFile);
- //请求者
- Client client = new Client();
- //执行命令
- client.setCommand(create).executeCommand("d://test1.txt");
- client.setCommand(create).executeCommand("d://test2.txt");
- client.setCommand(delete).executeCommand("d://test2.txt");
- }
- }//执行完后在D盘会有一个test1.txt的文件,test2.txt本页创建了,不过又被删除了。。
命令模式不宜滥用,比如:使用这种模式,会多出来很多对象(命令)。
命令模式中还有一种具体命令叫宏命令,它会包含一些其他具体命令的引用,执行宏命令可以执行这个宏命令所包含的引用的命令,概念清楚后实现起来也是容易的:
比如输出文章的命令,有中文输出命令、英文输出命令和宏命令,宏命令包含了其他两个命令的引用(可以使用列表保存这些命令),如果执行宏命令,宏命令会一次执行它所包含引用的其他命令(迭代命令列表并执行即可)。
8、解释器模式(Interpreter Patterm)
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
概念其实很简单。在有些问题上,我们可能希望自定定义简单的语言来描述,然后我们自己能解释它。
这种模式一般会应用到一些特殊的问题上,使用这种模式一般需要了解形式语言中的基本知识。js内核就是一个强大的解释器。
简单的解释器模式,我们需要解释出来表达式的信息即可;而更深一层的,我们需要把表达式中的内容,翻译成我们程序运行的一部分来执行。
本初不提供例子,理解概念即可。有需求的时候再深入学习。如果确实需要举例,请在回复中说明,我会更新文章并添加一些内容。
9、迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素,而由不需要暴露该对象的内部细节。
通常容器提供的迭代器时可以高速遍历它本身的,而使用其本身的机制(如LinkedList中使用get(i)方法遍历)遍历性能可能并不好。
其实这个在工厂方法模式给出的例子就足够解释这个模式的使用了,如需看具体代码实现,请移步工厂方法模式中的例子查看。
迭代器其实很简单,下面我们就继续工厂方法模式中的例子,将它完善一下:
//集合接口
public interface MyList<T> {
MyIterator<T> iterator(); //返回一个遍历器
boolean add(T t); //添加元素到列表
T get(int index); //得到元素
T remove(); //删除最后一个元素
boolean remove(T element); //删除指定元素
T remove(int index); //删除指定位置元素
boolean set(int index,T element); //修改指定位置值
int size();
}
- public class MyArrayList<T> implements MyList<T>{
- private int size; //存放的元素个数,会默认初始化为0
- private Object[] defaultList; //使用数组存放元素
- private static final int defaultLength = 10;//默认长度
- public MyArrayList(){ //默认构造函数
- defaultList = new Object[defaultLength];
- }
- @Override
- public MyIterator<T> iterator() {
- return new Iterator();
- }
- //大小自动增长
- private void ensureCapacity(int capacity){
- int nowLength = defaultList.length;
- if(capacity >= nowLength){
- nowLength = nowLength + (nowLength>>1);
- if(nowLength<0)//溢出
- nowLength = Integer.MAX_VALUE;
- defaultList = Arrays.copyOf(defaultList, nowLength);
- }
- }
- //添加元素
- @Override
- public boolean add(T t) {
- ensureCapacity(size+1);
- defaultList[size++] = t;
- return true;
- }
- //获取元素
- @SuppressWarnings("unchecked")
- @Override
- public T get(int index) {
- if(index<0 || index>=size) return null;
- return (T) defaultList[index];
- }
- @Override
- public T remove() {
- return remove(size-1);
- }
- @SuppressWarnings("unchecked")
- @Override
- public T remove(int index) {
- if(index<0||index>=size) return null;
- T element = (T) defaultList[index];
- if(index != size-1)
- System.arraycopy(defaultList, index+1, defaultList, index,size-index-1);
- size--;
- return element;
- }
- @Override
- public boolean remove(T element) {
- if(element==null){
- for(int i = 0 ; i<size;i++)
- if(defaultList[i]==null){
- System.arraycopy(defaultList, i+1, defaultList, i,size-i-1);
- size--;
- return true;
- }
- }
- else{
- for(int i = 0 ; i<size;i++)
- if(defaultList[i].equals(element)){
- System.arraycopy(defaultList, i+1, defaultList, i,size-i-1);
- size--;
- return true;
- }
- }
- return false;
- }
- @Override
- public boolean set(int index,T element) {
- if(index<0||index>=size) return false;
- defaultList[index] = element;
- return true;
- }
- @Override
- public int size() {
- return size;
- }
- //迭代器
- private class Iterator implements MyIterator<T>{
- private int next;
- @Override
- public boolean hasNext() {
- return next<size;
- }
- @SuppressWarnings("unchecked")
- @Override
- public T next() {
- return (T) defaultList[next++];
- }
- @Override
- public T remove() {
- // TODO Auto-generated method stub
- return null;
- }
- }
- }
public class MyLinkedList<T> implements MyList<T>{
private int size; //存放的元素个数,会默认初始化为0
private Node<T> first; //首节点,默认初始化为null
@Override
public MyIterator<T> iterator() {
return new Iterator();
}
@Override
public boolean add(T t) {
if(size==0){
first = new Node<T>(t,null);
size++;
return true;
}
Node<T> node = first;
while(node.next!=null)
node = node.next;
node.next = new Node<T>(t,null);
size++;
return true;
}
@Override
public T get(int index) {
Node<T> node = first;
while(--index>=0)
node = node.next;
return node.data;
}
@Override
public T remove() {
return remove(size-1);
}
@Override
public T remove(int index) {
if(index<0||index>=size) return null;
Node<T> node = first;
while(--index>0)
node = node.next;
T element = node.next.data;
node.next = node.next.next;
size--;
return element;
}
@Override
public boolean remove(T element) {
if(element == null){
if(first.data==null){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data==null){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next!=null);
}
else{
if(first.data.equals(element)){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data.equals(element)){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next!=null);
}
return false;
}
@Override
public boolean set(int index, T element) {
if(index<0||index>=size) return false;
Node<T> node = first;
while(--index>0)
node = node.next;
node.data = element;
return true;
}
@Override
public int size() {
return size;
}
//链表节点
private static class Node<T>{
T data;
Node<T> next;
Node(T data,Node<T> next){
this.data = data;
this.next = next;
}
}
//遍历器
private class Iterator implements MyIterator<T>{
private Node<T> next; //下一个节点
Iterator(){
next = first;
}
@Override
public boolean hasNext() {
return next!=null;
}
@Override
public T next() {
T data = next.data;
next = next.next;
return data;
}
@Override
public T remove() {
// TODO Auto-generated method stub
return null;
}
}
}
具体的迭代器就是集合具体实现里面的迭代器内部类,下面是使用 :
public class TestUse {
public static void main(String args[]){
//分别定义两种结构
MyList<String> array = new MyArrayList<String>();
MyList<String> link = new MyLinkedList<String>();
//添加数据
for(int i = 1;i < 15; i++){
array.add(i+"");
link.add(i+"");
}
//数组操作
System.out.println(array.remove());
System.out.println(array.get(5));
System.out.println(array.remove(5));
System.out.println(array.get(5));
System.out.println(array.remove("7"));
System.out.println(array.set(0, "00"));
//使用迭代器
MyIterator<String> ai = array.iterator();
while(ai.hasNext())
System.out.print(ai.next()+" ");
System.out.println();
System.out.println(link.remove());
System.out.println(link.get(5));
System.out.println(link.remove(5));
System.out.println(link.get(5));
System.out.println(link.remove("7"));
System.out.println(link.set(0, "00"));
//使用迭代器
MyIterator<String> li = link.iterator();
while(li.hasNext())
System.out.print(li.next()+" ");
System.out.println();
System.out.println("a size=" + array.size());
System.out.println("l size=" + link.size());
}
}
- 14
- 6
- 6
- 7
- true
- true
- 00 2 3 4 5 8 9 10 11 12 13
- 14
- 6
- 6
- 7
- true
- true
- 00 2 3 4 5 8 9 10 11 12 13
- a size=11
- l size=11
这里的迭代器就是典型的迭代器模式的实现(不过此处的迭代器没有实现remove()方法,查找集合的remove()方法实现也简单),介绍得有点多了,把集合都给介绍了...
10、中介者模式(Mediator Pattern)
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之前的交互。
两个类直接关联,是很好实现的,但如果不希望两个类直接发生交互,那么就需要使用中介者模式了。
//同事(接口)
public interface IPersistent {
void getData(Object data);
void getData(Object data,Midiator midiator);
void saveData();
}
- //具体同事
- public class PersistentFile implements IPersistent{
- private Object data;
- @Override
- public void getData(Object data, Midiator midiator) {
- getData(data);
- midiator.notifyOther(this, data);
- }
- @Override
- public void saveData() {
- System.out.println(data + " 已保存到文件");
- }
- @Override
- public void getData(Object data) {
- this.data = data;
- saveData();
- }
- }
//具体同事
public class PersistentDB implements IPersistent{
private Object data;
@Override
public void getData(Object data, Midiator midiator) {
getData(data);
midiator.notifyOther(this, data);
}
@Override
public void saveData() {
System.out.println(data + " 已保存到数据库");
}
@Override
public void getData(Object data) {
this.data = data;
saveData();
}
}
- //具体中介者
- public class Midiator {
- PersistentDB persistentDB;//此处可以使用List来存放所有的同事
- PersistentFile persistentFile;
- public Midiator setPersistentDB(PersistentDB persistentDB) {
- this.persistentDB = persistentDB;
- return this;
- }
- public Midiator setPersistentFile(PersistentFile persistentFile) {
- this.persistentFile = persistentFile;
- return this;
- }
- public void notifyOther(IPersistent persistent,Object data){
- if(persistent instanceof PersistentDB)//如果同事都放在List中,此处遍历即可
- persistentFile.getData(data);
- if(persistent instanceof PersistentFile)
- persistentDB.getData(data);
- }
- }
public class TestUse {
public static void main(String args[]){
Object data = "数据";
PersistentDB persistentDB = new PersistentDB();
PersistentFile persistentFile = new PersistentFile();
Midiator midiator = new Midiator();
midiator.setPersistentDB(persistentDB).setPersistentFile(persistentFile);
persistentDB.getData(data, midiator);
persistentFile.getData(data, midiator);
}
}//输出(省略了换行符):数据 已保存到数据库数据 已保存到文件数据 已保存到文件数据 已保存到数据库
就上例,如果还有许多的持久化组件(具体同事),可以在中介者中使用一个List来存放他们的引用,set的时候就添加。在通知其他同事时,遍历这个List,除了参数本身这个同事,其他的依次通知,即可实现。
11、备忘录模式(Memento Pattern)
别名:标记(Another Name:Token)
Without violating encapsulation,captrue and externalize an object' orifianl state so that the object can be restored to this state later.
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存该状态,这样就可以将该对象恢复到之前保存的状态。
何时使用:
必须保存一个对象在某一时刻的全部或部分状态,以便在需要时恢复该对象先前的状态。
一个对象不想通过提供public权限的,诸如getXXX()的方法让其他对象得到自己IDE内部状态。
优点:
备忘录模式使用备忘录可以吧原先者的内部状态全部保存起来,使是有很“亲密”的对象可以访问备忘录中的数据。
备忘录模式强调了类设计单一责任的原则,即将状态的刻画和保存分开。
备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。 备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。
备忘录模式中有三种角色:
备忘录(Memento)角色:将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
发起人(Originator)角色:创建一个含有当前的内部状态的备忘录对象。使用备忘录对象存储其内部状态。
负责人(Caretaker)角色:负责保存备忘录对象。不检查备忘录对象的内容。
先看一个简单的实现方式:
- //简单的备忘录模式
- public class SimpleMemento {
- public static void main(String[] args) throws Exception {
- Originator originator = new Originator(); //发起人,要被保存的对象,也是他创建要保存的信息的
- Caretaker caretaker = new Caretaker(); //辅助保存的对象
- originator.setState("stateOne"); //设置状态
- caretaker.saveMemento(originator.createMemento()); //保存状态
- originator.setState("stateTwo"); //修改状态
- originator.recoverMemento(caretaker.recoverMemento()); //恢复状态
- }
- }
- //发起人
- class Originator {
- private String state;
- public Memento createMemento(){
- return new Memento(state);
- }
- public void recoverMemento(Memento memento){
- this.state = memento.getState();
- }
- public String getState() {
- return state;
- }
- public void setState(String state) {
- this.state = state;
- }
- }
- //备忘录
- class Memento {
- private String state;
- public Memento(String state){
- this.state = state;
- }
- public String getState() {
- return state;
- }
- public void setState(String state) {
- this.state = state;
- }
- }
- //负责人
- class Caretaker {
- private Memento memento;
- public Memento recoverMemento() throws Exception{
- if(memento==null)
- throw new Exception("没有保存的状态");
- return this.memento;//恢复状态
- }
- public void saveMemento(Memento memento){
- this.memento = memento;//保存状态
- }
- }
备忘录角色对任何对象都提供一个接口,备忘录角色的内部所存储的状态就对所有对象公开,因此是破坏封装性的。
按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。
如果上例中,我们把备忘录以发起人的私有内部类的方式实现的话,那它就只能被发起人访问了,这正好就符合备忘录模式的要求,但是我们的负责人是需要存放备忘录的引用的,于是,我们提供一个公共的接口,他是空的,我们用备忘录实现它,主要就是利用其中的类型信息,具体实现如下:
//备忘录模式
public class BlackMemento {
public static void main(String[] args) {
BlankOriginator originator = new BlankOriginator(); //发起人
BlackCaretaker caretaker = new BlackCaretaker(); //负责人
originator.setState("stateOne"); //设置状态
caretaker.saveMemento(originator.createMemento()); //保存信息
originator.setState("stateTwo"); //修改状态
originator.recoverMemento(caretaker.recoverMemento());//恢复状态
}
}
interface MementoIF {}
//发起人
class BlankOriginator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public MementoIF createMemento(){
return new Memento(state);
}
public void recoverMemento(MementoIF memento){
this.setState(((Memento)memento).getState());
}
//以内部类实现备忘录角色
private class Memento implements MementoIF{
private String state;
private Memento(String state){
this.state = state;
}
private String getState() {
return state;
}
}
}
//负责人
class BlackCaretaker {
private MementoIF memento;
public MementoIF recoverMemento(){
return memento;
}
public void saveMemento(MementoIF memento){
this.memento = memento;
}
}
上面两个例子,演示的都是保存一个状态(不是指一个成员,而是只存了最近一次状态),即一个检查点,但是实际应用中,状态往往不止存储一次,我们将上面储存状态的变量改为一个栈(或队列,主要看需求)即可。比如:BlackCaretaker中的private MementoIF memento;改为LinkedList<MementoIF> mementos 实现,保存的时候压栈(入队),恢复的时候出栈(出队)。具体实现都已经描述很清楚了,代码就不贴了(文章本来就太长了)。
针对上例,如果发起人和负责人我们并不介意他们必须是独立的,就可以把他们融合到一起,实现就会更佳简单,代码也简洁:
- //自述历史备忘录
- public class MementoSelf {
- public static void main(String[] args) {
- OriginatorCaretaker originatorCaretaker = new OriginatorCaretaker();//发起人,同时为负责人
- originatorCaretaker.changeState("stateOne"); //改变状态
- IMemento memento = originatorCaretaker.createMemento(); //保存状态
- originatorCaretaker.changeState("stateTwo"); //改变状态
- originatorCaretaker.recoverMemento(memento); //恢复状态
- }
- }
- interface IMemento {}
- //发起人兼负责人
- class OriginatorCaretaker {
- public String state;
- public void changeState(String state){
- this.state = state;
- }
- //创造快照
- public Memento createMemento(){
- return new Memento(this);
- }
- //恢复状态
- public void recoverMemento(IMemento memento){
- Memento m = (Memento)memento;
- changeState(m.state);
- }
- //内部类实现备忘录
- private class Memento implements IMemento{
- private String state;
- private Memento(OriginatorCaretaker originatorCaretaker){
- this.state = originatorCaretaker.state;
- }
- }
- }
上例演示仅保存一个检查点。下面再给出一个实际的例子:
我们有个程序,供用户编辑文本,用户做出修改后,可以保存文本,保存修改后,可以依次恢复到保存前的多个状态中的一个,如果恢复后用户没有修改,还可以取消恢复(重做),下面就演示整个程序。
这个程序为了保证功能相对完整,写作演示可能有点长了:
//文本编辑器
public class TextEditor {
public static void main(String[] args) {
//使用这个文本编辑器
MyTextEditor editor = new MyTextEditor("这里是初始文本,可能为文件中读取的值。");
System.out.println("开始修改文本:");
editor.append("添加文字1");
editor.delWords(); //删除最后一个
// editor.delWords(2); //删除最后2个 这两个方法是没有问题的,这里避免控制台输出太多,取消这两次修改
// editor.delWords(1,5); //删除前面5个
System.out.println("开始恢复:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
System.out.println("开始重做:");
for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
System.out.println("再次恢复:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
System.out.println("再次重做:");
for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
System.out.println("再次恢复:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
editor.append("添加文字2");
System.out.println("再次重做:");
for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
}
}
interface IMemento {}
//发起人兼负责人
class MyTextEditor {
public StringBuffer text;
private LinkedList<IMemento> mementos; //保存快照
private LinkedList<IMemento> undos; //保存撤销的操作
public MyTextEditor(){
this("");
}
public MyTextEditor(String defaultStr){
text = new StringBuffer(defaultStr);
mementos = new LinkedList<IMemento>();
undos = new LinkedList<IMemento>();
print();
}
public void clearHistory(){
mementos.clear();
undos.clear();
}
public void append(String appendStr){
if(appendStr==null||appendStr.length()==0) return;
createMemento();
text.append(appendStr);
print();
undos.clear();
}
//删除最后一个
public void delWords(){
delWords(1);
}
//删除最后n个
public void delWords(int n){
if(n<1||n>text.length()) return;
delWords(text.length()-n+1,text.length());
}
//删除中间start到end的字符,第一个文字为第一个(而不是0)
public void delWords(int start,int end){
if(start<1 || end>text.length()+1) return;
createMemento();
text = text.delete(start-1, end);
print();
}
public void reset(String text){
this.text = new StringBuffer(text);
}
//新的快照
public void createMemento(){
mementos.push(new Memento(this));
}
//恢复状态
public boolean recoverMemento(){
Memento memento = (Memento) mementos.poll();
if(memento==null) return false;
undos.push(new Memento(this));
reset(memento.state);
print();
return true;
}
//redo,redo的操作也可以恢复!
public boolean redo(){
Memento memento = (Memento) undos.poll();
if(memento==null) return false;
createMemento();
reset(memento.state);
print();
return true;
}
//内部类实现备忘录
private class Memento implements IMemento{
private String state;
private Memento(MyTextEditor editor){
this.state = editor.text.toString();
}
}
void print(){
System.out.println("当前文本:" + text);
}
}
控制台输出:
- 当前文本:这里是初始文本,可能为文件中读取的值。
- 开始修改文本:
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字
- 开始恢复:
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
- 当前文本:这里是初始文本,可能为文件中读取的值。
- 开始重做:
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字
- 再次恢复:
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
- 当前文本:这里是初始文本,可能为文件中读取的值。
- 再次重做:
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字
- 再次恢复:
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
- 当前文本:这里是初始文本,可能为文件中读取的值。
- 当前文本:这里是初始文本,可能为文件中读取的值。添加文字2
- 再次重做:
可以看到功能都是正确的,最后的重做因为在恢复后有修改发生,所以重做是无效的(目前我们所用的编辑器都是这种策略)。多次的恢复和重做是没有问题的。
该例子就是备忘录模式典型的例子。
12、观察者模式(Observer Pattern)
别名: 依赖,发布/订阅(Another Name: Dependents, Publish/Subscribe)
定义对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
比如我们有个天气服务(主题),然后有多个使用它的客户端(观察者),包括android和iphone端app的服务(观察者),那么就可以使用这么模式。
我们需要一种结构存放天气信息(注意,省略了get、set方法!):
//天气的消息实体
public class WeatherInfo {
private long time;
private String weather;
public WeatherInfo(long time,String weather){
this.time = time;
this.weather = weather;
}
@Override
public boolean equals(Object obj) {
WeatherInfo info = (WeatherInfo) obj;
return info.time==this.time&&info.weather.equals(this.weather);
}
}
然后我们定义天气服务的接口(主题),以表示它应实现哪些功能:
- //主题
- public interface IWeatherService {
- void addClient(Client client); //添加观察者
- boolean deleteClient(Client client);//删除观察者
- void notifyClients(); //通知
- void updateWeather(WeatherInfo info);//主题内容更新
- }
- //具体主题
- public enum WeatherService implements IWeatherService{
- instance;
- private LinkedList<WeatherInfo> weatherInfos = new LinkedList<WeatherInfo>();
- private LinkedHashSet<Client> clients = new LinkedHashSet<Client>(); //存放观察者
- //添加观察者
- @Override
- public void addClient(Client client) {
- clients.add(client);
- }
- //删除观察者
- @Override
- public boolean deleteClient(Client client) {
- return clients.remove(client);
- }
- //通知观察者
- @Override
- public void notifyClients() {
- Iterator<Client> iterator = clients.iterator();
- while(iterator.hasNext()){
- iterator.next().getWeather(weatherInfos.peekFirst());
- }
- }
- //更新天气
- @Override
- public void updateWeather(WeatherInfo info) {
- if(weatherInfos.size()>0)
- if(weatherInfos.peekFirst().equals(info)) return;
- weatherInfos.push(info);
- if(clients.size()==0) return;
- notifyClients();
- }
- }
public class ClientAndroidServer implements Client {
private static String name = "安卓服务";
private WeatherInfo info;
@Override
public void getWeather(WeatherInfo info) {
this.info = info;
dealMsg();
}
private void dealMsg(){
System.out.println(name + "收到最新天气:time="+info.getTime()+"msg="+info.getWeather()+"。马上开始推送消息...");
}
}
- public class ClientIphoneServer implements Client {
- private static String name = "苹果服务";
- private WeatherInfo info;
- @Override
- public void getWeather(WeatherInfo info) {
- this.info = info;
- dealMsg();
- }
- private void dealMsg(){
- System.out.println(name + "收到最新天气:time="+info.getTime()+"msg="+info.getWeather()+"。马上开始推送消息...");
- }
- }
public class TestUse {
public static void main(String args[]){
//创建主题
WeatherService service = WeatherService.instance;
//添加观察者
service.addClient(new ClientAndroidServer());
service.addClient(new ClientIphoneServer());
//更新主题
service.updateWeather(new WeatherInfo(System.currentTimeMillis(), "多云"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24, "多云转晴"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24*2, "晴"));
}
}
- 安卓服务收到最新天气:time=1461246047007msg=多云。马上开始推送消息...
- 苹果服务收到最新天气:time=1461246047007msg=多云。马上开始推送消息...
- 安卓服务收到最新天气:time=1461332447007msg=多云转晴。马上开始推送消息...
- 苹果服务收到最新天气:time=1461332447007msg=多云转晴。马上开始推送消息...
- 安卓服务收到最新天气:time=1461418847007msg=晴。马上开始推送消息...
- 苹果服务收到最新天气:time=1461418847007msg=晴。马上开始推送消息...
可以看出,观察者模式是一对多的。而本例是将更新的内容整个推给客户端。
推的方式会将主题更改的内容全部直接推给客户端,拉的方式就是主题的数据更新后,不直接将数据推给客户端,而是先推送一个通知并提供对应的方法供客户端拉取数据。
java.util包中也提供了观察者模式的支持,因为java程序设计中使用比较广泛。有一个Observable类(相当于这里的具体主题)和一个Observer接口(相当于这里的主题接口):
- public class Observable {
- private boolean changed = false;
- private Vector<Observer> obs;
- public Observable() { obs = new Vector<>();}
- public synchronized void addObserver(Observer o) {
- if (o == null) throw new NullPointerException();
- if (!obs.contains(o)) { obs.addElement(o); }
- }
- public synchronized void deleteObserver(Observer o) { obs.removeElement(o); }
- public void notifyObservers() { notifyObservers(null); }
- public void notifyObservers(Object arg) {
- Object[] arrLocal;
- synchronized (this) {
- if (!changed) return;
- arrLocal = obs.toArray();
- clearChanged();
- }
- for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);
- }
- public synchronized void deleteObservers() { obs.removeAllElements(); }
- protected synchronized void setChanged() { changed = true; }
- protected synchronized void clearChanged() { changed = false; }
- public synchronized boolean hasChanged() { return changed; }
- public synchronized int countObservers() { return obs.size(); }
- }
13、状态模式(State Pattern)
别名:状态对象(Another Name:Objects for States)
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
- 使用一个类封装对象的一种状态,很容易增加新的状态
- 在状态模式中,环境(Context)中不必出现大量的条件判断语句。环境(Context)实例所呈现的状态变得更加清晰、容易理解。
- 使用状态模式可以让用户程序很方便地切换环境(Context)实例的状态。
- 使用状态模式不会让环境(Context)中的实例中出现内部状态不一致的情况。
- 当状态对象没有实例变量时,环境(Context)的各个实例可以共享一个状态对象。
用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
可能这段时间老是在想数据库相关的事儿,所以一想例子就就想到这方面来了...不过,这样大家也能更好的对比设计模式之间的差异,下本例还是与这方面相关的。
//环境(Context)
public class SaveDataController {
private ISaveData saveData;
public void save(String data){
//为了演示,此处的大的数据其实也是很小的
if(data.length()<1<<2)
saveData = SaveSmallData.instance;
else if(data.length()<1<<4)
saveData = SaveMiddleData.instance;
else
saveData = SaveBigData.instance;
saveData.save(data);
}
}
//具体状态
public enum SaveSmallData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("保存到Redis:" + data);
}
}
- //具体状态
- public enum SaveMiddleData implements ISaveData{
- instance;
- @Override
- public void save(Object data) {
- System.out.println("保存到Mysql:" + data);
- }
- }
//具体状态
public enum SaveBigData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("保存到文件:" + data);
}
}
- public class TestUse {
- public static void main(String args[]){
- String smallData = "小数据";
- String middleData = "介于小数据和大数据之间的数据";
- String bifgData = "这里就假定这是一个很大很大很大的数据";
- SaveDataController saveDataController = new SaveDataController();
- saveDataController.save(smallData);
- saveDataController.save(middleData);
- saveDataController.save(bifgData);
- }
- }
可以看到,我们对三种数据都使用了同一个对象的相同方法,但是行为是不同的,因为他们的状态不一样。
上面例子的状态更改是自动的,也可以添加setState()方法,手动切换状态,并在执行的方法体中不在自动判断状态。不过自动判断的,更智能一些,而手动切换状态的,可控性更好。
14、策略模式(Strategy Pattern)
定义一系列算法,把他们一个个封装起来,并且使他们可相互替换。本模式使得算法可独立于其他客户端而变化。
何时使用
优点
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。下面就以一个示意性的实现讲解策略模式实例的结构。
策略模式中包括三种角色:
策略(Strategy):一个接口,定义了若干个算法(抽象方法)。
具体策略(ConcreteStrategy):策略的实现。
上下文/环境(Context):依赖于策略接口的类。
策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。 这其实也是典型的将代码向继承等级结构的上方集中的标准做法。
上次我们使用状态模式将数据按不同状态保存到不同地方,这里,我们使用策略模式来实现通过不同的策略来选择数据的保存方式。
首先是抽象的数据保持类(策略):
- //策略
- public interface ISaveData {
- void save(Object data);
- }
然后是具体的数据保存类,三个(具体策略):
public class SaveToRedis implements ISaveData {
@Override
public void save(Object data) {
System.out.println("数据:" + data + " 保存到Redis");
}
}
- //具体策略
- public class SaveToFile implements ISaveData {
- @Override
- public void save(Object data) {
- System.out.println("数据:" + data + " 保存到文件");
- }
- }
//具体策略
public class SaveToMysql implements ISaveData {
@Override
public void save(Object data) {
System.out.println("数据:" + data + " 保存到Mysql");
}
}
最后是客户端(环境Context):
- //环境
- public class SaveClient {
- private ISaveData saveData;
- public SaveClient(ISaveData saveData){
- this.saveData = saveData;
- }
- public void setSaveData(ISaveData saveData){
- this.saveData = saveData;
- }
- public void save(Object data){
- saveData.save(data);
- }
- }
使用:
public class TestUse {
public static void main(String args[]){
Object data = "数据";
ISaveData saveData = new SaveToRedis();
SaveClient client = new SaveClient(saveData);
client.save(data);
client.setSaveData(new SaveToFile());
client.save(data);
}
}
这里数据的保存就是根据使用的时候设置的策略来决定。
使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
15、模板方法模式(Template Method Pattern)
Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
何时使用:
设计者需要给出一个算法的固定步骤,并将某些步骤的具体实现留给子类来实现。
需要对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中以避免代码重复。
优点:
可以通过在抽象摸吧能定义模板方法给出成熟的算法步骤,同时又不限制步骤的细节,具体模板实现算法细节不会改变整个算法的骨架。
在抽象模板模式中,可以通过钩子方法对某些步骤进行挂钩,具体模板通过钩子可以选择算法骨架中的某些步骤。
模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。 模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。
例如,我们有这样的操作:首先得到一些数据,然后计算这些数据,最后再输出数据,至于这些操作如何实现(当然,一些方法也可以提前实现),我们没有要求,但是这些操作的先后逻辑我们已经确定好了,子类不能改变:
抽象模板:
- //抽象模板
- public abstract class AbstractTemplate {
- Object data;
- //这个就是模板方法
- void dealData(){
- getData();
- calcData();
- printData();
- }
- //下面是普通方法,可能已经实现,也可能需要子类实现
- abstract void getData();
- abstract void calcData();
- void printData(){
- System.out.println(data);
- }
- }
具体模板:
//具体模板
public class Template extends AbstractTemplate {
@Override
void getData() {
data = "data";
}
@Override
void calcData() {
data = (String)data+data;
}
}
使用:
- public class TestUse {
- public static void main(String args[]){
- Template template = new Template();
- template.dealData();
- }
- }
模板方法也比较简单,但是非常常用,如Android中Activity中生命周期的一些方法,都会被按序调用,也用到了这种设计模式。同样的,我们通常会使用一些封装好的http请求库,里面的实现要我们自己写,但是逻辑都已经规定好了,不也是模板方法模式么。总之,模板方法模式使用是非常广泛的。
16、访问者模式(Visitor Pattern)
表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。
- 一个对象结构中,比如某个集合中,包含很多对象,想对集合中的对象增加一些新的操作。
- 需要对集合中的对象进行很多不同的并且不相关的操作,而不想修改对象的类,就可以使用访问者模式。访问者模式可以在Visitor类中集中定义一些关于集合中对象的操作。
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
这个模式可能稍微难理解一点,希望读者一点一点读下去,碰到不清楚的先跳过,看完例子再回过头来基本就清楚是怎么回事了。
变量被声明时的类型叫做变量的静态类型(Static Type),而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type),如:
这个list变量的静态类型是List,而它的实际类型是ArrayList。根据对象的类型而对方法进行的选择,就是分派(Dispatch)。分派又分为两种:静态分派和动态分派。
静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派(Dynamic Dispatch)发生在运行时期,动态分派动态地置换掉某个方法。
- public class Dispatch {
- void print(FatherClass c){
- System.out.print("父类");
- }
- void print(ChildClass c){
- System.out.print("子类");
- }
- public static void main(String args[]){
- FatherClass child = new ChildClass();
- new Dispatch().print(child);
- child.print();
- }
- }
- class FatherClass{
- void print(){
- System.out.println("父类");
- }
- }
- class ChildClass extends FatherClass{
- void print(){
- System.out.print("子类");
- }
- }//输出:父类子类
java的方法重写是根据实际类型来的(动态分派),编译器编译时并不知道其真实类型,而是运行时动态决定的。
一个对象又叫做它所包含的方法的接收者,java中的动态分派,要调用哪一个方法,是由这个对象的真实类型决定的。
OK,讲到重点了,访问者模式正是实现双重分派的模式。java中通过两次方法调用来实现两次分派。
public class MultiDispatch {
public static void main(String args[]){
Child child = new Child();
child.print();
child.print(new Vistor());
}
}
class Father{
void print(){
System.out.println("父类");
}
}
class Child extends Father{
void print(){
System.out.print("子类");
}
void print(Vistor c){
c.print(this);
}
}
class Vistor {
public void print(Child child){
child.print();
}
}//输出:子类子类
这样,动态双重分派也算是完成了(通过调用一个其它对象的方法,传入自己,在其他类的这个方法中再通过传入的这个参数调用自己。如果还是不清楚,请继续看下面的例子)。那么这个有什么用呢?下面继续解释。
比如我们有个app需要接收用户的反馈,用户由会员和普通用户,因为反馈太多,并不是所有反馈都会被记录到有效反馈表中,是否记录为有效通常不是用户说了算,而是有我们自己定。
//普通用户,具体元素
public class UserOrdinary implements User{
String estimation;
public UserOrdinary(String estimation){
this.estimation = estimation;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);//这个就是重点,第一次分派是调用accept()方法时根据接收者的实际类型来调用的,第二次分派就是通过visitor.visit(this),传入静态类型,然后再visit()方法中反过来调用this本身的方法。
}
String getEstimation(){
return estimation;
}
}
- //VIP用户,具体元素
- public class UserVIP implements User{
- String estimation;
- public UserVIP(String estimation){
- this.estimation = estimation;
- }
- @Override
- public void accept(Visitor visitor) {
- visitor.visit(this);
- }
- String getEstimation(){
- return estimation;
- }
- }
- //具体访问者
- public class APPOwner implements Visitor{
- @Override
- public void visit(UserVIP user) {
- String estimation = user.getEstimation();
- if(estimation.length()>5)
- System.out.println("记录一条有效反馈:" + estimation);
- }
- @Override
- public void visit(UserOrdinary user) {
- String estimation = user.getEstimation();
- if(estimation.length()>10)
- System.out.println("记录一条有效反馈:" + estimation);
- }
- }
public class TestUse {
public static void main(String args[]){
Visitor appOwner = new APPOwner();
ArrayList<User> users = new ArrayList<User>();
users.add(new UserOrdinary("普通用户短反馈"));
users.add(new UserOrdinary("这是一个普通用户的比较长的反馈"));
users.add(new UserVIP("VIP用户的短反馈"));
users.add(new UserVIP("VIP用户的比较长的反馈反馈"));
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){
iterator.next().accept(appOwner);
}
}
}
17、适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另外一个接口。该模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
原理就是保留现有的类所提供的服务,修改其接口,从而达到客户端的期望。
再者,适配器也分对象适配器和类适配器,先看下面代码再来解释。
举个例子,你有一个播放器,只能播放MP3格式的音乐,但是现在需要它能播放flac格式的,我们不能直接使用这个播放器,但可以添加一个适配器来解决这个问题:
//对象适配器
public class ObjectAdapter implements Target{
private Adaptee adaptee;
public ObjectAdapter(){
super();
adaptee = new Adaptee();
}
@Override
public void playFlac(Object src) {
//可能需要对src作处理
adaptee.playMp3(src);
}
}
- //类适配器
- public class ClassAdapter extends Adaptee implements Target {
- @Override
- public void playFlac(Object src) {
- //可能需要对src作处理
- playMp3(src);
- }
- }
public class TestUse {
public static void main(String args[]){
Adaptee adaptee = new Adaptee();
adaptee.playMp3("mp3");
Target target = new ClassAdapter();
target.playFlac("flac");
target = new ObjectAdapter();
target.playFlac("flac");
}
}
可以看到,类适配器与对象适配器的区别就是类适配器需要继承被适配者,而Java的单继承的,所以通常情况下,使用对象适配器更好。
如果目标接口中的方法数与被适配器者中的数目相同,就是完全适配,若目标接口中的方法更多,则是剩余适配,反之,为不完全适配。
上面的被适配器是一个类,但也可以使用接口,这样,其实现会由子类决定。如果适配器同时实现目标接口和被适配者的接口,那么适配器就成为了一个双向适配器。
- //被适配者
- public class Book{
- private Vector<String> books;
- public Book(){
- books = new Vector<String>();
- }
- public void add(String book){
- books.add(book);
- }
- public Enumeration<String> getEnum(){
- return books.elements();
- }
- }
public class TestUse {
public static void main(String args[]){
Book books = new BookAdapter();
books.add("think in java");
books.add("c++ primer");
books.add("伊索寓言");
Iterator<String> iterator = books.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
那么这两者显然是冲突的,为了解决,我们需要添加适配器,首先要把Enumeration的行为转换为Iterator,然后还需要添加一个Book的适配器(所以,这里是用到了2次适配器的):
- //适配器,目标就是Iterator,被适配者是Enumeration
- public class IteratorAdapter implements Iterator<String> {
- Enumeration<String> myEnum;
- public IteratorAdapter(Enumeration<String> myEnum){
- this.myEnum = myEnum;
- }
- @Override
- public boolean hasNext() {
- return myEnum.hasMoreElements();
- }
- @Override
- public String next() {
- return myEnum.nextElement();
- }
- }
//适配器
public class BookAdapter extends Book implements Iterable<String>{
@Override
public Iterator<String> iterator() {
return new IteratorAdapter(getEnum());
}
}
18、组合模式(Composite Pattern)
Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使用户对单个对象和组合对象的使用具有一致性。
何时使用:
当想表示对象的部分-整体层次结构。
希望用户用一致的方式处理个体对象和组合对象。
优点:
组合模式中包含个体对象和组合对象,并形成树形结构,使用户可以方便地处理个体对象和组合对象。
组合对象和个体对象实现了相同的接口,用户一般无须区分个体对象和组合对象。
当增加新的Composite节点和Leaf节点时,用户的重要代码不需要做出修改。
组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念 ,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。组合模式让你可以优化处理递 归或分级数据结构。有许多关于分级数据结构的例子,使得组合模式非常有用武之地。关于分级数据结构的一个普遍性的例子是电脑的文件系统。下面我们就以这个例子来介绍组合模式(虽然我们直接使用Tree这种数据结构也能直接描述)。
一个文件系统中,有目录,目录下有文件和目录
目录和文件的抽象接口(抽象组件):
- //抽象组件
- public interface Component {
- void addFile(Component file);
- Component addFolder(Component folder);
- void removeFile(Component file);
- void removeFolder(Component folder);
- List<Component> getFiles();
- List<Component> getFolders();
- List<Component> getAll();
- Iterator<Component> iterator();
- void display();
- }
目录(Composite节点):
public class Folder implements Component {
private String name;
private List<Component> files;
private List<Component> folders;
public Folder(String name){
this.name = name;
files = new ArrayList<Component>();
folders = new ArrayList<Component>();
}
@Override
public void addFile(Component file) {
files.add(file);
}
@Override
public Component addFolder(Component folder) {
folders.add(folder);
return this;
}
@Override
public void removeFile(Component file) {
files.remove(file);
}
@Override
public void removeFolder(Component folder) {
folders.remove(folder);
}
@Override
public List<Component> getFiles() {
return files;
}
@Override
public List<Component> getFolders() {
return folders;
}
@Override
public List<Component> getAll() {
List<Component> all = new ArrayList<Component>(folders);
all.addAll(files);
return all;
}
@Override
public Iterator<Component> iterator() {
List<Component> all = new ArrayList<Component>();
add(all,this);
return all.iterator();
}
private void add(List<Component> all,Component component){
if(component==null) return;
all.add(component);
Iterator<Component> iterator = component.getFolders().iterator();
while(iterator.hasNext()){
add(all,iterator.next());
}
all.addAll(component.getFiles());
}
@Override
public void display() {
System.out.println(name);
}
}
文件(Leaf节点):
- //Leaf节点
- public class File implements Component{
- private String name;
- public File(String name){
- this.name = name;
- }
- @Override
- public void addFile(Component file) {}
- @Override
- public Component addFolder(Component folder) { return null; }
- @Override
- public void removeFile(Component file) {}
- @Override
- public void removeFolder(Component folder) {}
- @Override
- public List<Component> getFiles() { return null; }
- @Override
- public List<Component> getFolders() { return null; }
- @Override
- public List<Component> getAll() { return null; }
- @Override
- public Iterator<Component> iterator() { return null; }
- @Override
- public void display() {
- System.out.println(name);
- }
- }
使用:
public class TestUse {
public static void main(String args[]){
Component root = new Folder("root");//根目录
Component folder1 = new Folder("java");
Component folder2 = new Folder("c++");
Component folder3 = new Folder("c#");
Component file1 = new File("info.txt");
root.addFolder(folder1).addFolder(folder2).addFolder(folder3).addFile(file1);//添加一级目录
folder1.addFile(new File("info.java"));
Iterator<Component> iterator = root.iterator();
while(iterator.hasNext()){
Component component = iterator.next();
if(component instanceof Folder)
System.out.print("folder:");
else
System.out.print("file:");
component.display();
}
}
}
控制台输出:
- folder:root
- folder:java
- file:info.java
- folder:c++
- folder:c#
- file:info.txt
输出与我们预期想得到的迭代器是一样的(从某一个目录开始,先输出目录名,然后如果有目录,就递归进入下一级目录,如果没有目录,就输出文件列表)。
文件和目录(Composite和Leaf)实现了相同的接口,所以操作起来很方便,包括迭代。
19、代理模式(Proxy Pattern)
Provide a surrogate or placeholder for another object to control access to it.
- 程序可能不希望用户直接访问该对象,而是提供一个特殊的对象以控制对当前对象的访问。
- 如果一个对象(例如很大的图像)需要很长时间才能完成加载。
- 如果对象位于远程主机上,需要为用户提供访问该远程对象的能力。
一个用户不想或者不能够直接引用一个对象(或者设计者不希望用户直接访问该对象),而代理对象可以在客户端和目标对象之间起到中介的作用。而且这个代理对象中,我们可以做更多的操作。
- //具体对象
- public class TargetObject implements AbstractObject {
- @Override
- public void method1() {
- System.out.println("具体对象的方法1");
- }
- @Override
- public int method2() {
- System.out.println("具体对象的方法2");
- return 0;
- }
- @Override
- public void method3() {
- System.out.println("具体对象的方法3");
- }
- }
//代理对象
public class ProxyObject implements AbstractObject {
AbstractObject object = new TargetObject();
@Override
public void method1() {
object.method1();
}
@Override
public int method2() {
return object.method2();
}
@Override
public void method3() {
System.out.println("调用目标对象前的操作");
object.method3();
System.out.println("调用目标对象后的操作");
}
}
- public class TestUse {
- public static void main(String args[]){
- AbstractObject obj = new ProxyObject();
- obj.method1();
- obj.method2();
- obj.method3();
- }
- }
控制台的输出就不放上来了,可以看到,代理模式实现确实比较简单,好处也显而易见,将目标对象与使用的用户隔离开,而且在调用目标对象的方法前后还能执行额外的操作(有点aop的概念)。
LinkedList可以实现栈、队列等数据结构,但我们还是希望一个专一的队列或数据结构,下面就以代理(部分代理)实现栈和队列:
//LinkedList的使用
public class TestUse {
public static void main(String[] args) {
//栈
Stack<Integer> stack = new Stack<Integer>();
for(int i=0;i<10;i++)
stack.push(i);
System.out.println(stack.peek());
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
Iterator<Integer> iterator = stack.iterator();
while(iterator.hasNext())
System.out.print(iterator.next());
System.out.println();
//队列
Queue<Integer> queue = new Queue<Integer>();
for(int i=0;i<10;i++)
queue.enqueue(i);
System.out.println(queue.peek());
System.out.println(queue.peek());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
iterator = queue.iterator();
while(iterator.hasNext())
System.out.print(iterator.next());
}
}
//很使用使用代理模式利用LinkedList实现一个栈
class Stack<T> implements Iterable<T>{
private LinkedList<T> stack = new LinkedList<T>();
public T pop(){//出栈,会删除栈顶元素
return stack.poll();
}
public T peek(){//出栈,但不删除栈顶元素
return stack.peek();
}
public void push(T t){//入栈
stack.push(t);
}
@Override
public Iterator<T> iterator() {
return stack.iterator();
}
}
//很使用使用代理模式利用LinkedList实现一个队列
class Queue<T> implements Iterable<T>{
private LinkedList<T> queue = new LinkedList<T>();
public void enqueue(T t){
queue.offer(t);
}
public T dequeue(){
return queue.poll();
}
public T peek(){
return queue.peek();
}
@Override
public Iterator<T> iterator() {
return queue.iterator();
}
}
还有种代理叫远程代理,需要使用到RMI,然后在程序中可以调用网络上另外一个JVM上的对象方法。此处不多介绍,读者可以自行检索RMI相关资料。
20、享元模式(Plyweight Pattern)
Use sharing to support large numbers of fine-grained objects efficiently.
运用共享技术有效地支持大量细粒度的对象。
何时使用:
一个应用程序使用大量的对象,这些对象之间部分属性本质上是相同的,这时应使用享元来封装相同的部分。
对象的多数状态都可变为外部状态,就可以考虑将这样的对象作为系统中发的享元来使用。
优点:
使用享元可以节省内存的开销,特别适合处理大量细粒度对象,这些对象的许多属性值是相同的,而且一旦创建则不允许修改。
享元模式中的享元可以使用方法的参数接收外部状态中的数据,但外部状态数据不会干扰到享元中的内部数据,这就使享元可以在不同的环境中被共享。
在JAVA语言中,String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String str="string",其中"str"就是一个字符串常量。
享元模式包括三种角色:
享元接口(Plyweight):定义了对外公开的获取其内部数据和接收外部数据的方法。
具体享元(Concrete Plyweight):享元接口的实现。
享元工厂(Plyweight Factory):该类的实例负责创建和管理享元对象,用户或其他对象必须请求他以获取一个享元对象。
先看一个简单的享元模式实现:
- //简单的享元模式
- public class SimpleFlyweight {
- public static void main(String args[]){
- FlyweightFactory factory = new FlyweightFactory();
- IFlyweight flyweight1,flyweight2,flyweight3,flyweight4;
- flyweight1 = factory.getFlyweight("value1");
- flyweight2 = factory.getFlyweight("value1");
- flyweight3 = factory.getFlyweight("value1");
- flyweight4 = factory.getFlyweight("value2");
- flyweight1.doSomething();
- flyweight2.doSomething();
- flyweight3.doSomething();
- flyweight4.doSomething();
- System.out.println(factory.size());
- }
- }
- //享元接口
- interface IFlyweight{
- void doSomething();
- }
- //具体享元
- class Flyweight implements IFlyweight{
- private String value;
- public Flyweight(String value){
- this.value = value;
- }
- @Override
- public void doSomething() {
- System.out.println(value);
- }
- }
- //享元工厂
- class FlyweightFactory{
- HashMap<String, IFlyweight> flyweights = new HashMap<String, IFlyweight>();
- IFlyweight getFlyweight(String value){
- IFlyweight flyweight = flyweights.get(value);
- if(flyweight == null){
- flyweight = new Flyweight(value);
- flyweights.put(value, flyweight);
- }
- return flyweight;
- }
- public int size(){
- return flyweights.size();
- }
- }
上例中,具体的享元我们一共创建了4个,而我们输出的创建的对象个数是2个,这就是享元模式。可以减少对象创建的个数。
刚刚的例子,因为具体享元对象仅以一个String作为成员,实现很方便。下面我们再讲一个实际的例子:我们要检查许多天气的数据,主要有天气情况和温度组成,但是天气和温度的组合实际上是很容易相同的,为了减少创建的对象数量,我们使用享元模式:
首先是抽象的天气接口:
//享元接口
public interface IWeather {
void printWeather();
}
其次是天气的实现。注意!这里我们使用HashMap来持有享元的引用,以为天气由具体天气情况和温度共同确定他们是否相同,我们需要使用两个值做key,但key只能是一个对象,所以最终我们选择这个对象来当key。HashMap的Key是有限制的,必须正确提供hashCode()方法(HashMap以这个值为基础存取数据的)和equals()方法(HashMap通过key取值时判断Key是否相等会调用Key的这个方法)。下面的实现中就实现了这两个方法:
- //具体享元
- public class Weather implements IWeather{
- private String weather;
- private Integer temperature;
- public Weather(String weather,int temperature){
- this.weather = weather;
- this.temperature = temperature;
- }
- @Override
- public void printWeather() {
- System.out.print("天气:" + weather);
- System.out.println(" 温度:" + temperature);
- }
- @Override
- public boolean equals(Object obj) {//两个值同时相等这个对象才相同
- Weather weather = (Weather)obj;
- return weather.weather.equals(this.weather)&&weather.temperature==temperature;
- }
- @Override
- public int hashCode() {//Integer和String的hashCode()方法都是很合理的,这里取均值即可
- return (weather.hashCode()+temperature.hashCode())/2;
- }
- }
接下来就是享元工厂,为我们产生具体天气的工厂类:
//享元工厂
public class WeatherFactory {
private HashMap<IWeather, IWeather> weathers;
public WeatherFactory(){
weathers = new HashMap<IWeather, IWeather>();
}
public IWeather getFlyWeight(String weather,int temperature){
Weather objectWeather = new Weather(weather, temperature);
IWeather flyweight = weathers.get(objectWeather);
if(flyweight == null){
flyweight = objectWeather;
weathers.put(objectWeather, flyweight);
}
else objectWeather = null;//方便gc回收
return flyweight;
}
public int getFlyweightSize(){
return weathers.size();
}
}
最后就是使用:
- public class TestUse {
- public static void main(String args[]){
- WeatherFactory factory = new WeatherFactory();
- IWeather weather1,weather2,weather3,weather4,weather5,weather6,weather7,weather8;
- weather1 = factory.getFlyWeight("多云",15);
- weather2 = factory.getFlyWeight("晴",23);
- weather3 = factory.getFlyWeight("多云",16);
- weather4 = factory.getFlyWeight("阴",10);
- weather5 = factory.getFlyWeight("多云",15);
- weather6 = factory.getFlyWeight("多云",15);
- weather7 = factory.getFlyWeight("多云",15);
- weather8 = factory.getFlyWeight("多云",15);
- weather1.printWeather();
- weather2.printWeather();
- weather3.printWeather();
- weather4.printWeather();
- weather5.printWeather();
- weather6.printWeather();
- weather7.printWeather();
- weather8.printWeather();
- System.out.println("实际对象个数" + factory.getFlyweightSize());
- }
- }
输出:
- 天气:多云 温度:15
- 天气:晴 温度:23
- 天气:多云 温度:16
- 天气:阴 温度:10
- 天气:多云 温度:15
- 天气:多云 温度:15
- 天气:多云 温度:15
- 天气:多云 温度:15
- 实际对象个数4
可以看到,确实是我们预期的那样,相同的对象并没有重复创建。
享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。享元模式将享元对象的状态外部化,而读取外部状态使得运行时间变长。
21、外观模式
为系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
我们提供给用户的某个功能,可能是包含很多个步骤的,但我们可以把这些步骤封装到一个统一的接口中,让用户感觉仅仅就是一个单一的操作,使用起来也就更加简单。
比如说,我们往回一些年,那时候的相机不是每个人都会用的,按下快门前还需要调焦等操作,然而现在呢,我们拍照只需要按下快门就行了,中间的一系列操作都被自动执行了。外观模式也就是提供这样一种机制。
//获取商品价格
public class ProductPrice {
int getPrice(String product){
return Math.abs(product.hashCode());//模拟获取商品价格
}
}
- //计算邮费
- public class Postage {
- int getPostage(String addr){
- return Math.abs(addr.hashCode())%20+6;//模拟邮费计算
- }
- }
//计算优惠
public class Discount {
int getDiscount(String discountCode){
return Math.abs(discountCode.hashCode())%3;
}
}
计算最终价格的子系统,同时他也是一个外观,因为它含有以上三个子系统的引用:
- //计费子系统
- public class FinalPrice {
- ProductPrice productPrice;
- Postage postage;
- Discount discount;
- public FinalPrice(){
- productPrice = new ProductPrice();
- postage = new Postage();
- discount = new Discount();
- }
- int getFinalPrice(String product,String addr,String discountCode){
- return productPrice.getPrice(product)+postage.getPostage(addr)-discount.getDiscount(discountCode);
- }
- }
//库存子系统
public class Stock {
boolean hasStock(String product){
return new Random().nextInt(Math.abs(product.hashCode()))>0;//模拟是否还有库存
}
}
最后是面向用户的购买接口,在这里也是一个外观,同时,这里使用了枚举实现的单例模式:
- //外观
- public enum ProductSalesman {
- instance;
- Stock stock = new Stock();
- FinalPrice finalPrice = new FinalPrice();
- Object buySomething(String product,String addr,String discountCode){
- if(!stock.hasStock(product))
- return "库存不足";
- int price = finalPrice.getFinalPrice(product, addr, discountCode);
- return "订单信息:" + product + "-" + addr + "-" + discountCode + "-" + price;
- }
- }
使用很简单,用户无需关系内部是如何操作了,只需要使用这个购买接口即可:
public class TestUse {
public static void main(String args[]){
Object info = ProductSalesman.instance.buySomething("银河飞船", "地球", "K1234523");
System.out.println(info);
}
}
当然,如果要增加子系统,这里是比较麻烦的,但是我们若为子系统抽象一个接口,然后融合责任链模式的一种变体(处理失败时才终止请求的传递),使用起来就会方便很多了。
22、桥接模式(Bridge Pattern)
别名:柄体模式(Another Name:Handle-Body)
Decouple an abstraction from its implementation so that the two can vary independently.
将抽象部分与它的实现部分分离,使它们都可以独立的变化。
何时使用:
不想让抽象和某些重要的实现代码是固定的绑定关系,这部分实现可运行时动态决定。
抽象和实现者都可以继承当方式独立地扩充而互不影响,程序在运行期间可能需要动态的将一个抽象的子类的实例与一个实现者的子类的实例进行组合。
希望对实现者层次代码的修改对抽象层不产生影响,即抽象层的代码不需要重新编译,反之亦然。
优点:
桥接模式分离实现与抽象,使抽象可实现可以独立的扩展。当修改实现的代码时,不影响抽象的代码,反之也一样。
满足开闭-原则,抽象和实现者处于同层次,使系统可独立的扩展这两个层次。增加新的具体实现者,不需要修改细化抽象,反之增加新的细化抽象也不需要修改具体实现。
桥接模式是一种结构型模式,它主要应对的是:由于实际的需要,某个类具有两个或两个以上的维度变化,如果只是用继承将无法实现这种需要,或者使得设计变得相当臃肿。
桥接模式的做法是把变化部分抽象出来,使变化部分与主类分离开来,从而将多个维度的变化彻底分离。最后,提供一个管理类来组合不同维度上的变化,通过这种组合来满足业务的需要。
桥接模式中有4种角色:
抽象
细化抽象
实现者
具体实现者
下面看一个例子(并不是黑联想和AMD,仅仅是一个例子而已...):
- //桥接模式
- public class SimpleBridge {
- public static void main(String args[]){
- new LenevoComputer(new Amd()).discribe();
- new HaseeComputer(new Intel()).discribe();
- }
- }
- //实现者
- interface Cpu{
- String discribe();
- }
- //具体实现者*2
- class Amd implements Cpu{
- public String discribe() {
- return "just so so...";
- }
- }
- class Intel implements Cpu{
- public String discribe() {
- return "great !";
- }
- }
- //抽象
- abstract class AbstractComputer{
- Cpu cpu;
- public AbstractComputer(Cpu cpu){
- this.cpu=cpu;
- }
- public abstract void discribe();
- }
- //细化抽象*2
- class LenevoComputer extends AbstractComputer{
- public LenevoComputer(Cpu cpu) {
- super(cpu);
- }
- @Override
- public void discribe() {
- System.out.println("联想笔记本cpu:"+super.cpu.discribe());
- }
- }
- class HaseeComputer extends AbstractComputer{
- public HaseeComputer(Cpu cpu) {
- super(cpu);
- }
- @Override
- public void discribe() {
- System.out.println("神舟笔记本cpu:"+super.cpu.discribe());
- }
- }//输出:联想笔记本cpu:just so so... 神舟笔记本cpu:great !
上面的抽象是多变的,实现也是多变的,就可以应用桥接模式。
我们再次那数据存储举一个实际的例子。数据存储可以实现为存到文件或是存到数据库,而我们存储时可以选择是本地还是网络上的位置,下面一一道来:
首先是定义数据存储的实现:
//实现
public interface ISaveData {
void save(Object data);
}
然后就是具体的实现:
- //具体实现
- public class SaveToFile implements ISaveData{
- @Override
- public void save(Object data) {
- System.out.println(data + " 存储到文件");
- }
- }
//具体实现
public class SaveToDB implements ISaveData{
@Override
public void save(Object data) {
System.out.println(data + " 存储到数据库");
}
}
然后是抽象,如何存储:
- //抽象
- public abstract class AbstractSave {
- ISaveData saveData;
- public AbstractSave(ISaveData saveData){
- this.saveData = saveData;
- }
- public abstract void save();
- }
接下来是细化抽象:
//细化抽象
public class LocalSave extends AbstractSave{
public LocalSave(ISaveData saveData) {
super(saveData);
}
@Override
public void save(Object data) {
System.out.print("本地存储:");
saveData.save(data);
}
}
- //细化抽象
- public class NetSave extends AbstractSave{
- public NetSave(ISaveData saveData) {
- super(saveData);
- }
- @Override
- public void save(Object data) {
- System.out.print("网络存储:");
- saveData.save(data);
- }
- }
使用:
public class TestUse {
public static void main(String args[]){
Object data = "数据";
ISaveData saveDataDb = new SaveToDB();
ISaveData saveDataFile = new SaveToFile();
AbstractSave save;
save = new NetSave(saveDataDb);
save.save(data);
save = new NetSave(saveDataFile);
save.save(data);
save = new LocalSave(saveDataDb);
save.save(data);
save = new LocalSave(saveDataFile);
save.save(data);
}
}
输出:
- 网络存储:数据 存储到数据库
- 网络存储:数据 存储到文件
- 本地存储:数据 存储到数据库
- 本地存储:数据 存储到文件
而且这个程序是很容易扩展的,直接添加细化抽象和具体实现就可以了。
23、装饰模式(Decorator Pattern)
动态的给对象添加额外的职责。就功能来说,装饰模式比生产子类更为灵活。
装饰模式使用被装饰类的一个子类的实例,把客户端的调用委派到被装饰类,装饰模式的关键在于这种扩展是完全透明的。装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为。
- //具体的被装饰者
- public class PersistentUtil implements IPersistentUtil{
- @Override
- public void persistentMsg(String msg) {
- System.out.println(msg + " 存入文件");
- }
- }
首先需要抽象一个装饰器,我们不能直接使用它(即使使用匿名内部类使用了它,也跟最初的实现效果一样,没有意义),因为他是一个抽象类(装饰):
//装饰
public abstract class PersistentDecorator implements IPersistentUtil {
IPersistentUtil iPersistentUtil;
public PersistentDecorator(IPersistentUtil iPersistentUtil){
this.iPersistentUtil = iPersistentUtil;
}
@Override
public void persistentMsg(String msg) {
iPersistentUtil.persistentMsg(msg);
}
}
然后,我们添加一个装饰器,可以同时实现数据持久化到数据库(具体装饰):
- //装饰--存入数据库
- public class PersistentDbDecorator extends PersistentDecorator {
- public PersistentDbDecorator(IPersistentUtil iPersistentUtil){
- super(iPersistentUtil);
- }
- @Override
- public void persistentMsg(String msg) {
- iPersistentUtil.persistentMsg(msg);
- persistentToDb(msg);
- }
- private void persistentToDb(String msg){
- System.out.println(msg + " 存入数据库");
- }
- }
在后来,我们添加一个可以同时在网络存储上持久化数据的装饰器(具体装饰):
//装饰--存入网络其他地方
public class PersistentNetDecorator extends PersistentDecorator {
public PersistentNetDecorator(IPersistentUtil iPersistentUtil){
super(iPersistentUtil);
}
@Override
public void persistentMsg(String msg) {
iPersistentUtil.persistentMsg(msg);
persistentToNet(msg);
}
private void persistentToNet(String msg){
System.out.println(msg + " 存入网络的其他地方");
}
}
- public class TestUse {
- public static void main(String args[]){
- //被装饰者
- final String data = "数据";
- IPersistentUtil iPersistentUtil = new PersistentUtil();
- iPersistentUtil.persistentMsg(data);
- System.out.println("下面装饰数据库持久化:");
- iPersistentUtil = new PersistentDbDecorator(iPersistentUtil);
- iPersistentUtil.persistentMsg(data);
- System.out.println("下面继续装饰网络存储器持久化:");
- iPersistentUtil = new PersistentNetDecorator(iPersistentUtil);
- iPersistentUtil.persistentMsg(data);
- }
- }
可以看到,使用了装饰器的对象,功能已经增强了,而且可以使用多个装饰器。
Java的二十三种设计模式相关推荐
- Java实现二十三种设计模式(五)—— 十一种行为型模式 (中)——解释器模式、迭代器模式、中介者模式、备忘录模式
Java实现二十三种设计模式(五)-- 十一种行为型模式 (中)--解释器模式.迭代器模式.中介者模式.备忘录模式 一.解释器模式 我国 IT 界历来有一个汉语编程梦,虽然各方对于汉语编程争论不休,甚 ...
- Java的二十三种设计模式(单例模式、工厂方法模式、抽象工厂模式)
从这一块开始,我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析. 创建型模式(5种):用于描述"怎样创建对象",它的主要特点是& ...
- Java的二十三种设计模式(原型模式(Prototype))
原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.本小结会通过对象的复制,进行讲解.在Java中 ...
- Java的二十三种设计模式(适配器模式(Adapter)、对象的适配器模式)
适配器模式(Adapter) 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题.主要分为三类:类的适配器模式.对象的适配器模式.接口的适配器模式 ...
- Java的二十三种设计模式(建造者模式(Builder))
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到 ...
- Java二十三种设计模式 之代理(proxy)
Java二十三种设计模式 之代理(proxy) 今天我们学习一下静态代理和动态代理 我们来看代码(写一个坦克运行了多少时间): 第一种方法: public calss Tank implements ...
- 二十三种设计模式(第十二种)-----代理模式(Proxy)
二十三种设计模式(第十二种)-----代理模式(Proxy) 尚硅谷视频连接https://www.bilibili.com/video/BV1G4411c7N4?from=search&se ...
- 深入理解常见的二十三种设计模式
深入理解常见的二十三种设计模式 文章目录 深入理解常见的二十三种设计模式 一.设计模式的分类 1.1 创建型(五种) 1.2 结构型(七种) 1.3 行为型(十一种) 二.创建型 2.1 单例模式 2 ...
- 二十三种设计模式-六大原则
二十三种设计模式 一.创建型: 单例模式.工厂模式.抽象工厂模式.原型模式.建造者模式: 二.结构型: 代理模式,装饰器模式.适配器模式.外观模式.组合模式.享元模式.桥梁模式: 三.行为型: 策略模 ...
最新文章
- 程序员被知乎优化!瞄准bat大厂再次求职!结果让人意外!
- 中大南方学院计算机温澍潜,中大南方学院
- 关于C# this 指针
- Docker 常用命令整合!!!带你一起理解和复习每个操作命令!!!带操作图,不会也能看懂的!!
- Introduction to algrithms exercise2.3-7
- ipad和iphone切图_如何在iPhone,iPad和Mac上签名PDF
- linux之通过strings命令查看so里面是否包含****字符串
- python while break try 无法中断_解码不能正常工作并且while循环不能正确中断python...
- 方便微信公众号等手机网页调试插件eruda和vConsole
- linux vim命令及使用,极大提升使用linux的舒适程度
- 更改Linux Shell的提示符
- 各大物联网通信技术对比
- openlayers3.0叠加天地图底图及注记后再叠加geoserverWMS服务
- 使人疲惫的不是远方的高山,而是你鞋子里的一粒沙子
- 第十一届“认证杯”数学中国数学建模国际赛 (2022 CERTIFICATE AUTHORITY CUP INTERNATIONAL
- MPAndroidChart实现曲线阴影效果
- 5-LVI-SAM源码分析_imageProjection初步分析
- Linux网络嗅探器实验报告,分析网络嗅探器实验报告
- c 抓取ajax异步数据,用requests.post提交表单抓取异步ajax信息失败
- material-design-icons-iconfont 图标引入步骤
热门文章
- 人脸表情识别解干扰论文解读2:D3Net:Dual-Branch Disturbance Disentangling Networkfor Facial Exp
- 黑暗意志【map水题】
- 毫米波角雷达进入“新赛道”
- ORACLE回滚段管理(上)
- 漫步者TWSNB2左右耳离线,放进充点仓无反应,不亮灯,怎么办。
- Linux中的open和close
- 你可能不知道的电脑软件
- js / jquery 使用val()赋值监控事件失效
- html+css中的animation 动画(二)孙悟空巡山
- python max函数中使用key