本文包括:创建型模式【工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式】
设计模式的类型
设计模式的类型:
1、创建者模式:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
2、结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
3、行为型模式:这些设计模式特别关注对象之间的通信。
4、J2EE模式:这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。
1、创建者模式
工厂模式(Factory Pattern)
- 简单工厂模式:
简单工厂模式就是隐藏对象实例化的细节,将对象的创建和实例化操作全都交给一个工厂处理,只需要告诉工厂想要什么样的类就可以了。
缺点:违背了设计模式的开闭原则,如果需要增加工厂能够实例化的类,必须修改工厂类
开闭原则(Open Close Principle):对扩展开放,对修改关闭。在程序修改的时候,不能去修改原来的代码,实现一个热拔插的效果。(需要使用接口和抽象类来实现)
实例:Spring
代码实现:
public interface Car {
void driver();
}
public class Benz implements Car {
@Override
public void driver() {
System.out.println("开奔驰............");
}
}
public class BMW implements Car {
@Override
public void driver() {
System.out.println("开宝马............");
}
}
public class LandRover implements Car {
@Override
public void driver() {
System.out.println("开路虎............");
}
}
public class CarFactory {
public static Car getCar(String logo){
if (logo == null){
return null;
}
if ("bmw".equalsIgnoreCase(logo)){
return new BMW();
}else if ("benz".equalsIgnoreCase(logo)){
return new Benz();
}else if ("landrover".equalsIgnoreCase(logo)){
return new LandRover();
}
return null;
}
}
public class TestDemo {
public static void main(String[] args) {
Car car1 = CarFactory.getCar("BMW");
car1.driver();
Car car2 = CarFactory.getCar("Benz");
car2.driver();
Car car3 = CarFactory.getCar("LandRover");
car3.driver();
}
}
// 运行结果:
开宝马............
开奔驰............
开路虎............
2.工厂方法模式
工厂方法模式就是把简单工厂模式中的工厂设计成一个接口,如果想曾加一个新的类型,直接新建一个新类型的工厂类继承工厂接口,在需要获取实例的时候直接从子工厂获取。(这样就符合了开闭原则)
实例:Spring
代码实现:
// 基本的方法不变
public interface CarFactory {
Car getCar();
}
public class LandRoverCarFactory implements CarFactory{
@Override
public Car getCar() {
return new LandRover();
}
}
public class BMWCarFactory implements CarFactory{
@Override
public Car getCar() {
return new BMW();
}
}
public class TestDemo {
public static void main(String[] args) {
Car car1 = new LandRoverCarFactory().getCar();
car1.driver();
Car car2 = new BMWCarFactory().getCar();
car2.driver();
}
}
// 运行结果
开路虎............
开宝马............
抽象工厂模式(Abstract Factory Pattern)
原文链接
单例模式(Singleton Pattern)
重点:构造函数必须是私有的
1.饿汉式(线程安全)
比较常用,但是容易产生垃圾对象(类只要被加载就会new个新对象,浪费内存)
优点:因为没有加锁,所以访问速度比较快。
代码实现:
// 单例模式-饿汉式-线程安全
public class HungryPattern {
private HungryPattern(){}
private static HungryPattern instance = new HungryPattern();
public static HungryPattern getInstance(){
return instance;
}
}
这个问什么是线程安全的呢?
一般在介绍的时候都是一句话:它基于 classloader 机制避免了多线程的同步问题
那么具体是怎么通过classloader机制避免多线程同步问题的呢?
多个线程同时调用getInstance()方法(静态方法)的时候,如果不存在HungryPattern 实例对象,则会触发类的初始化,如果存在则直接调用。因为静态变量只会被加载一次,所以实例是不会改变的。
jvm有严格的规定(五种情况):
1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。
其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段,因为他们已经被塞进常量池了)、以及执行静态方法的时候。
2.使用java.lang.reflect.*的方法对类进行反射调用的时候,如果类还没有进行过初始化,马上对其进行。
3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。
4.当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),则jvm会先去初始化这个类。
5.用Class.forName(String className);来加载类的时候,也会执行初始化动作。
注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化。
2.懒汉式(线程不安全)
重点:延迟加载,调用getInstance方法时才创建实例
代码实现:
// 单例模式-懒汉式-线程不安全
public class LazyPattern {
private static LazyPattern instance = null;
private LazyPattern(){}
public static LazyPattern getInstance(){
if (instance == null){
instance = new LazyPattern();
}
return instance;
}
}
3.懒汉式(线程安全)
重点:延迟加载,给getInstance方法加锁
缺点:99%情况是不需要加锁的(因为创建成功直接返回instance就可以),浪费性能。
代码实现:
// 单例模式-懒汉式-线程安全
public class LazyPatternSafe {
private static LazyPatternSafe instance = null;
private LazyPatternSafe(){}
public static synchronized LazyPatternSafe getInstance(){
if (instance == null){
instance = new LazyPatternSafe();
}
return instance;
}
}
4.双检锁(线程安全)
通过双锁在保证线程安全的同时,还能保证高性能(只有在实例为空的时候才加锁)
代码实现:
// 单例模式-双检锁-线程安全
public class DoubleCheckLocking {
private volatile static DoubleCheckLocking instance = null;
private DoubleCheckLocking(){}
public static DoubleCheckLocking getInstance(){
if (instance == null){
synchronized (DoubleCheckLocking.class){
if (instance == null){
instance = new DoubleCheckLocking();
}
}
}
return instance;
}
}
5.登记式(线程安全)
重点:使用静态内部类
在外部类被加载的时候,内部类不会被加载,只有在调用内部类中的变量的时候才会初始化内部类。也是 基于classloader 机制来保证初始化 instance 时只有一个线程。
在外部类被加载的时候,内部类不会被加载;内部类被加载的时候外部类会被加载。
代码实现:
public class RegisterPattern {
private static class RegisterPatternHandler{
private static final RegisterPattern INSTANCE = new RegisterPattern();
}
private RegisterPattern(){}
public static final RegisterPattern getInstance(){
return RegisterPatternHandler.INSTANCE;
}
}
6.枚举(线程安全)
暂未研究
public enum EnumPattern {
INSTANCE;
public void whateverMethod(){
}
}
建造者模式/生成器模式(Builder Pattern)
1、用户不知道对象的建造过程和细节就可以创建出复杂的对象「屏蔽了建造的具体细节」
2、用户只需给出复杂对象的内容和类型可以创建出对象
3、建造者模工按流程一步步的创建出复杂对象
原文链接
原型模式(Prototype Pattern)
1、原型模式是指在已有对象的基础上,使用clone方法克隆出新的对象,而不需要使用new去实例化。
浅克隆和深克隆
浅克隆:克隆的时候会把对象中基本数据类型的值拷贝过去,但是对于对象类型只会复制内存地址,也就是克隆后两个对象中对象类型的数据还是指向同一片内存地址,如果这个内存地址上的内容改变,是会相互影响的。
注意:对于常量池方式创建的 String 类型,会针对原值克隆,不存在引用地址一说,对于其他不可变类,如 LocalDate,也是一样
例如下图,B是A拷贝出来的对象,基本数据类型和String类型拷贝的都是值,但是引用list拷贝的是内存地址,他们还是指向同一片内存地址。
深克隆
深克隆不仅拷贝对象本身和对象中的基本变量,而且拷贝对象包含的引用指向的所有对象
深克隆例子:下面代码中,深克隆后,c1改变了 list的值,但是因为c2中list与c1指向的已经不是一个内存了,所以说c2中list的值并不会改变。
class MyCloneable2 implements Cloneable{
String name;
ArrayList list = new ArrayList();
public MyCloneable2() {
System.out.println("MyCloneable2对象创建了!!!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getList() {
String result = "";
for (int i = 0; i < list.size(); i++) {
result += list.get(i);
}
return result;
}
public void setList(ArrayList list) {
this.list = list;
}
@Override
public MyCloneable2 clone() {
MyCloneable2 object = null;
try {
object = (MyCloneable2) super.clone();
object.list = (ArrayList) list.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
public class CloneableDemo {
public static void main(String[] args) {
MyCloneable2 c1 = new MyCloneable2();
ArrayList list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
c1.setList(list);
MyCloneable2 c2 = c1.clone();
list.add("ccc");
System.out.println(c1.getList());
System.out.println(c2.getList());
}
}
打印结果:
MyCloneable2对象创建了!!!
aaabbbccc
aaabbb
下面的代码:当直接使用 = 赋值的时候,会直接把c1的引用赋值给c2,那么这两个对象还是指向同一片内存地址,所以当更改了c1的值时,c2的值也会跟着变(同理更改c2,c1也会变)。
class MyCloneable{
String name;
public MyCloneable() {
System.out.println("MyCloneable对象创建了!!!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CloneableDemo {
public static void main(String[] args) {
MyCloneable c1 = new MyCloneable();
c1.setName("测试");
MyCloneable c2 = c1;
c1.setName("嘻嘻");
System.out.println(c1.getName()); // 嘻嘻
System.out.println(c2.getName()); // 嘻嘻
c2.setName("嘿嘿");
System.out.println(c1.getName());
System.out.println(c2.getName());
}
}
输出结果:
MyCloneable对象创建了!!!
嘻嘻
嘻嘻
嘿嘿
嘿嘿
但是当我们使用clone的话就不同了。从下面的代码可以看出,使用clone方法克隆出来的对象是将对象的引用复制了一份,指向了另一片内存,所以当更改c1时,c2的值不会变(同理更改c2,c1也不会变)。另外可以看到,构造方法只打印了一次,也就是说克隆是不会执行构造方法的,单例模式需要使用私有的构造方法,也就是说这两种模式是互斥的。
class MyCloneable implements Cloneable{
String name;
public MyCloneable() {
System.out.println("MyCloneable对象创建了!!!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
public class CloneableDemo {
public static void main(String[] args) {
MyCloneable c1 = new MyCloneable();
c1.setName("测试");
MyCloneable c2 = (MyCloneable) c1.clone();
c1.setName("嘻嘻");
System.out.println(c1.getName());
System.out.println(c2.getName());
c2.setName("嘿嘿");
System.out.println(c1.getName());
System.out.println(c2.getName());
}
}
输出结果:
MyCloneable对象创建了!!!
嘻嘻
测试
嘻嘻
嘿嘿