23种设计模式之五种创建型模式从概念介绍再到案例分析,不懂就从例子去感受

  • 2020 年 10 月 30 日
  • 笔记

一、创建型模式(Factory Method)

1.工厂模式
1.1普通工厂模式

就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:

举例如下:(我们举一个发送邮件和短信的例子)
首先创建二者的共同接口

public interface Sender {  
    public 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 sms sender!");  
    }  
}

最后,建工厂类:

public class SendFactory {  
  
    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
           return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("请输入正确的类型!");  
           return null;  
        }  
    }  
}

我们来测试下:

public class FactoryTest {  
  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }  
}
1.2 多工厂方法模式

是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:

将上面的代码做下修改,改动下SendFactory类就行,如下:

public class SendFactory {  
      
    public Sender produceMail(){  
        return new MailSender();  
    }  
      
    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  

测试类如下:

public class FactoryTest {  
  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}  

输出:this is mailsender!

1.3 静态工厂方法模式

将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

public class SendFactory {  
      
    public static Sender produceMail(){  
        return new MailSender();  
    }  
      
    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}
public class FactoryTest {  
  
    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
}

输出:this is mailsender!

  总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
2. 抽象工厂模式

​ 工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解。

请看例子:

public interface Sender {  
    public 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 sms sender!");  
    }  
} 

两个工厂类:

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 interface Provider {  
    public Sender produce();  
} 
public class Test {  
  
    public static void main(String[] args) {  
        Provider provider = new SendMailFactory();  
        Sender sender = provider.produce();  
        sender.Send();  
    }  
} 

其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!

3. 单例模式

通过私有构造方法,不允许外面去创建对象,实现只有一个实例。

3.1 饿汉式单例

缺点:一开始就把所有的对象都加载,容易浪费空间。

public class Hungry {
	//核心是构造,不允许外面去创建对象
	private Hungry() {
	}
	//创建静态变量
	private final static Hungry HUNGRY = new Hungry();
	//返回对象
	public static Hungry getInstance() {
		return HUNGRY;
	}
}
3.2 懒汉式单例

(1)只有在对象为空时才去new对象——单线程下没有问题

public class LazyMan {

	private LazyMan() {
	}
	private static LazyMan lazyMan;
	
	//只有在对象为空时才去new对象------单线程下没有问题
	public static LazyMan getInstance() {
		if(lazyMan == null) {
			lazyMan = new LazyMan();
		}
		return lazyMan;
	}
}

(2)通过多线程去测试,发现单例被破坏。

private LazyMan() {
	System.out.println(Thread.currentThread().getName()+"ok");
}
//多线程的情况下就会出现多个对象
public static void main(String[] args) {
	for(int i=0;i<10;i++) {
		new Thread(()->{
			LazyMan.getInstance();
		}).start();
	}
}

去实现双重检测锁模式的懒汉式单例–DCL懒汉式,并且在lazyMan = new LazyMan();位置,由于还不是原子操作,容易造成指令重排,去加上volatile属性,他是可见的不是原子的,来保证安全。

private volatile static LazyMan lazyMan;

public static LazyMan getInstance() {
		if(lazyMan==null) {
			synchronized (LazyMan.class) {   //锁住类就会只有一个对象
				if(lazyMan == null) {
					lazyMan = new LazyMan();   //不是一个原子操作
					/**
					 * 1. 分配内存空间
					 * 2. 执行构造方法,初始化对象
					 * 3. 把对象指向这个空间
					 *  假如又A,B两个线程,A执行了132是没有问题的,但是当B在执行时就							直接return了,但是此时还没有完成构造,是不安全的。
					 *  volatile只能保证可见性,不证原子性。
					 * */
				}
			}
		}
		return lazyMan;
	} 

(3)通过反射机制去破环构造方法的私有性,从而破坏单例模式—一个反射对象。

public static void main(String[] args) throws Exception{
		LazyMan instance = LazyMan.getInstance();
		Constructor<LazyMan> declaredConstructor = 			    LazyMan.class.getDeclaredConstructor(null);
		declaredConstructor.setAccessible(true);   //用反射使其私有构造无效
		LazyMan instance2 = declaredConstructor.newInstance();
		
		System.out.println(instance);
		System.out.println(instance2);
	}

去实现三重检测,也就是在构造方法上再次进行加锁判断,避免某一种反射的破坏,其余不变。

private LazyMan() {
	synchronized (LazyMan.class) {
		if(lazyMan!=null) {
			throw new RuntimeException("不要试图使用反射破坏单例");
		}
	}
}

(4)上面的先正常创建对象,再用反射,如果我们直接建立多个反射对象就可以破坏单例。

