单例模式确保了在整个系统中这个类只有一个对象被创建。他提供了一种访问他唯一的一个对象的方式,可以直接访问,不需要额外实例化对象。
单例模式有几个显著的特点:
单例模式分为两类:
类加载的时候就生成了对象,但是不用的话就很浪费内存.
public class HungrySingleton1 {
private HungrySingleton1(){}
private static HungrySingleton1 instance=new HungrySingleton1();
public static HungrySingleton1 getInstance(){
return instance;
}
}
public class HungrySingleton2 {
private HungrySingleton2(){}
private static HungrySingleton2 instance;
static{
instance=new HungrySingleton2();
}
public static HungrySingleton2 getInstance(){
return instance;
}
}
上述的这两种方式其实差不多,就是在类里面new了一个私有静态变量,然后对外的方法只返回他。
public enum HungrySingleton3 {
INSTANCE;
public void method1(){
System.out.println("do sth");
}
}
怎么样是不是非常的简单,而且枚举类的实现方式是最安全的。不会被反射或者序列化破解。
懒汉式在类被使用到的时候,才会生成对象避免了内存的浪费。
这个方式就是在调用getInstance()
方法的时候会先判断是否已经创建了对象。如果创建了就返回,没有就创建一个在返回。但是在多线程的情况下,有可能出二次创建的现象。
public class Lazy1 {
private Lazy1() {
}
private static Lazy1 instance;
public static Lazy1 getInstance() {
if (instance == null) {
instance=new Lazy1();
}
return instance;
}
}
和上面的差异就是多了一个synchronized
关键字。使用synchronized关键字给getInstance()
方法加锁,但是锁的加在了整个方法上,但是性能较差。
public class Lazy1 {
private Lazy1() {
}
private static Lazy1 instance;
public static synchronized Lazy1 getInstance() {
if (instance == null) {
instance=new Lazy1();
}
return instance;
}
}
双重检查锁方式,把锁加在了方法里面而不是整个方法。只有在需要创建对象的时候才需要获得锁,让锁的粒度更细了。效率也就更高。
public class Lazy2 {
private Lazy2(){}
//volatile 是为了保证指令的有序性,不然在多线程环境下 由于jvm的指令重排序可能会导致空指针
private static volatile Lazy2 instance;
public static Lazy2 getInstance(){
if(instance==null){
synchronized(Lazy2.class){
if(instance==null){
instance=new Lazy2();
}
}
}
return instance;
}
}
通过在对象里面创建一个静态内部类来持有对象。静态内部类在类加载是不会被加载,只有在被调用的时候才会被加载,从而实现了懒汉式。而且是线程安全的。
public class Lazy3 {
private Lazy3(){}
private static class Lazy3Holder{
private static final Lazy3 INSTANCE=new Lazy3();
}
public static Lazy3 getInstance(){
return Lazy3Holder.INSTANCE;
}
}
先说一下基本的概念,
public static void write2File() throws FileNotFoundException, IOException {
Lazy2 l = Lazy2.getInstance();
ObjectOutputStream oStream = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oStream.writeObject(l);
oStream.close();
}
然后通过字节流在反序列化成对象。
public static Lazy2 read4File() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream oInputStream=new ObjectInputStream(new FileInputStream("obj.txt"));
Lazy2 l=(Lazy2)oInputStream.readObject();
oInputStream.close();
return l;
}
我们直接反序列化两次,生成两个对象。输出结果为:false。说明我们生成了两个不同的对象,破坏了单例模式全局唯一对象的特性。
public static void main(String[] args) throws Exception{
write2File();
System.out.println(read4File()==read4File());
}
反射可以通过一个类对应的class类来获得这个类所有的成员变量和方法,不管是私有的还是公开的。
可以理解为,每个类都有一个大门。门里面是他的私有的变量和方法,只有类自己有钥匙打开大门去操作他们。但是反射不讲武德,他有电锯。直接把门锯开了,然后去操作这个类私有的变量和方法。
public class Reflection {
public static void main(String[] args) throws Exception{
//获取Lazy2的class类
Class class1=Lazy2.class;
//获取私有构造方法
Constructor con=class1.getDeclaredConstructor();
//暴力破门(设置私有方法可访问)
con.setAccessible(true);
//使用构造方法创建对象
Lazy2 l=(Lazy2) con.newInstance();
Lazy2 l2=(Lazy2) con.newInstance();
System.out.println(l==l2);
}
}
输出:false
一下的例子我都是在Lazy2上修改的
在需要序列化反序列化的单例模式类中添加readResolve()
方法来规定反序列化时的返回值。
在反序列化时,Java会判断是否有这个函数,如果有就返回这个函数的返回值。没有就会new一个新的对象来返回
public Object readResolve(){
return instance;
}
public static void main(String[] args) throws Exception{
write2File();
Lazy2 l=read4File();
Lazy2 l2=read4File();
System.out.println(l==l2);
}
输出:true
既然反射时通过调用构造方法来实现的创建对象的,那么我们就在构造方法里面加上一点逻辑判断,在有对象的情况下报错,不让他创建就好了。
private Lazy2(){
synchronized(Lazy2.class){
if(instance!=null){
throw new RuntimeErrorException(null, "不准创建多个对象");
}
}
}
在常规调用之后,在使用反射创建对象就会报错。
Lazy2 l=Lazy2.getInstance();
Lazy2 l1=(Lazy2) con.newInstance();
但是直接使用反射还是可以破解
Lazy2 l1=(Lazy2) con.newInstance();
Lazy2 l2=(Lazy2) con.newInstance();
反射实在太猛了
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的。完美符合单例模式!
使用一个接口A来规定工厂生产的产品的的规范。然后使用这个接口A的实现类来说明实际生产的产品。
我们的程序需要输出日志
public class Log {
public void log(String name){
if (name.equals("txt")) {
System.out.println("往txt中输出数据");
} else if (name.equals("cmd")) {
System.out.println("往CMD中输出数据");
}
}
}
但是一般情况下,我们会有很多种Log
类,如果每个Log
类都可以往txt和命令行中输出日志。假设有n种Log
类,那么这段代码我们就要写n次,这问题还不大。但是突然有一天,你需要往一数据库里面写日志了,那我们就需要修改上面的代码,改成这样。
public class Log {
public void log(String name){
if (name.equals("txt")) {
System.out.println("往txt中输出数据");
} else if (name.equals("cmd")) {
System.out.println("往CMD中输出数据");
} else if(name.equals("db")){
System.out.println("往数据库种输出日志");
}
}
}
改一次还简单,但是别忘了我们有n个Log类,这就要了老命了。我们要去n个地方改,而且忘记改了就会导致系统出错。
因此我们可以使用简单工厂模式。
Log
产品Log
接口来规范Log
的功能。public interface Log{
public void writeLog();
}
CMDLog
,TxtLog
来表示具体Log
的类(具体的产品)public class CMDLog implements Log{
@Override
public void writeLog() {
System.out.println("往CMD中输出数据");
}
}
public class TxtLog implements Log{
@Override
public void writeLog() {
System.out.println("往txt中输出数据");
}
}
LogFactory
工厂来返回Log产品public class LogFactory {
public static Log createLog(String name) {
if (name.equals("txt")) {
return new TxtLog();
} else if (name.equals("cmd")) {
return new CMDLog();
} else {
return null;
}
}
}
Log
调用工厂来获得Log
实现类,进行业务操作即可public class ApacheLog {
public void log(String name){
Log log=LogFactory.createLog(name);
log.writeLog();
}
public void ApacheLogMethod(){
System.out.println("Apache日志专属方法");
}
}
public class SysLog {
public void log(String name){
Log log=LogFactory.createLog(name);
log.writeLog();
}
public void sysLogMethod(){
System.out.println("系统日志专属方法");
}
}
SysLog
和ApacheLog
都有他们独有的方法,但是又可以随意的往任何地方输出日志,虽然最后还是用了if/else来判断具体往哪里输出日志,但是当我们需要增加新的需求时,我们只需要修改LogFactory
的代码和新增一个实现类就好了。不需要去修改SysLog
和ApacheLog
了。
public static void main(String[] args) {
ApacheLog apacheLog=new ApacheLog();
SysLog sysLog=new SysLog();
apacheLog.log("txt");
apacheLog.ApacheLogMethod();
sysLog.log("cmd");
sysLog.sysLogMethod();
}
输出:
往txt中输出数据
Apache日志专属方法
往CMD中输出数据
系统日志专属方法
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度。
作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
相对于简单工厂模式,工厂方法模式完全符合了开闭原则(对修改关闭,对扩展开放)
工厂方法模式定义一个工厂接口来用于创建对象,然后让其子类来决定具体创建哪个对象。工厂方法让一个产品类的创建延迟到了其工厂的子类中。
优点:
缺点:
上述的简单工厂模式和工厂方法模式都只是生产同一种类的产品。比如咖啡工厂只生产咖啡,牧场只养动物,九阳豆浆机只造豆浆机,手机店只卖手机。但是在现实情况下,店家不会这么单纯滴。就比如华为手机店,就不可能只卖手机,他还买电脑、电视甚至是汽车(对的,华为还卖汽车哦)。
也就是说简单工厂模式和工厂方法模式都只能生产同一类的产品(不同的咖啡,或者不同品种的狗狗),而抽象工厂可以生产同一族,但是不同类的产品(同时生产手机和电脑,同时生产上衣和裤子)
关于同类和同族,举例来说就是所有电子产品都是同一族的,比如电脑和手机是同一族的。然后一个产品的不同类型是同一类的,比如华为mate20和华为p30。
结构其实和工厂方法是一样的,但是有一些细微的区别
由于抽象工厂在使用的时候会有非常多的类,所以不是什么时候都适合用他的。(比如我这咖啡店就只卖咖啡)
他使用一个已经被创建的实例作为模板,通过复制来创建一个和原型对象相同的新对象。
clone()
方法。(Cloneable
)原型模式的复制方式有两种:
浅拷贝:直接复制了对象A地址给另一个对象B。由于对象A,B指向的是同一个地址因此修改B,A也会跟着改变。
深拷贝:复制了对象A的值给另一个对象B,这两个对象没有任何关系指向不同的地址。修改其中一个并不会对另一个造成影响。
接下来的例子我都按浅拷贝来实现了,有关浅拷贝和深拷贝的详细内容,看这里
Builder
和Director
负责,并最终可以生成复杂的对象。这个模式适用于:生成一个及其复杂的对象的情况。Builder
):规定建造者需要实现的方法,不涉及具体的对象部件的创建Director
):调用具体建造者来创建复杂对象的各个部件,并按照顺序把他们拼起来。优点:
Director
中稳定性较好。Builder
的实现类也就是具体建造者就行了。建造者模式通常用来创建复杂对象。这种对象的部件变化频繁,但是组合的过程相对稳定。
工厂方法模式注重整体对象的创建,而建造者模式注重部件构造的过程,通过一个个部件的搭建来最终生成一个复杂对象。
也就是说工厂方法模式生成电脑就是直接生成一台电脑,建造者模式就是先拿到cpu、gpu然后主板什么的最终拼成一台电脑。
抽象工厂模式关注的是一个产品族的生产。他不关心构建的过程,只关心什么产品由什么工厂生产即可。
建造者则是按照指定的蓝图构建产品,他的目的是通过组装零件来生产一个新产品。
如果抽象工厂模式是汽车配件生产厂,那建造者就是汽车组装厂。由抽象工厂生产零件,再由建造者组装。