本系列文章:
设计模式详解(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)可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。它一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
优点
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,
用户使用不同的具体建造者即可得到不同的产品对象 。 - 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”
缺点
- 产品之间差异性很大的情况:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 产品内部变化很复杂的情况: 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
抽象工厂模式与建造者模式的区别
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
示例
我们先建造一个具体的产品对象 – 单车
//自行车的产品对象
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.datecomputer2.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)-- 结构型模式详解上