public static void main(String[] args) throws Exception{
		Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
		declaredConstructor.setAccessible(true);   //用反射使其私有构造无效
		LazyMan instance = declaredConstructor.newInstance();
		LazyMan instance2 = declaredConstructor.newInstance();
		
		System.out.println(instance);
		System.out.println(instance2);
}

通过增加一个标志位,可以防止多个反射对象的破坏。

private static boolean flag = false;
	
private LazyMan() {
	synchronized (LazyMan.class) {
		if(flag==false) {
			flag = true;
		}else {
			throw new RuntimeException("不要试图使用反射破坏单例");
		}
	}
}

(5)如果我们能够破解这个标志位,仍然是可以去破坏单例。

public static void main(String[] args) throws Exception{
		Field flag = LazyMan.class.getDeclaredField("flag");//获得标志位
		flag.setAccessible(true);
		
		Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
		declaredConstructor.setAccessible(true);   //用反射使其私有构造无效
		LazyMan instance = declaredConstructor.newInstance();
		flag.set(instance, false);	//破坏标志位
		LazyMan instance2 = declaredConstructor.newInstance();
		
		System.out.println(instance);
		System.out.println(instance2);
	}

(6)通过使用枚举去保证单例和安全,因为枚举是不可以被反射的。

public enum EnumSingle {
	INSTANCE;
	//枚举本身也是一个类,只是继承了枚举
	public EnumSingle getInstance() {
		return INSTANCE;
	}
}
public static void main(String[] args) throws Exception {
		EnumSingle instance1 = EnumSingle.INSTANCE;
		EnumSingle instance2 = EnumSingle.INSTANCE;
		
		System.out.println(instance1);
		System.out.println(instance2);
}

我们可以通过我们的反编译class字节文件,发现有一个带参构造,如果这个时候再去用反射破坏,发现就会报menu不能被反射的错了。

public static void main(String[] args) throws Exception {		
		EnumSingle instance1 = EnumSingle.INSTANCE;
		Constructor<EnumSingle> de = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
		de.setAccessible(true);
		EnumSingle instance2 = de.newInstance();
	}

3.3 静态内部类实现单例

public class Holder {

	private Holder() {
		
	}
	public static Holder getInstance() {
		return InnerClass.HOLDER;
	}
	public static class InnerClass{
		private static final Holder HOLDER = new Holder();
	}
}
4.建造者模式

将一个复杂对象的构建与他的表示层分离,实现了解耦,使得同样的构造过程可以构造不同的表示。具体的建造者类之间是相互独立的,这有利于系统的扩展,符合“开闭原则”。

缺点:共性、内部变化复杂,导致多个建造者类,系统庞大。

(1)举例工人盖房子,有四个步骤地基、钢筋、水泥、粉刷。工人有指挥官,负责指挥工作的顺序,如果需要建不同的楼,只需要换不同的工人,因为工人的技能是不同的。

//抽象的建造者:方法
public abstract class Builder {

	abstract void buildA();  //地基
	abstract void buildB();  //钢筋
	abstract void buildC();  //铺电线
	abstract void buildD();  //粉刷
	
	//完工:得到产品
	abstract Product getProduct();
}
//产品:房子
public class Product {

	private String builderA;
	private String builderB;
	private String builderC;
	private String builderD;
	
	public String getBuilderA() {
		return builderA;
	}
	public void setBuilderA(String builderA) {
		this.builderA = builderA;
	}
	public String getBuilderB() {
		return builderB;
	}
	public void setBuilderB(String builderB) {
		this.builderB = builderB;
	}
	public String getBuilderC() {
		return builderC;
	}
	public void setBuilderC(String builderC) {
		this.builderC = builderC;
	}
	public String getBuilderD() {
		return builderD;
	}
	public void setBuilderD(String builderD) {
		this.builderD = builderD;
	}
	@Override
	public String toString() {
		return "Product [builderA=" + builderA + ", builderB=" + builderB + ", builderC=" + builderC + ", builderD="
				+ builderD + "]";
	}
}
public class Worker extends Builder{
	
	private Product product;
	public Worker() {
		product = new Product();
	}
	//工人把工作,实现建造的没有步骤
	public void buildA() {
		product.setBuilderA("地基");
		System.out.println("地基");
	}
	public void buildB() {
		product.setBuilderB("钢筋");
		System.out.println("钢筋");
	}
	public void buildC() {
		product.setBuilderC("铺电线");
		System.out.println("铺电线");
	}
	public void buildD() {
		product.setBuilderD("粉刷");
		System.out.println("粉刷");
	}

	public Product getProduct() {
		return product;
	}
}
//指挥:核心,负责指挥构建一个工程,工程如何构建由他决定
public class Director {

