设计模式详解(2)-- 创建型模式详解

本系列文章:
设计模式详解(1)-- 初识设计模式
设计模式详解(2)-- 创建型模式详解
设计模式详解(3)-- 结构型模式详解上
设计模式详解(4)-- 结构型模式详解下
设计模式详解(5)-- 行为型模式详解上
设计模式详解(6) – 行为型模式详解下

本博客专门介绍创建型模式

简单工厂模式

工厂模式提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

实例

我们先创建一个表示动物通用特性的接口

//公共接口
public interface Animal {

	void run();
}

再随便创建两个实现类

public class Cat implements Animal {

	@Override
	public void run() {
		System.out.println("猫在跑");
	}

}
public class Dog implements Animal {

	@Override
	public void run() {
		System.out.println("狗在跑");
	}

}

然后我们创建一个工厂,生成基于给定信息的实体类的对象。

//工厂类,用于创建对象
public class AnimalFactory {

	public static Animal createAnimal(String name) {
		
		if(name == null || name.equals("")) {
			return null;
		}else if(name.equals("Cat")) {
			return new Cat();
		}else if(name.equals("Dog")) {
			return new Dog();
		}
		
		return null;
	}
}

最后我们测试一下工厂类的作用

public class FactoryDemo {

	public static void main(String[] args) {
		
		//创建一只猫
		Animal cat = AnimalFactory.createAnimal("Cat");
		cat.run();
		
		//创建一只狗
		Animal dog = AnimalFactory.createAnimal("Dog");
		dog.run();
		
		//故意创建一个不存在的对象
		Animal tiger = AnimalFactory.createAnimal("Tiger");
		tiger.run();
	}
}

在这里插入图片描述
如我们所愿,结果一切正常,后面报错是因为我们创建了一个不存在的对象

工厂方法模式(Factory Method)

简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到工厂方法模式,创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

示例

先创建一个动物接口

//公共接口
public interface Animal {

	void run();
}

随便创建两个实现类

public class Cat implements Animal {

	@Override
	public void run() {
		System.out.println("猫在跑");
	}

}
public class Dog implements Animal {

	@Override
	public void run() {
		System.out.println("狗在跑");
	}

}

再提供一个工厂接口

public interface Factory {

	Animal createAnimal();
}

提供两个工厂类

public class CatFactory implements Factory {

	@Override
	public Animal createAnimal() {
		return new Cat();
	}

}
public class DogFactory implements Factory {

	@Override
	public Animal createAnimal() {
		return new Dog();
	}

}

最后我们测试一下刚才写的代码

public class FactoryDemo {

	public static void main(String[] args) {
		//创建一个猫工厂
		Factory catFactory = new CatFactory();
		//创建一只猫
		Animal cat = catFactory.createAnimal();
		cat.run();
		
		//创建一个狗工厂
		Factory dogFactory = new DogFactory();
		//创建一只狗
		Animal dog = dogFactory.createAnimal();
		dog.run();
	}
}

在这里插入图片描述
没有问题

其实这个模式的最大的好处在于不必修改现有的代码,拓展性较好!但是可能需要的类较多,使程序变得臃肿。

抽象工厂模式(Abstract Factory Pattern)

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

我们换一种容易理解的方式来讲解抽象工厂模式,例如我们需要键盘和鼠标来组装电脑,华为电脑需要配置华为键盘和华为鼠标,小米电脑需要配置小米键盘和小米鼠标。那么使用抽象工厂模式,在为电脑生产相关配件时,就无需制定配件的型号,它会自动根据电脑类型生产对应的配件。

注意事项

产品族难扩展,产品等级易扩展。

例子

QQ 换皮肤,一整套一起换。

示例

创建鼠标类

//鼠标类
public class Mouse {

}

//华为鼠标类
public class HuaWeiMouse extends Mouse {

	public void say() {
		System.out.println("这是一个华为鼠标类");
	}
}

//小米鼠标类
public class XiaoMiMouse extends Mouse{

	public void say() {
		System.out.println("这是一个小米鼠标类");
	}
}

创建键盘类

//键盘类
public class Keyboard {

}

//华为键盘类
public class HuaWeiKeyboard extends Keyboard {

	public void say() {
		System.out.println("这是一个华为键盘");
	}
}

//小米键盘类
public class XiaoMiKeyboard extends Keyboard {

	public void say() {
		System.out.println("这是一个小米键盘类");
	}
}

