总体分为3大类:
创建型模式 (5种):工厂方法、抽象工厂、单例、建造者、原型
结构型模式(7种):适配器、装饰器、代理、外观、桥接、组合、享元
行为型模式(11种):策略、模板方法、观察者、迭代子、责任链、命令、备忘录、状态、访问者、中介者、解释器
其它(2种):并发型、线程池
设计模式的6大原则
- 开闭原则(总原则):对扩展开放,对修改关闭,需要使用接口和抽象类
- 单一职责:每个类应该实现单一的职责
- 里氏替换:基类可以出现的地方,子类一定可以出现。类似于向上转型,子类对父类的方法尽量不要重写和重载,会破坏父类定义好的与外界交互规范。是对开闭原则的补充,实现开闭原则的关键是抽象化,里氏替换原则是对实现抽象化规范
- 依赖倒转:面向接口编程,依赖于抽象,不依赖于具体。代码中:不与具体类交互,与接口交互。开闭原则的基础。
- 接口隔离:接口中的方法在子类中一定能用到,否则拆分成多个接口
- 迪米特法则(最少知道):对外暴露越少越好
- 合成复用:尽量使用合成/聚合的方式,而不是使用继承
创建型模式(5种):工厂方法、抽象工厂、单例、建造者、原型。
一、工厂方法模式
创建1个接口、2个实现类
public interface Sender {
void send();
}
public class MailSender implements Sender {
@Override
public void send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void send() {
System.out.println("this is smssender!");
}
}
创建1个工厂接口、2个工厂实现类
public interface Provider {
Sender produce();
}
public class SendMailFactory implements Provider {
@Override
public Sender produce() {
return new MailSender();
}
}
public class SendSmsFactory implements Provider {
@Override
public Sender produce() {
return new SmsSender();
}
}
测试类
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.send();
}
}
如果想增加一个功能,则只需做一个实现类,实现 Sender 接口,同时做一个工厂类,实现 Provider 接口,就 OK 了,无需去改动现成的代码。这样做,拓展性较好!
二、抽象工厂模式
工厂方法模式和抽象工厂模式的区别如下:
工厂方法模式:
- 1个抽象产品类可以派生出多个具体产品类。
- 1个抽象工厂类可以派生出多个具体工厂类。1个具体工厂类只能创建1个具体产品类的实例。
抽象工厂模式:
- 多个抽象产品类,1个抽象产品类可以派生出多个具体产品类。
- 1个抽象工厂类可以派生出多个具体工厂类。1个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。
对于 java 来说,你能见到的大部分抽象工厂模式都是这样的:
---它的里面是一堆工厂方法,每个工厂方法返回某种类型的对象。
比如说工厂可以生产鼠标和键盘。那么抽象工厂的实现类(它的某个具体子类)的对象都可以生产鼠标和键盘,但可能工厂 A 生产的是罗技的键盘和鼠标,工厂 B 是微软的。
用了工厂方法模式,你替换生成键盘的工厂方法,就可以把键盘从罗技换到微软。但是用了抽象工厂模式,你只要换家工厂,就可以同时替换鼠标和键盘一套。如果你要的产品有几十个,当然用抽象工厂模式一次替换全部最方便(这个工厂会替你用相应的工厂方法)所以说抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线
三、单例模式(Singleton)
单例模式能保证在一个JVM中该对象只有一个实例存在。好处:
1、 避免频繁创建对象,节省系统开销,减轻 GC 压力。
2、在系统中某些对象只能有一个(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团)
-
简单的单例类(单线程):
只能在单线程中用,不能用于多线程。public class Singleton { /* 持有私有静态实例,防止被引用,此处赋值为 null,目的是实现延迟加载 */ private static Singleton instance = null; /* 私有构造方法,防止被实例化 */ private Singleton() { } /* 静态工程方法,创建实例 */ public static Singleton getInstance() { if (instance == null){ instance = new Singleton(); } return instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve(){ return instance; } }
同步代码块:
/* 静态工程方法,创建实例 */ public static Singleton getInstance() { if (instance == null){ synchronized (instance){ if(instance == null){ instance = new Singleton(); } } } return instance; }
在 Java 指令中创建对象和赋值操作是分开进行的,也就是说 instance = new Singleton();语句是分两步执行的,可能会先为Singleton实例分配空间,再赋值给instance,最后初始化Singleton实例。
A、B 两个线程为例:- A、B 线程同时进入了第一个 if 判断
- A首先进入 synchronized 块,由于 instance 为 null,所以它执行 instance = new Singleton();
- 由于 JVM 内部的优化机制,JVM 先画出了一些分配给 Singleton 实例的空白内存,并赋值给 instance 成员(注意此时 JVM 没有开始初始化这个实例),然后 A 离开了 synchronized块。
- B进入 synchronized 块,由于 instance 此时不是 null,因此它马上离开了 synchronized 块并将结果返回给调用该方法的程序。
- 此时 B 线程打算使用 Singleton 实例,却发现它没有被初始化,于是错误发生了。
上面这些话可以理解为A线程从同步代码块出来后,JVM没有初始化Singleton实例,B线程调用instance时发现Singleton没有初始化。
-
多线程单例
使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用 getInstance 的时候,JVM 能够帮我们保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。public class Singleton { /* 私有构造方法,防止被实例化 */ private Singleton() { } /* 此处使用一个内部类来维护单例 */ private static class SingletonFactory{ private static Singleton instance = new Singleton(); } /* 获取实例 */ public static Singleton getInstance(){ return SingletonFactory.instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve(){ return getInstance(); } }
-
将创建和赋值分开,单独为创建类加静态同步方法
因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加 synchronized 关键字,也是可以的public class SingletonTest { private static SingletonTest instance = null; public SingletonTest() { } private static synchronized void syncInit(){ if(instance == null){ instance = new SingletonTest(); } } public static SingletonTest getInstance() { if (instance == null){ syncInit(); } return instance; } }
补充:用 采用" 影子实例"的办法为单例对象的属性同步更新
public class SingletonTest { private static SingletonTest instance = null; private Vector properties = null; public Vector getProperties() { return properties; } public SingletonTest() { } private static synchronized void syncInit(){ if(instance == null){ instance = new SingletonTest(); } } public static SingletonTest getInstance() { if (instance == null){ syncInit(); } return instance; } public void updateProperties(){ SingletonTest shadow = new SingletonTest(); properties = shadow.getProperties(); } }
类和静态方法与静态类区别:
- 静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有 static 修饰的方法,所以即使实现了也是非静态的)
- 单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
- 单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是 static,无法被覆写。
四、建造者模式(Builder)
五、原型模式(Prototype)
将一个对象作为原型,对其进行复制、克隆后产生一个和原对象类似的新对象
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。
一个原型类,只需要实现 Cloneable 接口,覆写 clone 方法,此处 clone 方法可以改成任意的名称,因为 Cloneable 接口是个空接口,你可以任意定义实现类的方法名,如 cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在 Object 类中,clone()是 native 的。这里写一个深浅复制的例子
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;
/*浅复制*/
public Object clone() throws CloneNotSupportedException{
Prototype proto = (Prototype)super.clone();
return proto;
}
/*深复制*/
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}
class SerializableObject implements Serializable{
private static final long serialVersionUID = 1L;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。