	//指挥工人按照顺序建房子
	public Product build(Builder builder) {
		builder.buildA();
		builder.buildB();
		builder.buildC();
		builder.buildD();
		return builder.getProduct();
	}
}
public class Test {

	public static void main(String[] args) {
		//需要一个指挥者 
		Director director = new Director();
		//指挥工人去完成具体的产品
		Product product = director.build(new Worker());
		System.out.println(product.toString());
	}
}

(2)上述是有指挥官的构造,如果没有指挥官,就不需要顺序,也就是实现无序的搭配。

举例:麦当劳的套餐,服务员(建造者)随意搭配几种食物形成套餐销售给客户。

//抽象的建造者:方法
public abstract class Builder {

	abstract Builder buildA(String msg);  //汉堡
	abstract Builder buildB(String msg);  //可乐
	abstract Builder buildC(String msg);  //薯条
	abstract Builder buildD(String msg);  //甜品
	
	//完工:得到产品套餐
	abstract Product getProduct();
}
//产品:套餐
public class Product {
	//默认值,也就是有默认的套餐
	private String builderA = "汉堡";
	private String builderB = "可乐";
	private String builderC = "薯条";
	private String builderD = "甜点";
	
	public String getBuilderA() {
		return builderA;
	}
	public void setBuilderA(String builderA) {
		this.builderA = builderA;
	}
	public String getBuilderB() {
		return builderB;
	}
	public void setBuilderB(String builderB) {
		this.builderB = builderB;
	}
	public String getBuilderC() {
		return builderC;
	}
	public void setBuilderC(String builderC) {
		this.builderC = builderC;
	}
	public String getBuilderD() {
		return builderD;
	}
	public void setBuilderD(String builderD) {
		this.builderD = builderD;
	}
	@Override
	public String toString() {
		return "Product [builderA=" + builderA + ", builderB=" + builderB + ", builderC=" + builderC + ", builderD="
				+ builderD + "]";
	}
}
public class Worker extends Builder{
	
	private Product product;
	
	public Worker() {
		product = new Product();
	}
	public Builder buildA(String msg) {
		product.setBuilderA(msg);
		return this;
	}
	public Builder buildB(String msg) {
		product.setBuilderB(msg);
		return this;
	}
	public Builder buildC(String msg) {
		product.setBuilderC(msg);
		return this;
	}
	public Builder buildD(String msg) {
		product.setBuilderD(msg);
		return this;
	}
	
	public Product getProduct() {
		return product;
	}
}
public class Test {

	public static void main(String[] args) {
		//服务员
		Worker worker = new Worker();
		//不改变参数,默认套餐
		//Product product = worker.getProduct();
		
		//链式编程:在原来的基础上可以自由组合了,如果不组合也有默认的套餐。
		Product product = worker.buildA("披萨").buildB("美年达").getProduct();
		System.out.println(product.toString());
	}
}
5.原型模式

原型模式是以一个为原型,在使用的时候不用再去new ,而是通过clone来提升效率。

第一步,实现一个接口 Cloneable;第二步,重写一个方法 clone();

(1)浅克隆:通过克隆出来的对象和原对象是一样的,但是属性的引用也是一样的,指向同一块内存空间。

public class Video implements Cloneable{

	private String name;
	private Date createTime;	
	//浅克隆
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
	public Video() {}
	public Video(String name,Date createTime) {
		this.name = name;
		this.createTime = createTime;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Date getCreateTime() {
		return createTime;
	}
	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}
	
	public String toString() {
		return "Video [name=" + name + ", createTime=" + createTime + "]";
	}
}

测试如下:

public class Bilibili {

	public static void main(String[] args) throws CloneNotSupportedException {
		//原型对象
		Date date = new Date();
		Video v1 = new Video("狂神说",date);
		System.out.println("v1:"+v1);
		System.out.println("v1.hashcode():"+v1.hashCode());
		//clone一个对象
		Video v2 = (Video)v1.clone();   //克隆出来的对象和原来的对象是一摸一样的
		System.out.println("v2:"+v2);
		System.out.println("v2.hashcode():"+v2.hashCode());
		
		v2.setName("clone:狂神说");
		System.out.println(v2);
		
		System.out.println("=====但是这样是浅克隆,date会随着原型值的改变而改变=====");
		date.setTime(123135);
		System.out.println("v1:"+v1);
		System.out.println("v2:"+v2);
	}
}

(2)深克隆:克隆对象额同时也会把属性一同克隆,使克隆的属性拥有自己独立的内存空间。还有序列化,反序列化。

protected Object clone() throws CloneNotSupportedException {
		Object obj =  super.clone();
		Video v = (Video)obj;
		//克隆属性
		v.createTime = (Date)this.createTime.clone();
		return obj;
	}

//www.cnblogs.com/yanwenbing/p/Pattern_OOP.html