文章目录
java设计模式-创建型模式
简单工厂模式
概述
创建型模式描述如何将对象的创建和使用分离
GoF包含5种创建型模式,还有一种非GoF设计模式为简单工厂模式
简单工厂模式 Simple Factory Pattern:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
因为简单工厂模式用于创建实例的方法通常是static静态方法,故而又被称为静态工厂方法Static Factory Method模式
结构
- 工厂角色Factory:核心,提供静态的工厂方法,返回类型为抽象产品类型Product
- 抽象产品角色Product:工厂类创建的所有对象的父类,封装了各种产品对象的公有方法,工厂类只需要定义一个通用的工厂方法,因为其创建的具体产品对象都是其子类对象
- 具体产品角色ConcreteProduct:创建的Product的具体实例
public class Factory {
public static Product getProduct(String type) {
Product product = null;
if (type.equalsIgnoreCase('producta')) {
product = ProductA();
}
if () {...}
...
return product;
}
}
简单工厂模式的使用
Java创建对象的方式:
使用new
反射
克隆
工厂类
使用和创建分离,使系统更符合单一职责原则,工厂类的重载方法通过不同方法名表明不同构造方法创建对象的功能
简单工厂模式的创建对象逻辑全部放在了工厂角色中
适用:
工厂类负责创建的对象比较少
客户端只知道传入工厂类的参数,对于如何创建对象并不关心
工厂方法模式
概述
在简单工厂模式中,若新添加一个Product子类则需要在工厂类中修改代码,违反开闭原则,故而不再提供一个工厂类来统一负责所有产品的创建,而是将具体产品子类的创建过程交给工厂子类,即工厂方法模式
先定义一个抽象的工厂类,再定义具体的工厂类生产不同产品的子类。此抽象化的结果使得不修改具体工厂类的情况下引进新的产品。
结构
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象
- Product抽象产品
- ConcreteProduct具体产品
- Factory抽象工厂
- ConcreteFactory具体工厂:抽象工厂类的子类,实现了在抽象工厂中声明的工厂方法,并可由客户端调用,返回一个具体产品类的实例
实际使用时,具体工厂类在实现工厂方法时除了创建具体产品对象之外,还可以负责产品对象的初始化工作以及一些资源和环境配置工作,如连接数据库,创建文件等。
public interface Factory {
public Product factoryMethod();
}
public class ConcreteFactory implements Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
在抽象工厂声明多个重载的工厂方法,在具体工厂中实现这些工厂方法,这些方法可以包含不同的业务逻辑。
注意:工厂方法的隐藏,直接使用工厂对象来调用产品对象的业务方法
factory.write();// 无需创建产品对象 将业务写在抽象工厂类中
工厂方法被称为多态工厂正式因为所有的具体工厂类都有具有同一抽象父类
解决简单工厂模式扩展修改代码的问题,工厂方法的扩展不修改原有代码
抽象工厂模式
概念
工厂方法模式每一个具体工厂只创建一种具体类型对象;抽象工厂模式每一个具体工厂可以创建一组相关的具体类型对象
产品等级结构与产品族:
产品等级结构即产品的继承结构 抽象父类与其他具体实现子类构成
抽象父类:汽车,自行车 具体子类:宝马汽车,别克汽车 ;宝马自行车,别克自行车
产品族是指同一个工厂创建的位于不同产品等级结构中的一组产品
同一个工厂宝马,别克 产品族:宝马汽车,宝马自行车
抽象工厂模式与工厂方法模式最大的区别在于:工厂方法模式针对的是一个产品等级结构,而抽象工厂模式需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建;
抽象工厂模式中的具体工厂不是创建一种产品,而是负责创建一个产品族的产品对象;
定义:
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
结构与实现
抽象工厂模式结构:
- AbstractFactory抽象工厂:它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
- ConcreteFactory具体工厂:它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
- AbstractProduct抽象产品:它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
- ConcreteProduct具体产品:它定义具体工厂生成的具体产品对象,实现抽象产品接口中声明的业务方法。
// 产品结构AbstractFactory具体工厂ConcreteFactory1,2,3决定
// 产品等级结构AbstractProductA,B(横向,A1,B1是一组产品族,A是一个产品等级结构(包括A1,A2) 产品族ConcreteProductA1,A2 B1,B2
public interface AbstractFactory {
public AbstractProductA createProductA(); // 工厂方法一
public AbstractProductB createProductB(); // 工厂方法二
...
}
public class ConcreteFactory1 extends AbstractFactory {
// 工厂方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
// 工厂方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
...
}
抽象工厂方法优缺点:
在抽象工厂模式中新增产品族很方便,新建产品族对象只需要新建ConcreteProduct和ConcreteFactory;即新增一个皮肤界面,新增皮肤样式,按钮,文本框,界面不需增删,改变样式即可。
然而新增新的产品等级结构很繁琐,需要新增AbstractProduct,更改AbstractFactory,ConcreteFactory等等。
抽象工厂存在开闭原则的倾斜性,增加产品族符合开闭原则,增加产品等级结构不符合;故而需要在设计之初考虑全面。
适用环境:
产品等级结构稳定,在以后不会向系统中增加新的产品等级结构或者删除已有的产品等级结构
属于同一个产品族的产品将在一起使用,这一约束在系统的设计中体现。即同一个产品族中的产品可以是没有任何关系的对象,但有共同的约束,如同操作系统下的按钮和文本框,按钮与文本框无直接关系,但它们都属于某一操作系统的,此时具有一个共同的约束条件,即操作系统的类型。
存在多于一个的产品族
产品实例的创建,组合和表达没有被依赖
总结:
- 抽象工厂模式中,产品等级结构即产品的继承结构,产品族是指由同一个工厂生产的不同产品等级结构中的一组产品。
- 一个产品族很长时,抽象工厂模式工厂类的数量远远小于工厂方法模式
建造者模式
建造者概述
客户端只需知道所需建造者的类型即可;建造者模式关注如何一步一步地创建一个复杂对象,不同的建造者定义了不同的创建过程。
建造者模式定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式结构与实现
结构:
- Builder抽象建造者:它为创建一个产品对象的各个部件指定抽象接口,在该接口中一般声明两类方法:一类方法是buildPartX()[用于创建复杂对象的各个部件];另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
- ConcreteBuilder具体建造者:它实现了Builder接口,实现了各个部件的具体构造和装配方法,定义并明确所创建的复杂对象,还可以提供一个方法返回创建好的复杂产品对象(该方法也可由抽象建造者实现)
- Product产品:它是被构建的复杂对象,包含多个组成部件,具体构建者创建该产品的内部表示并定义它的装配过程。
- Director指挥者:指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制实现),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。
实现:
public class Product {
private String partA; // 定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
...// set get方法
}
抽象建造者类中定义了产品的创建方法和返回方法
public abstract class Builder {
// 创建产品对象
protected Product product = new Product();
public abstract void builderPartA();
public abstract void builderPartB();
public abstract void builderPartC();
// 返回产品对象
public Product getResult() {
return product;
}
}
抽象类Builder中声明了一系列抽象的build方法,用于创建复杂产品的各个部件,具体建造过程在ConcreteBuilder中实现
在ConcreteBuilder中实现了build方法,通过调用Product的set方法给产品对象的成员变量设值,不同的具体建造者在实现build方法时有所区别,如set方法的参数不一样。
public class ConcreteBuilder1 extends Builder {
public void buildePartA() {
product.setPartA("A1");
}
public void buildePartB() {
product.setPartA("B1");
}
public void buildePartC() {
product.setPartA("C1");
}
}
除此之外,在建造者模式中还引入了指挥类Director,此类有两个作用:一方面隔离客户端与创建过程;另一方面它控制产品对象的创建过程,决定build方法调用的次数和顺序。
指挥者Director针对抽象建造者编程,客户端只需知道具体建造者的类型便可通过指挥者类调用建造者的相关方法,返回一个完整产品对象。
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void setBuilder(Builder builder) {
this.builder = builder;
}
// 产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
...
Builder builder = new ConcreteBuilder1();
Director director = new Director(builder);
Product product = director.construct();
...
建造者模式返回一个完整的复杂产品,抽象工厂模式返回一系列相关的产品;在抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而在建造者模式中,客户端通过指定具体建造者类型来指导Director类如何去生成对象,侧重于一步步构造一个复杂对象并返回。
配置文件读取
public class XMLUtil {
// 该方法用于从XML配置文件中提取具体类的类名,并返回一个实例对象
public static Object getBean() {
try{
// 创建DOM文档对象
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = docFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(File file);
//获取包含类名的文本节点
NodeList nodeList = doc.getElementByTagName("className");
Node classNode = nodeList.item(0).getFirstChild();
String className = classNode.getNodeValue();
// 通过类名生成实例对象并将其返回
Class cls = Class.forName(className);
Object obj = cls.newInstance();
return obj;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
}
当需要增加新的具体角色建造者时只需将新增具体角色建造者作为抽象角色建造者的子类,原有代码不修改,符合开闭原则
Director指挥者类的几种方式
省略Director:
简化系统结构,可将Director和抽象建造者Builder进行合并,在Builder中提供逐步构建复杂产品对象的construct()方法。由于Builder通常为抽象类,因此可将construct()方法定义为静态static方法。
public abstract class Builder {
// product以及construct为静态成员
protected static Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public static Product construct(Builder builder) {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return product;
}
}
除此之外,可将construct()方法中的参数去掉,直接在construct()方法中调用build方法
public abstract class Builder {
// product以及construct为对象成员
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product construct() {
this.buildPartA();
this.buildPartB();
this.buildPartC();
return product;
}
}
省略指挥类会加重抽象建造者类的职责。若构建产品的组成部分较多,使用指挥者类更符合单一职责原则。
钩子方法:
建造者模式除了可以逐步构建一个复杂产品对象外,还可以通过Director类更加精细地控制产品的创建过程,如增加一类称为钩子方法(Hook Method)的特殊方法来控制是否对某个build方法进行调用。
钩子方法的返回类型通常为boolean类型,方法名一般为isXXX(),钩子方法定义在抽象建造者类中。在具体建造者类中提供一个默认实现,其返回值为false;
public abstract class Builder {
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
// 钩子方法
public boolean isNotA() {
return false;
}
public Product getResult() {
return product;
}
}
若某个产品无需partA组件,则对应的具体构造器NotPartABuilder将覆盖isNotA()方法,并将返回值置true;
public class NotPartABuilder {
public void buildPartA() {}
public void buildPartB() {}
public void buildPartC() {}
// 覆盖钩子方法
public boolean isNotA() {
return true;
}
}
此时指挥者类需要加一个对isNotA()方法的判断:
public class Director {
public Product construct(Builder builder) {
Product product;
builder.builderPartB();
builder.builderPartC();
if (!builder.isNotA()) {
builder.builderPartA();
}
product = builder.getResult();
return product;
}
}
通过引入钩子方法,可以在Director中对复杂产品的构建进行精细的控制,不仅指定build方法的执行顺序,还可控制是否需要执行某个build方法。
优缺点及适用环境
优点:
- 产品本身与产品的创建解耦
- 具体建造者相对独立,客户端使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,故而增加新的具体建造者无需修改原有类库的代码,符合开闭原则。
- 可以更加精细地控制产品对象的创建过程,若需要创建复杂对象并希望系统具有很好的灵活性和可扩展性可以考虑使用建造者模式。
缺点:
- 使用范围有限制:建造者模式创建的产品一般有较多的共同点,其组成部分相似,若产品间差异性很大,如很多组成部分不同则不适用。
- 产品的内部变化复杂,需要定义很多具体建造者类来实现这种变化,导致系统变得庞大,增加理解难度和运行成本。
适用环境:
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量。
- 需要生成的产品对象的属性互相依赖,需要指定生成顺序。
- 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类将创建过程封装在指挥者类中,而不在建造者类和客户类中。
- 隔离复杂对象的创建和使用,使得相同的创建过程可以创建不同的产品。
原型模式
概述
一种特殊的创建模式,通过复制一个已有对象来获取更多相同或者相似的对象。原型模式可以提高相同类型对象的创建效率。
定义:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
工作原理:将一个原型对象传给要发动创建的对象(即客户端),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程。创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责原型对象的克隆方法来实现。
注意:通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址。
结构与实现
结构:
- Prototype抽象原型类:声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类,接口,具体实现类。
- ConcretePrototype具体原型类:实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
- Client客户类:在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
浅克隆与深克隆:根据在复制原型对象的同时是否复制包含在原型对象中引用类型的成员变量,原型模式分为浅克隆和深克隆
浅克隆:
原型对象的成员变量是值类型(基本数据类型),则复制一份给克隆对象
原型对象的成员变量是引用类型,则将引用类型的地址复制一份给克隆对象。
深克隆:
无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
原型模式实现:实现的关键在于如何实现克隆方法
通用实现方法:通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,同时将相关的参数传入新创建的对象中,保证他们的成员变量相同。
public abstract class Prototype {
public abstract Prototype clone();
}
public class ConcretePrototype extends Prototype {
private String attr; // 成员变量
...set,get方法...
// 克隆方法
public Prototype clone() {
Prototype prototype = new ConcretePrototype();// 创建新对象
prototype.setAttr(this.attr);
return prototype;
}
}
在客户类中只需要创建一个ConcretePrototype对象作为原型对象,然后调用clone方法即可
...
ConcretePrototype prototype = new ConcreteProtype();
prototype.setAttr("A");
ConcretePrototype copy = (ConcretePrototype)prototype.clone();
...
对于引用类型的对象,可以在clone方法中通过赋值的方式实现复制,这是浅复制;若在clone方法中通过创建一个全新的成员对象来实现复制,则是深克隆实现。
2Java中的clone()方法和Cloneable接口
Object类提供了一个clone()方法,可将一个Java对象复制一份,实现浅复制。
注意:能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。若一个类没有实现这个接口却调用clone方法,则抛出CloneNotSupportedException
public class ConcretePrototype implements Cloneable {
...
public Prototype clone() {
Object object = null;
try {
object = super.clone(); // 浅克隆
} catch (CloneNotSupportedException e) {
sout(e.msg);
}
return (Prototype) object;
}
...
}
Java中的clone满足:
- 对任何对象x,x.clone() != x 不是同一个对象;
- 对任何对象x,x.clone().getClass() == x.getClass() 类型一致;
- 若对象x的equals()方法重写定义恰当,则x.clone().equals(x)成立;
获取对象克隆:
- 派生类覆盖基类的clone方法,并声明public
- 在派生类的clone方法中调用super.clone()
- 派生类需要实现Cloneable接口
深克隆方式:Java中可以通过序列化Serialization等方式实现深克隆。(必须实现Serializable接口)
public Prototype deepClone() throw IOException,ClassNotFoundException,OptionalDataException {
// 对象写入字节数组缓冲流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将对象从流中取出
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Prototype)ois.readObject();
}
原型管理器
原型管理器Prototype Manager将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,若需要某个原型对象的一个克隆,可通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。
public class PrototypeManager {
private Map<String, Prototype> prototypeMap = new HashMap<String, Prototype>();
public PrototypeManager() {
prototypeMap.put("A", new ConcretePrototypeA());
}
public void add(String key, Prototype prototype) {
prototypeMap.put(key, prototype);
}
public Prototype get(String key) {
return ((Prototype)prototypeMap.get(key)).clone();
// 通过克隆方法创建新对象
}
}
实际开发中将PrototypeManager设计为单例类。
优缺点及适用环境
原型模式快速创建大量相同或相似的对象;很多软件的复制和粘贴就是基于此的应用
优点:
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程;
- 扩展性好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置或注解中,增减产品类对原有系统无影响。
- 无须专门的工厂类来创建产品,简化创建结构;
- 可以使用深克隆的方式保存对象的状态。
缺点:
- 需要为每一个类配备克隆方法,且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码,违反了开闭原则。
- 深克隆当对象之间存在多重嵌套引用时实现繁琐。
适用环境:
- 创建新对象成本比较大(例如初始化需要占用较长的时间,占用太多的CPU资源或网络资源)
- 系统要保存对象的状态,而对象的状态变化很小
- 需要避免使用分层次的工厂类来创建分层次的对象,且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便
单例模式
概述
单例模式确保系统中某个类的实例对象的唯一性
定义:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例
单例模式是一种对象创建型模式。单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
结构与实现
构造方法私有,对外提供静态方法获取实例。
public class Singleton {
private static Singleton instance = null; // 静态私有变量
// 私有构造函数
private Singleton() {}
// 静态公有工厂方法,返回唯一实例
public static Singleton getInstance() {
if(instance == null) {
instance == new Singleton();
}
return instance;
}
}
单例模式的实现要注意三点:
- 构造器私有
- 提供一个类型为自身的静态私有成员变量
- 提供一个公有的静态工厂方法
Eager Singleton
简单单例类:
定义静态变量的时候实例化单例类,故而类加载时单例对象已存在
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
Lazy Singleton
与Eager不同的是Lazy单例类在第一次被引用时将自己实例化,而在Lazy单例类加载时不会自行实例化,又称为延迟加载;为了避免多个线程同时调用,可使用关键字synchronized
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
// 使用synchronized关键字对方法加锁,确保任意时刻只有一个线程可执行该方法
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
以上代码保证线程安全但是效率过低,故而改良如下
...
public static LazySingleton getInstance() {
if (instance == null) {
synchronized(LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
...
以上改进存在线程不安全的问题,多个线程进入if导致单例对象不唯一
继续改良,进行双重检查
public class LazySingleton {
// volatile修饰的成员变量可确保多个线程都能够正确处理
private volatile static LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
// 第一重检查判断
if (instance == null) {
// 锁定代码块
synchronized(LazySingleton.class) {
// 第二重检查判断
if (instance == null) {
// 创建单例实例
instance = new LazySingleton();
}
}
}
return instance;
}
}
总结:
Eager 调用速度,反应时间优于 Lazy
Lazy 资源利用效率优于 Eager(Eager无论是否愿意都会在系统加载时创建单例对象)
静态内部类实现单例模式
Eager单例类不能实现延迟加载,并且始终占据内存;Lazy单例类线程安全控制导致性能慢。
IoDH(Initialzation on Demand Holder)技术实现单例模式
在IoDH中,需在单例类中增加一个静态内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance方法返回给外部使用
// IoDH
public class Singleton {
private Singleton() {}
// 静态内部类
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
}
加载外部类时不会自动加载静态内部类,加载静态内部类时自动加载外部类;??
故而IoDH可以实现延迟加载,保证线程安全,不影响性能。
优缺点与适用环境
优点:
- 单例模式提供了对唯一是来的受控访问
- 系统内存中只存在一个对象,节约系统资源
- 允许可变数目的实例
缺点:
- 单例模式没有抽象层,扩展困难
- 单例类的职责过重,一定程度违背了单一职责原则:将对象的创建和对象本身的功能耦合在一起
- 单例长时间不被利用可能会被垃圾回收
适用环境:
- 系统只需要一个实例对象,如资源管理器;或资源消耗太大只允许创建一个对象
- 客户调用类的单个实例只允许使用一个公共访问点,除了该访问点,不能通过其他途径访问该实例