然后我们创建一个抽象工厂接口

//抽象工厂接口
public interface AbstractFactory {

	//创建鼠标
	Mouse createMouse();
	//创建键盘
	Keyboard createKeyboard();
}

然后我们就可以创建华为电脑,小米电脑啦!

//创建一台华为电脑
public class HuaWeiComputer implements AbstractFactory {

	@Override
	public Mouse createMouse() {
		return new HuaWeiMouse();
	}

	@Override
	public Keyboard createKeyboard() {
		return new HuaWeiKeyboard();
	}

}

//创建一台小米电脑
public class XiaoMiComputer implements AbstractFactory {

	@Override
	public Mouse createMouse() {
		return new XiaoMiMouse();
	}

	@Override
	public Keyboard createKeyboard() {
		return new XiaoMiKeyboard();
	}

}

测试一下代码

public class AbstractFactoryDemo {

	public static void main(String[] args) {
		AbstractFactory huaWeiComputer = new HuaWeiComputer();
		HuaWeiKeyboard huaWeiKeyboard = (HuaWeiKeyboard) huaWeiComputer.createKeyboard();
		huaWeiKeyboard.say();
		HuaWeiMouse huaWeiMouse = (HuaWeiMouse) huaWeiComputer.createMouse();
		huaWeiMouse.say();
	}
}

没有任何问题
在这里插入图片描述

扩展

使用抽象工厂模式,我们甚至可以创建这样一台电脑,使用华为键盘和小米鼠标,是不是感觉方便了很多?如果还不理解的话,可以多看几遍代码,看着看着就理解了。

单例模式

顾名思义,单例模式确保只有单个对象被创建

下面介绍实现单例模式的几种方式

线程不安全的懒汉式

这种方式是最基本的实现方式,且lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}
线程安全的懒汉式

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。(必须加锁 synchronized 才能保证单例,但加锁会影响效率)

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}
饿汉式

没有 Lazy 初始化,容易产生垃圾对象。多线程安全,没有加锁,执行效率会提高。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}
双检锁/双重校验锁(DCL,即 double-checked locking)

Lazy 初始化,多线程安全,但实现比较复杂。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}
登记式/静态内部类

Lazy 初始化,多线程安全。

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}
枚举

没有Lazy 初始化,多线程安全。它自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

建造者模式

无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

建造者模式(Builder Pattern)可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。它一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

优点
  1. 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,
    用户使用不同的具体建造者即可得到不同的产品对象 。
  3. 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”
缺点
  1. 产品之间差异性很大的情况:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  2. 产品内部变化很复杂的情况: 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
抽象工厂模式与建造者模式的区别

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

示例

我们先建造一个具体的产品对象 – 单车

//自行车的产品对象
public class BikeProduct {

	//轮胎
	private String tyre;
	//座椅
	private String seat;
	
	public String getTyre() {
		return tyre;
	}
	public void setTyre(String tyre) {
		this.tyre = tyre;
	}
	public String getSeat() {
		return seat;
	}
	public void setSeat(String seat) {
		this.seat = seat;
	}
	
}

接着,我们构建一个抽象建造者Builder,用于创建产品对象的各个指定部件

//自行车建造者抽象类
public abstract class BikeBuilder {

	protected BikeProduct bike = new BikeProduct();
	
	//造轮胎
	abstract public void buildTyre();
	//造座椅
	abstract public void buildSeat();
	
	public BikeProduct getBike() {
		return bike;
	}
}

然后我们构建具体建造者,用于构建和装配各个部件

//摩拜单车,具体建造者
public class MoBikeBuilder extends BikeBuilder {
	
	@Override
	public void buildTyre() {
		bike.setTyre("摩拜轮胎");
	}

	@Override
	public void buildSeat() {
		bike.setSeat("摩拜座椅");
	}

}

//ofo单车,具体建造者
public class OfoBikeBuilder extends BikeBuilder {
	
	@Override
	public void buildTyre() {
		bike.setTyre("ofo轮胎");
	}

	@Override
	public void buildSeat() {
		bike.setSeat("ofo座椅");
	}

}

接着我们构建一个指挥者,使用Builder接口类装配对象。它不仅可以隔离了客户与对象的生产过程,也负责控制产品对象的生产过程。

//指挥者
public class Director {

	private BikeBuilder bikeBuilder;

	public void setBikeBuilder(BikeBuilder bikeBuilder) {
		this.bikeBuilder = bikeBuilder;
	}
	
