一、创建型模式
对象创建,实例化形式
1. 单例模式
单例模式说是简单,却又不简单。单说单例模式的创建方式,就有7种。
单例模式供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
应用实例:
- 1、一个班级只有一个班主任。
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
1)饿汉模式(多线程安全)
饿汉模式,所谓饿汉就是在类装载的时候就创建,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间。
class Singleton {
private static Singleton instance = new Singleton(); // 实例化对象
//让构造函数为 private,这样该类就不会被实例化
private Singleton(){}
// 创建唯一的对象
public static Singleton getInstance(){
return instance;
}
public void show(){
System.out.println("Singleton show---");
}
}
public class Main {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
instance.show();
}
}
「饿汉式」是最简单的实现方式,这种实现方式适合那些在初始化时就要用到单例的情况,这种方式简单粗暴,如果单例对象初始化非常快,而且占用内存非常小的时候这种方式是比较合适的,可以直接在应用启动时加载并初始化。
2)懒汉模式(线程不安全)
懒汉模式申明了一个静态对象,在用户第一次调用时初始化,虽然节约了资源,但第一次加载时需要实例化,反映稍慢一些,而且在多线程不能正常工作。在多线程访问的时候,很可能会造成多次实例化,就不再是单例了。
class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
public void show(){
System.out.println("Singleton show---");
}
}
public class Main {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
instance.show();
}
}
3)懒汉模式(线程安全)
对创建对象的加锁,适用于多线程中,但是效率不高
因为这种方式在getInstance()
方法上加了同步锁,所以在多线程情况下会造成线程阻塞,把大量的线程锁在外面,只有一个线程执行完毕才会执行下一个线程。
class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){ // 对创建对象加速
if(instance == null){
instance = new Singleton();
}
return instance;
}
public void show(){
System.out.println("Singleton show---");
}
}
public class Main {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
instance.show();
}
}
4)双重校验锁 - DCL(线程安全)【推荐使用】
懒汉式(线程安全)」毫无疑问存在性能的问题 — 如果存在很多次getInstance()的调用,那性能问题就不得不考虑了
为什么双重校验锁可以保证线程安全,原因就是检测null的操作和创建对象的操作分离了。如果这两个操作能够原子地进行,那么单例就已经保证了。
class Singleton {
private volatile static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){ // 对创建对象加速
if(instance == null){
synchronized(Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public void show(){
System.out.println("Singleton show---");
}
}
public class Main {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
instance.show();
}
}
被volatile修饰的变量的值,将不会被本地线程缓存,
所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
- 延迟初始化。和懒汉模式一致,只有在初次调用静态方法
getSingleton
,才会初始化signleton
实例。 - 性能优化。同步会造成性能下降,在同步前通过判读
singleton
是否初始化,减少不必要的同步开销。 - 线程安全。同步创建Singleton对象,同时注意到静态变量
singleton
使用volatile
修饰。
为什么要使用volatile修饰?
虽然已经使用synchronized进行同步,但在第4步创建对象时,会有下面的伪代码:
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间
复制代码
当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton
方法,在判断singleton==null
时不为null
,则返回singleton
。但此时singleton
并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!
「双重校验锁」:既可以达到线程安全,也可以使性能不受很大的影响,换句话说在保证线程安全的前提下,既节省空间也节省了时间,集合了「饿汉式」和两种「懒汉式」的优点,取其精华,去其槽粕。
对于volatile关键字,还是存在很多争议的。由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
5)静态内部类(线程安全)【推荐使用】
在很多情况下JVM已经为我们提供了同步控制,比如:
- 在
static {...}
区块中初始化的数据 - 访问final字段时
在JVM进行类加载的时候他会保证数据是同步的,我们可以这样实现:采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现「懒汉式」的延迟加载和线程安全。
class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return Inner.instance;
}
// 内部静态类
private static class Inner{
private final static Singleton instance = new Singleton();
}
public void show(){
System.out.println("Singleton show---");
}
}
public class Main {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
instance.show();
}
}
内部类的实现方法有以下优点:
- 实现代码简洁。和双重检查单例对比,静态内部类单例实现代码真的是太简洁,又清晰明了。
- 延迟初始化。调用
getSingleton
才初始化Singleton
对象。- 线程安全。JVM在执行类的初始化阶段,会获得一个可以同步多个线程对同一个类的初始化的锁。
6)使用枚举
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高,不建议用。
enum Singleton {
INSTANCE;
public void show(){
System.out.println("Singleton show---");
}
}
public class Main {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
instance.show();
}
}
7)使用容器
在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static Object getService(String key) {
return objMap.get(key) ;
}
}
2. 抽象工厂模式
代码实现:
//实现一个抽象工厂
interface IFactory{
public Reader getReader();
}
class JPGFac implements IFactory{
public Reader getReader(){
return new JPGReader();
}
}
class GIFFac implements IFactory{ // 使用工厂创建对应的类
public Reader getReader(){
return new GIFReader();
}
}
interface Reader{
public void show();
}
class JPGReader implements Reader{
public void show(){
System.out.println("this is jpg");
}
}
class GIFReader implements Reader{
public void show(){
System.out.println("this is gif");
}
}
public class Main {
public static void main(String[] args) {
System.out.println("Hello W3Cschool!");
IFactory gifFac = new GIFFac();
gifFac.getReader().show();
}
}
二、结构型模式
类和对象结合形成更大的结构
1. 适配器模式
让不兼容的类、接口可以合作适配。(增加Adapter 适配类)
类的适配器:
有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里
class Source{
public void method1(){
System.out.println("this is method1");
}
}
interface ITargetable{ // 需要适配的接口
public void method1();
public void method2();
}
class Adapter extends Source implements ITargetable{ // 自定义一个适配器
public void method2(){
System.out.println("this is method2");
}
}
public class Main {
public static void main(String[] args) {
ITargetable target = new Adapter(); // 将 Source 类的方法适配到了接口ITargetable上
target.method1();
target.method2();
}
}
2. 桥接模式
抽象与实现解耦,使二者可以独立变化
把事物和其具体实现分开,使他们可以各自独立的变化。
像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了
interface ISQLManager{
public void show();
}
class Mysql implements ISQLManager{
public void show(){
System.out.println("this is Mysql");
}
}
class Oracle implements ISQLManager{
public void show(){
System.out.println("this is oracle");
}
}
// -------- 这里可一增加多个数据库的实现 -------
abstract class Bridge{
private ISQLManager manager;
public void setManager(ISQLManager manager){
this.manager = manager;
}
public ISQLManager getManager(){
return this.manager;
}
public abstract void show();
}
class MyBridge extends Bridge{
public void show(){
getManager().show();
}
}
public class Main {
public static void main(String[] args) {
ISQLManager mysql = new Mysql();
Bridge myBridge = new MyBridge();
myBridge.setManager(mysql);
myBridge.show();
}
}
3. 装饰者模式
为类对象增加功能
Source类是被装饰类,Decorator类是一个装饰类,可以为Source类动态的添加一些功能
interface IModify{
public void method();
}
class BeModified implements IModify{ // 需要被装饰的类,即需要扩展的功能
public void method(){
System.out.println("this is orignal method");
}
}
class Decorator implements IModify{ // 扩展功能的类
private IModify modify;
public Decorator(IModify modify){
this.modify = modify;
}
public void method(){
System.out.println("before method----");
modify.method();
System.out.println("afther method -------");
}
}
public class Main {
public static void main(String[] args) {
IModify modify = new BeModified();
IModify target = new Decorator(modify);
target.method(); // 扩展增加了功能
}
}
4. 外观模式
外观模式是为了解决类与类之家的依赖关系的,就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度
比如计算机主机中,只需要按下主机的开机按钮(on()),就可以调用其他硬件设备和软件的启动方法,如内存(Memory)的开启(check()),CPU的运行(run()),硬盘(Harddisk)的读取(read())。
计算机关机,则按下关机按钮(off),依次关闭
class Momery{
public void check(){
System.out.println("momery is checking ---");
}
public void shutDown(){
System.out.println("momery shutdown");
}
}
class CPU{
public void run(){
System.out.println("cpu is running ---");
}
public void shutDown(){
System.out.println("cpu shutdown");
}
}
class Harddisk{
public void read(){
System.out.println("Harddisk is reading ---");
}
public void shutDown(){
System.out.println("Harddisk shutdown");
}
}
class Computer{
private Momery momery;
private CPU cpu;
private Harddisk harddisk;
public Computer(){
momery = new Momery();
cpu = new CPU();
harddisk = new Harddisk();
}
public void start(){
momery.check();
cpu.run();
harddisk.read();
}
public void shutDown(){
momery.shutDown();
cpu.shutDown();
harddisk.shutDown();
}
}
public class Main {
public static void main(String[] args) {
Computer computer = new Computer();
computer.start();
computer.shutDown();
}
}
5. 组合模式
组合模式让客户以一致性的方式处理个别对象或者组合
使用组合模式,设计一个杀毒软件框架,该软件既可以对某个文件夹Folder杀毒,也可以对某个指定的文件杀毒,文件种类包括,文本文档TextFile,图片文件ImageFile,视频文件VideoFile,绘制类图并编程模拟实现。
import java.util.ArrayList;
interface IKillVirus{
public void killVirus();
}
class TextFile implements IKillVirus{
private String name;
public TextFile(String name){
this.name = name;
}
public void killVirus(){
System.out.println("name : " + name +" killing virus-- ");
}
}
class ImageFile implements IKillVirus{
private String name;
public ImageFile(String name){
this.name = name;
}
public void killVirus(){
System.out.println("name : " + name +" killing virus-- ");
}
}
class Folder implements IKillVirus{
private ArrayList<IKillVirus> array;
private String name;
public Folder(String name){
this.name = name;
array = new ArrayList<IKillVirus>();
}
public void add(IKillVirus kv){
array.add(kv);
}
public void remove(IKillVirus kv){
array.remove(kv);
}
public int getFolder(int index){
return array.get(index);
}
public void killVirus(){
System.out.println("name : " + name +" killing virus-- ");
for (int kv: array){
kv.killVirus();
}
}
}
public class Main {
public static void main(String[] args) {
IKillVirus textFile = new TextFile("textFile");
IKillVirus imageFile = new ImageFile("imageFile");
IKillVirus folder = new Folder("folder");
folder.add(textFile);
folder.add(imageFile);
folder.killVirus();
}
}
6. 代理模式
代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。
interface IService{
public void method();
}
class ServiceObject implements IService{
public void method(){
System.out.println("this is ServiceObject method----");
}
}
class Proxy implements IService{
private ServiceObject sobject;
public Proxy(){
this.sobject = new ServiceObject();
}
public void before(){
System.out.println("before method --");
}
public void afther(){
System.out.println("afther method ----");
}
public void method(){
before();
this.sobject.method();
afther();
}
}
public class Main {
public static void main(String[] args) {
IService pro = new Proxy();
pro.method();
}
}
三、行为型模式
类与对象之间如何交互,关心他们之间的通讯方式
1. 命令模式
实现命令发出者和执行者解耦
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。
interface ICommand{
public void command();
}
class MyCommand implements ICommand{
private Sodier sd;
public MyCommand(Sodier sd){
this.sd = sd;
}
public void command(){ // 命令士兵做出对应动作
sd.action();
}
}
class Sodier{
public void action(){
System.out.println("sodier get command----");
}
}
class Boss{
private ICommand commander;
public Boss(ICommand commander){ // boss 指示命令
this.commander = commander;
}
public void order(){
this.commander.command();
}
}
public class Main {
public static void main(String[] args) {
ICommand com = new MyCommand(new Sodier());
Boss boss = new Boss(com);
boss.order();
}
}
2. 观察者模式
对象一对多依赖,当一个对象改变的时候,它的对象会收到通知
某公司欲开发一套机房监控系统,如果机房达到一定指定温度,传感器将作出反应,将信号传递给响应设备,如警示灯将闪烁,报警器将发出警报,安全逃生门将自动开启、隔热门将自动关闭,每一响应设备的行为右专门的程序来控制,为支持将来引入新类型的响应设备,用观察者模式设计该系统.
import java.util.ArrayList;
interface IAlarmListener{
public void doSomthing();
}
class Voice implements IAlarmListener{
public void doSomthing(){
System.out.println("voice alarm ---------- ");
}
}
class Light implements IAlarmListener{
public void doSomthing(){
System.out.println("light red ---------- ");
}
}
class Sercurity implements IAlarmListener{
public void doSomthing(){
System.out.println("Sercurity ---------- ");
}
}
class Sensor{
private ArrayList<IAlarmListener> listener = new ArrayList();
private int value;
public void add(IAlarmListener al){
listener.add(al);
}
public void delete(IAlarmListener al){
listener.remove(al);
}
public void setValue(int value){
this.value = value;
}
public void sendMessage(){
if (this.value > 50){
for(IAlarmListener al: listener){
al.doSomthing();
}
}else{
System.out.println("normal operation ---- ");
}
}
}
public class Main {
public static void main(String[] args) {
IAlarmListener light = new Light();
IAlarmListener voice = new Voice();
IAlarmListener ser = new Sercurity();
Sensor sen = new Sensor();
sen.add(light);
sen.add(voice);
sen.add(ser);
sen.setValue(60);
sen.sendMessage();
sen.setValue(20);
sen.sendMessage();
}
}
3.模板模式
4. 状态模式
当对象的状态改变时,同时改变其行为
就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。
class State{
private String value;
public void setValue(String value){
this.value = value;
}
public String getValue(){
return this.value;
}
public void method1(){
System.out.println("this is method1 -------");
}
public void method2(){
System.out.println("this is mthod2 ------");
}
}
class Context{
private State state;
public void setState(State state){
this.state = state;
}
public void method(){
if (this.state.getValue().equals("state1")){
this.state.method1();
}else{
this.state.method2();
}
}
}
public class Main {
public static void main(String[] args) {
State state = new State();
state.setValue("state1");
Context context = new Context();
context.setState(state);
context.method();
}
}
5. 中介者模式
降低类与类之间的耦合
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。
6. 迭代器模式
迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。我们看下关系图:
具体的角色:
(1)迭代器角色(Iterator):定义遍历元素所需要的方法,一般来说会有这么三个方法:取得下一个元素的方法next(),判断是否遍历结束的方法hasNext()),移出当前对象的方法remove(),
(2)具体迭代器角色(Concrete Iterator):实现迭代器接口中定义的方法,完成集合的迭代。
(3)容器角色(Aggregate): 一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等
(4)具体容器角色(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
具体实现的代码:
package com.java.xlmdemo;
import java.util.ArrayList;
import java.util.List;
// 测试迭代器的使用
public class IteratorTest {
public static void main(String[] args) {
System.out.println("this is just for iterator test");
Collections ct = new ConcreteCollections();
ct.add(10);
ct.add(20);
ct.add(30);
// 使用for 循环来迭代
// for (Iterator iterator = ct.iterator(); iterator.hasNext();){
// int value = (int)iterator.next();
// System.out.println(value);
// }
Iterator iterator = ct.iterator();
while(iterator.hasNext()){
int value = (int)iterator.next();
System.out.println(value);
}
while (iterator.hasPrevious()){
int value = (int)iterator.previous();
System.out.println(value);
}
}
}
// 定义一个 迭代器 接口
interface Iterator{
// 判断是否有下一个元素
public boolean hasNext();
// 获取下一个元素
public Object next();
public boolean hasPrevious();
// 获取前一个元素
public Object previous();
}
interface Collections{
// 获取一个迭代器
public Iterator iterator();
// 增加、删除一个元素
public void add(Object obj);
public void remove(Object obj);
// 获取元素
public Object get(int i);
// 获取大小
public int size();
}
class ConcreteIterator implements Iterator{
private List list = new ArrayList();
private int cursor = 0;
public ConcreteIterator(List list){
this.list = list;
}
@Override
public boolean hasNext() {
if(cursor != list.size()){
return true;
}
return false;
}
@Override
public Object next() {
Object obj = null;
if (hasNext()){
obj = list.get(cursor++); // 获取元素,并使 cursor 加 1
}
return obj;
}
@Override
public boolean hasPrevious() {
// 必须大于 等于 1, 防止previous 先减去1 ,造成数组越界
if (cursor >= 1 && list.size() > 0){
return true;
}
return false;
}
@Override
public Object previous() {
Object obj = null;
if (hasPrevious()){
obj = list.get(--cursor); // 如果是打印前一个元素,由于数组从0开始数起,则需要先减去 1
}
return obj;
}
}
class ConcreteCollections implements Collections{
private List list = new ArrayList();
@Override
public Iterator iterator() {
return new ConcreteIterator(list);
}
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public Object get(int i) {
Object obj = null;
obj = list.get(i);
return obj;
}
@Override
public int size() {
return list.size();
}
}
迭代器模式的优缺点
1. 迭代器模式的优点有:
- 简化了遍历方式,对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。
- 可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
- 封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
2. 迭代器模式的缺点:
- 对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐,大家可能都有感觉,像ArrayList,我们宁可愿意使用for循环和get方法来遍历集合。
迭代器模式的适用场景
迭代器模式是与集合共生共死的,一般来说,我们只要实现一个集合,就需要同时提供这个集合的迭代器,就像java中的Collection,List、Set、Map等,这些集合都有自己的迭代器。假如我们要实现一个这样的新的容器,当然也需要引入迭代器模式,给我们的容器实现一个迭代器。
但是,由于容器与迭代器的关系太密切了,所以大多数语言在实现容器的时候都给提供了迭代器,并且这些语言提供的容器和迭代器在绝大多数情况下就可以满足我们的需要,所以现在需要我们自己去实践迭代器模式的场景还是比较少见的,我们只需要使用语言中已有的容器和迭代器就可以了。