设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
还有两类:并发型模式和线程池模式。
这里说的模式可以理解为如何创建实例,这种创建实例的设计方式有什么好处和用地;
1.1、FactoryMethod (工厂方法模式)
@判断创建实例:
接口; 实现接口类; 工厂(public Interface createObject(String which){return new A();//创建一个匿名实例});
调用:Interface a = new Factory().createObject("a"); a.function();
@选择创建实例:
Interface ImplementsClass(A,B,C,D....) Factory{createA(){return new A() } createB(){return new B() } }
调用:Interface b =new Factory().createB(); b.function();
@选择型,静态创建实例(2的升级版,常用这种模式):
Interface ImplementsClass(A,B,C,D....) Factory{static createA(){return new A() } static createB(){return new B() } }
调用:Interface b =Factory.createB(); b.function();
&:A的匿名实例通过向上转型成为接口的实例a,然后a执行A中复写接口的实现方法;
工厂方法模式的好处:将多个类的构造方法进行封装,在创建对象时,可以减少代码;如多个实例调用同样的构造方法,这样可大大减少代码的写入;将构造方法封装与其他方法隔离也体现了解耦的思想,增强程序健壮性;若是继承抽象类,调用同一方法时,还可以节省大量的方法代码;在需要创建多个实例时可以考虑使用工厂方法模式。工厂方法模式只创建了一个工厂类。
1.2、Abstract Factory(抽象工厂模式)
将工厂方法进一步细分,比如有类A,实现了接口IR1;Factory1实现一个接口Ifactory,复写方法createObject(){renturn new A();};
比如要得到A的实例:
Ifactory f = new Factory1();
IR1 a = f.createObject();
如果A类提供的方法不能满足新的需求,而又不能修改A提供的方法,或者A提供过多的方法而不会被使用,创建了A的实例必然会造成内存浪费;这样就产生了接口编程的思想,在接口中可以定义许多抽象方法,由它的子类去实现;子类有选择性的实现那些接口方法或覆写成各自需要的方法以实现不同功能;假如现在有一个新需求,A提供的方法不能满足,可以创建一个B实现同一个接口IR1,覆写方法function(),满足了新需求;但原来定义的工厂方法模式不能实例这类,因为没有定义,这时只需要创建一个新工厂Factory2实现工厂接口,并提供一个生产B的方法,就能实现功能扩展:
Ifactory f = new Factory2();
IR1 b = f.createObject();
从上面的解释就知道使用抽象工厂的好处:扩展功能!
1.3、单例模式
单列模式是编程语言常用的设计模式;比如多台电脑对一台打印机的调用,不可能同时打印,只能打印完了一个任务才能进行下一个打印任务,又如售票系统,当多个窗口同时出票,每个窗口都会对库存票进行查询,如果库存同时接收了相同的指令而没有进行排序操作的话,就有可能出现几个窗口对同一张票进行了操作,这肯定是行不通的;这里就涉及到一个多线程/进程 同步/死锁概念; 单例模式的作用就是当一个线程/进程 在操作某个资源(这个资源称为临界资源)的时候,操作开始前对资源加锁防止其他线程/进程操作这个资源,操作结束后进行解锁,后面的线程/进程就可以操作这个资源了,同时进行加锁/解锁;
但对于大数据、大对象、操作频繁的线程,显然这种纯 单例模式是不能满足要求的,数据隔离这个概念便产生了,数据隔离将在以后的章节讲解;
在java中A a = new A();这会调用两个线程执行:一个是类A的声明方法,一个是A的匿名实例创建方法;记住这一点,所以A a与new A()谁先谁后执行是无法确定的,这里涉及到线程安全问题;
返回一个单例对象的常用代码
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
synchronized 表名同步,即加锁
static 表名全局、类属性
两个关键字加在一起:当调用了这个类(不管是创建实例还是直接引用类名)都会率先开辟static的属性/方法的静态内存(虽然方法要调用后才执行);
在synchronized前加上了static声明,即在这个静态内存中,所有调用这个方法的对象都要被加锁;若没有加static关键字,每次new出一个新对象,就会产生一个新的堆内存空间,而synchronized的作用范围就在这个堆内存中,每次实例对象都会被对象的线程执行一次,synchronized 只对实例的线程进行了加锁,而其他对象也对这个方法进行了操作;这就是为什么使用static把这个方法放在全局内存中的原因;
使用单例模式的好处:对临界资源的顺序占有;
还有一些线程安全的获取单例的方法比如使用内部类创建、静态属性创建,这里不再说明,可以在网上找到;
1.4、Builder(建造者模式)
工厂模式是通过工厂得到单个接口实例,接口实例调用被覆写的方法;建造者可以想象为一个车间,车间里有多条生产线(多个create方法),一条生产线只生产同一类型的多个对象;
List<A> a = new ArrayList<A>();
public void createManyA(int x){
for(int i=0;i<x;i++){
a.add(new A());
}
}
public void createManyB(int x){ .....
1.5、Prototype(原型模式)
学习过javascript可能会有印象,一个js库提供者通常将属性、方法封装在原型属性中,用户在调用这个对象时可以添加自定义的属性和方法,提供者而不是直接写在对象本身中,那样会造成浏览器无法甄别该使用哪个属性或方法;可以把对象的原型想像成隐藏属性. 当访问对象的一个属性时, 首先查找对象本身, 找到则返回; 若找不到, 则查找原型属性;
原型模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。在Java中,复制对象是通过clone()实现的;先看下Cloneable接口:
public interface Cloneable { //没有任何方法
}
创建一个原型类要实现Cloneable接口,Cloneable接口是个空接口,此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的,具体怎么实现,参看23、jvm之JNI;先明白一个知识:基本数据类型值就是地址
class A{
public int x=32; //基本数据类型栈内存中保存
public String str0="why!";
//在堆内存中指定一块空间保存"why!","why!"本身是一个String的实例,只开辟一次堆内存空间,str0和info0都指向这个引用地址;
//这里str0是声明的一个实例,实例信息保存在栈内存中,"why!"将引用地址赋值给str0
public String str=new String("why!");
//在堆内存中给予一块区域来保存"why!"使用了new关键字:1.创建了匿名实例,2.开辟了新堆内存空间
}
class B{
public int y=32;
public String info0="why!";
//这里info0是一个新实例,"why!"将引用地址赋值给info0,不会再开辟一个堆内存空间来保存"why!",
public String info=new String("why!");
}
public class Demo5 {
public static void main(String...strings ){
System.out.println(new A().x==new B().y); //true 栈内存中值就是地址,所以地址相同
System.out.println(new A().str0==new B().info0); //true说明两者指向同一堆内存地址,
System.out.println(new A().str==new B().info); //false说明两者指向不同的堆内存地址
}
}
现在来创建一个原型:
class Prototype implements Cloneable{
public int x=2; //x为基本数据类型
public String s=new String("ok"); //s为引用数据类中
public void sayHi(){
System.out.println("Hello!!!");
}
public Prototype copy() throws CloneNotSupportedException { //Prototype可以用Object替代,这里为了看上去容易理解返回Prototype型
Prototype proto = (Prototype ) super.clone();
//在自身方法中创建了一个实例,联想单例模式,A.getA();返回一个A的匿名实例,这里是一个有声明名字的实例
return proto;
//调用 new Prototype().copy()将会返回一个Obejct对象,要调用proto的方法还是要用Prototype接收
}
}
public class Demo4{
public static void main(String... s) throws CloneNotSupportedException{
Prototype obj= new Prototype();
Prototype obj2 =obj.copy();
//用Prototype接收,这里与Prototype obj2=new Prototype()是不一样的,obj2复制obj也会生成一个堆内存空间来存放
System.out.println(obj.x==obj2.x); //判断基本数据类型是否相同,基本数据类型值就是地址 true
System.out.println(obj.s==obj2.s); //判断引用数据类型是否指向同一地址 true,说明new String("ok")只执行了一次
}
}
上面的例子中,通过克隆对象得到的引用地址一样的,匿名开辟堆内存空间方法只执行了一次,这就是java中所说的浅复制;
浅复制:将一个对象复制后,引用类型指向原对象的引用地址。
深复制:将一个对象复制后,引用类型重新创建,要实现深复制,需要采用流的形式读入被复制的对象,再写出二进制数据给对应的对象。[#]