	//组装
	public BikeProduct construct() {
		//造座椅
		bikeBuilder.buildSeat();
		//造轮胎
		bikeBuilder.buildTyre();
		//返回造好的对象
		return bikeBuilder.getBike();
	}
}

最后我们测试一下

public class Test {

	public static void main(String[] args) {
		Director director = new Director();
		
		//建造ofo单车
		director.setBikeBuilder(new OfoBikeBuilder());
		BikeProduct ofoBike = director.construct();
		//测试
		System.out.println(ofoBike.getSeat());
		System.out.println(ofoBike.getTyre());
		
		//建造摩拜单车
		director.setBikeBuilder(new MoBikeBuilder());
		BikeProduct moBike = director.construct();
		//测试
		System.out.println(moBike.getSeat());
		System.out.println(moBike.getTyre());
	}
}

结果一切正常

ofo座椅
ofo轮胎
摩拜座椅
摩拜轮胎

原型模式

若通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。原型模式以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。

克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后,再修改克隆对象的值。

优势

效率高(直接克隆,避免了重新执行构造步骤)。

浅克隆与深克隆的区别

浅克隆:复制该对象,然后保留该对象原有的引用。也就是说不克隆该对象的属性。

深克隆:复制该对象,并且把该对象的所有属性也克隆出一份新的。

浅克隆的实现
public class Computer implements Cloneable{

	String name;
	Date date;
	
	public Computer(String name,Date date) {
		this.name = name;
		this.date = date;
	}
	
	public Computer(){}
	
	//浅拷贝
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

我们可以测试一下

public class Test {

	public static void main(String[] args) throws CloneNotSupportedException {
		Computer computer1 = new Computer("computer1",new Date());
		Computer computer2 = (Computer) computer1.clone();
		
		System.out.println(computer2.date + " " + computer2.name);
		System.out.println("computer1==computer2 " + (computer1==computer2?true:false));
		System.out.println("computer1.date==computer2.date " + (computer1.date==computer2.date?true:false));
	}
}

测试结果

Tue Apr 16 21:00:08 CST 2019 computer1
computer1computer2 false
computer1.date
computer2.date true

可见,在实现浅克隆的时候,创建了一个新的对象,但仍然保留了原有对象的引用。若改变原对象的值,新对象也会随之改变

深克隆的实现

修改代码如下

public class Computer implements Cloneable{

	String name;
	Date date;
	
	public Computer(String name,Date date) {
		this.name = name;
		this.date = date;
	}
	
	public Computer(){}
	
	//深拷贝
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object object = super.clone();
		Computer computer = (Computer)object;
		
		computer.date = (Date) this.date.clone();

		return object;
	}
}

重新执行刚刚的测试代码,得到结果如下

Tue Apr 16 21:07:59 CST 2019 computer1
computer1==computer2 false
computer1.date==computer2.date false

深克隆在克隆对象的同时,把该对象的属性也连带着克隆出新的,修改原对象的属性,新对象的属性将不会随之发生变化。

序列化和反序列化实现深复制
public class Test {

	public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
		Computer computer1 = new Computer("computer1",new Date());
		
		System.out.println(computer1);
		System.out.println(computer1.date);
		System.out.println(computer1.name);
		
		//使用序列化和反序列化实现深复制
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		oos.writeObject(computer1);
		byte[] bytes = bos.toByteArray();
		
		ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
		ObjectInputStream ois = new ObjectInputStream(bis);
		Computer computer2 = (Computer)ois.readObject();  //克隆好的对象
		
		System.out.println(computer2);
		System.out.println(computer2.date);
		System.out.println(computer2.name);
		
		//修改computer2的日期
		computer2.date = new Date(123456);
		System.out.println(computer2.date);
		System.out.println(computer1.date);
	}
}

结果

Factory.Computer@11028347
Tue Apr 16 21:20:00 CST 2019
computer1
Factory.Computer@3f2a3a5
Tue Apr 16 21:20:00 CST 2019
computer1
Thu Jan 01 08:02:03 CST 1970
Tue Apr 16 21:20:00 CST 2019

经过测试发现,使用序列化和反序列化实现深复制效果与使用clone()方法等同。

友情提示

如果某个对象new的过程中很耗时,则可以考虑使用原型模式。

我的下一篇设计模式博客:设计模式详解(3)-- 结构型模式详解上

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值