创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
工厂方法模式
工厂模式重点就在“工厂”二字,理解工厂,就理解了工厂模式(工厂方法与抽象工厂模式)
简单工厂模式
概念
这种模式又叫静态工厂方法模式。它是通过将创建对象的过程交给一个类,由这个类来创建对象,这个类就是工厂。而被创建的实例通常都具有共同的父类。这里的实例我们叫它产品。
之所以叫静态工厂是因为工厂类是写死的,所有的创建对象都在这个类中添加。(若不理解,可以比较下面的工厂方法模式一起理解)
组成部分
- 接口:放着各个产品类的共有的功能
- 产品类:实现接口,重写方法
- 工厂类(核心):负责创建对象,也可以实现相关的逻辑
实现代码
//下面的代码应该是多个文件,这里全部写在一起了,如果想测试注意分开建立文件
//接口:电脑组件
public interface ComputerPart {
void getPart();
}
//键盘类
public class KeyBoard implements ComputerPart {
@Override
public void getPart() {
System.out.println("买到键盘了");
}
}
//显示器类
public class Screen implements ComputerPart {
@Override
public void getPart() {
System.out.println("买到电脑显示器了");
}
}
//工厂类:电脑组件工厂
public class ComputerFactory {
//这里使用static 不是因为它叫静态工厂,只是为了方便调用(易于理解)
public static KeyBoard getKeyBoard() {
return new KeyBoard();
}
public static Screen getScreen() {
return new Screen();
}
//通过反射来创建对象(常用)
public static ComputerPart getType(String type) throws IllegalAccessException, InstantiationException {
if ("keyboard".equalsIgnoreCase(type)) {
return KeyBoard.class.newInstance();
} else if ("screen".equalsIgnoreCase(type)) {
return Screen.class.newInstance();
} else {
return null;
}
}
}
//测试类
public class Test {
public static void main(String[] args) {
ComputerPart kb = ComputerFactory.getKeyBoard();
kb.getPart();
ComputerPart s = ComputerFactory.getScreen();
s.getPart();
//通过反射来创建对象
ComputerPart kb1 = ComputerFactory.getType("keyboard");
ComputerPart s1 = ComputerFactory.getType("screen");
kb1.getPart();;
s1.getPart();
}
}
运行结果
买到键盘了
买到电脑显示器了
买到键盘了
买到电脑显示器了
优缺点
优点:它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
缺点:
- “高内聚”处理不当:工厂类集中了所有实例的创建逻辑,所以“高内聚”方面做的并不好。
- 扩展性不好:当系统中的具体产品类不断增多时,可能会出现要求工厂类也要做相应的修改。
工厂方法模式
概念
这种模式又叫多态方法模式(我觉得是不是叫动态工厂方法模式好记)。工厂方法模式是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建。这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
简单工厂模式可以将工厂看做是一个公司,又需要做计划方案,又需要执行计划,将结果返回给客户
工厂方法模式的工厂接口和工厂实现类可以看做是总公司与子公司,总公司提供计划方案,子公司只负责执行,将结果返回给客户
组成部分
- 接口:各个普通类的共有的功能
- 产品类:实现接口,重写方法
- 工厂接口(核心):存放的是工厂实现方法
- 具体工厂类:负责创建具体的产品对象
实现代码
//下面的代码应该是多个文件,这里全部写在一起了,如果想测试注意分开建立文件
//接口:电脑组件
public interface ComputerPart {
void getPart();
}
//键盘类
public class KeyBoard implements ComputerPart {
@Override
public void getPart() {
System.out.println("买到键盘了");
}
}
//显示器类
public class Screen implements ComputerPart {
@Override
public void getPart() {
System.out.println("买到电脑显示器了");
}
}
//工厂接口
public interface Factory {
public getEle();
}
//工厂类:键盘工厂
public class KeyBoardFactory implements Factory {
public KeyBoard getEle() {
return new KeyBoard();
}
}
//工厂类:显示屏工厂
public class ScreenFactory implements Factory {
public Screen getEle() {
return new Screen();
}
}
//测试类
public class Test {
public static void main(String[] args) {
Factory kbf = new KeyBoardFactory();
ComputerPart kb = kbf.getEle();
kb.getPart();
Factory sf = new ScreenFactory();
ComputerPart s = sf.getEle();
s.getPart();
}
}
运行结果
买到键盘了
买到电脑显示器了
比较工厂方法模式与简单工厂模式
- 工厂方法类的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。
- 解决简单模式的“高内聚”和扩展性问题:系统扩展需要添加新的产品对象时,仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了“开放-封闭”原则。而简单工厂模式在添加新产品对象后不得不修改工厂方法,扩展性不好。
- 工厂方法模式退化后可以演变成简单工厂模式。
抽象工厂模式
概念
抽象工厂模式是所有形态的工厂模式中最为抽象和最其一般性的。抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,能够创建多个产品族的产品对象。
这里有一个产品族的概念,如果查资料还会有一个产品结构的概念,这里说一下我的理解:
拿电脑来说
电脑有显示屏、主机、鼠标、键盘,这些组成电脑,也是电脑的结构。这就相当于是产品结构
产品族可以看做是不同牌子的电脑:
戴尔笔记本:戴尔显示屏、戴尔主机、戴尔鼠标、戴尔键盘
联想笔记本:联想显示屏、联想主机、联想鼠标、联想键盘
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
组成部分
- 产品类的接口:各个产品共有的方法
- 产品类(抽象类):实现产品类接口
- 具体产品类:继承相关的产品类
- 工厂接口:工厂需要执行的方法(产品结构)
- 具体工厂类:负责创建产品对象( 产品族)
实现代码
//下面的代码应该是多个文件,这里全部写在一起了,如果想测试注意分开建立文件
//产品类接口
public interface ComputerPart {
void get();
}
//主机类
public abstract class HostMechine implements ComputerPart {
@Override
public abstract void get();
}
//键盘类
public abstract class KeyBoard implements ComputerPart {
@Override
public abstract void get();
}
//显示器类
public abstract class Screen implements ComputerPart {
@Override
public abstract void get();
}
//戴尔主机
public class DellHostMechine extends HostMechine {
@Override
public void get() {
System.out.println("买到了DELL的主机");
}
}
//戴尔键盘
public class DellKeyBoard extends KeyBoard {
@Override
public void get() {
System.out.println("买到了DELL的键盘");
}
}
//戴尔显示器
public class DellScreen extends Screen {
@Override
public void get() {
System.out.println("买到了DELL 的显示屏");
}
}
//联想主机
public class LenovoHostMechine extends HostMechine {
@Override
public void get() {
System.out.println("买到了Lenovo 的主机");
}
}
//联想键盘
public class LenovoKeyBoard extends KeyBoard {
@Override
public void get() {
System.out.println("买到了Lenovo 的键盘");
}
}
//联想显示器
public class LenovoScreen extends Screen {
@Override
public void get() {
System.out.println("买到了Lenovo 的显示屏");
}
}
//工厂接口
public interface Factory {
HostMechine getHM();
KeyBoard getKB();
Screen getS();
}
//戴尔工厂
public class DellFactory implements Factory {
@Override
public HostMechine getHM() {
return new DellHostMechine();
}
@Override
public KeyBoard getKB() {
return new DellKeyBoard();
}
@Override
public Screen getS() {
return new DellScreen();
}
}
//联想工厂
public class LenovoFactory implements Factory {
@Override
public HostMechine getHM() {
return new LenovoHostMechine();
}
@Override
public KeyBoard getKB() {
return new LenovoKeyBoard();
}
@Override
public Screen getS() {
return new LenovoScreen();
}
}
//测试类
public class Test {
public static void main(String[] args) {
Factory dell = new DellFactory();
ComputerPart dellHM = dell.getHM();
dellHM.get();
ComputerPart dellKB = dell.getKB();
dellKB.get();
ComputerPart dellS = dell.getS();
dellS.get();
Factory lenovo = new LenovoFactory();
ComputerPart lenovoHM = lenovo.getHM();
lenovoHM.get();
ComputerPart lenovoKB = lenovo.getKB();
lenovoKB.get();
ComputerPart lenovoS = lenovo.getS();
lenovoS.get();
}
}
运行结果
买到了DELL的主机
买到了DELL的键盘
买到了DELL 的显示屏
买到了Lenovo 的主机
买到了Lenovo 的键盘
买到了Lenovo 的显示屏
单例模式(Singleton)
概念
单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
GoF对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
组成部分
- 构造方法私有化
- 定义一个私有的静态的本类对象
- 提供一个公共的静态方法返回这个类对象
实现代码
单例(也叫饿汉式单例)
public class Singleton {
//1.构造方法私有化
private Singleton(){}
//2. 定义一个私有的静态的本类对象
private static Singleton obj = new Singleton();
//3. 提供一个公共的静态方法返回这个类对象
public static Singleton getInstance(){
return obj;
}
}
懒汉式单例(这种单例线程不安全)
public class Singleton {
//1.构造方法私有化
private Singleton(){}
//2. 定义一个私有的静态的本类对象,不初始化
private static Singleton obj;
//3. 提供一个公共的静态方法返回这个类对象
public static Singleton getInstance(){
//在第一次使用时给obj赋值
if(obj == null){
obj = new Singleton();
}
return obj;
}
}
线程安全的懒汉式单例(一)
public class Singleton {
//1.构造方法私有化
private Singleton(){}
//2. 定义一个私有的静态的本类对象,不初始化
private static Singleton obj;
//3. 提供一个公共的静态方法返回这个类对象
public synchronized static Singleton getInstance(){
//在第一次使用时给obj赋值
if(obj == null){
obj = new Singleton();
}
return obj;
}
}
线程安全的懒汉式单例(二),也叫双重检查
public class Singleton {
//1.构造方法私有化
private Singleton(){}
//2. 定义一个私有的静态的本类对象,不初始化
private static Singleton obj;
//3. 提供一个公共的静态方法返回这个类对象
public static Singleton getInstance(){
//在第一次使用时给obj赋值
if(obj == null){
synchronized(Singleton.class){
if(obj == null){
obj = new Singleton();
}
}
}
return obj;
}
}
饿汉式与懒汉式比较
- 饿汉式本身就是线程安全的,可用于多线程,懒汉式不是安全的,不支持多线程,需要加锁变成线程安全的。
- 饿汉式没有加锁,执行效率高;懒汉式需要加锁,执行效率低
- 饿汉式在类加载时就初始化,不管对象有没有使用,浪费内存;懒汉式只有在第一次调用时才会初始化,避免浪费内存
- 饿汉式在第一次调用时速度也会更快,因为其资源已经初始化完成。懒汉式第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了
- 第一种方式懒汉式比 双重检查执行效率更低
为什么是用单例?见过那些单例使用场景?
原因:
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 在整个程序空间使用全局变量,共享资源。例如:在多个线程之间,像servlet环境,共享同一个资源或者操作同一个对象
- 控制实例数目,节省系统资源。大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等,全局作用域对象,监听器。
优缺点
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
建造者模式(Builder)
概念
建造者模式,也叫生成器模式,建造对象也就是创建对象。它是将多个简单对象一步步构建成一个复杂的对象,将构建过程隐藏起来,具体的构建过程由指导者来执行。
依然那笔记本电脑来举例
- 笔记本有结构:显示器、主机、键盘
- 现在有个公司 :它可以生产显示器、主机、键盘
- 公司里有两个部门:一个策划生产戴尔笔记本方案,一个策划生产联想笔记本的方案
- 生产部门接到那个方案就建造那种方案的笔记本。
- 最后返回一个电脑笔记本对象
组成部分
- 有一个对象实类,作为生产对象
- 接口:建造者需要执行的方法
- 建造者类:负责实现建造者的执行方法
- 执行者类:负责执行安排建造者执行建造方法
- 建造者可以取得最终建造的对象
实现代码
//生产对象实类:电脑类
public class Computer {
//主机
private String hostMechine;
//键盘
private String keyBoard;
//显示器
private String screen;
public String getHostMechine() {
return hostMechine;
}
public void setHostMechine(String hostMechine) {
this.hostMechine = hostMechine;
}
public String getKeyBoard() {
return keyBoard;
}
public void setKeyBoard(String keyBoard) {
this.keyBoard = keyBoard;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
@Override
public String toString() {
return "Computer{" +
"hostMechine='" + hostMechine + '\'' +
", keyBoard='" + keyBoard + '\'' +
", screen='" + screen + '\'' +
'}';
}
}
//建造者接口:ComputerBuilder
public interface ComputerBuilder {
void makeHostMachine();
void makeKeyBoard();
void makeScreen();
Computer getComputer();
}
//建造者类
//设计师:戴尔类
public class DellBuilder implements ComputerBuilder {
Computer computer = new Computer();//在这里创建对象
//通过下面方法给对象属性赋值
@Override
public void makeHostMachine() {
computer.setHostMechine("戴尔主机");
}
@Override
public void makeKeyBoard() {
computer.setKeyBoard("戴尔键盘");
}
@Override
public void makeScreen() {
computer.setScreen("戴尔显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
//设计师:联想类
public class LenovoBuilder implements ComputerBuilder {
Computer computer = new Computer();//在这里创建对象
//下面是对象属性赋值方法
@Override
public void makeHostMachine() {
computer.setHostMechine("联想主机");
}
@Override
public void makeKeyBoard() {
computer.setKeyBoard("联想键盘");
}
@Override
public void makeScreen() {
computer.setScreen("联想显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
//执行者类
public class ComputerDirector {
//通过这个方法完成给对象属性赋值
public void makeComputer(ComputerBuilder builder) {
builder.makeHostMachine();
builder.makeScreen();
builder.makeKeyBoard();
}
}
//测试类
public class Test {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
ComputerBuilder lenovoBuilder = new LenovoBuilder();
director.makeComputer(lenovoBuilder);
Computer lenovo = lenovoBuilder.getComputer();//获取已创建的对象
System.out.println(lenovo.toString());//这不是重点,只是为了显示赋值
ComputerBuilder dellBuilder = new DellBuilder();
director.makeComputer(dellBuilder);
Computer dell = dellBuilder.getComputer();//获取已创建的对象
System.out.println(dell.toString());//这不是重点,只是为了显示赋值
}
}
运行结果
Computer{hostMechine='联想主机', keyBoard='联想键盘', screen='联想显示器'}
Computer{hostMechine='戴尔主机', keyBoard='戴尔键盘', screen='戴尔显示器'}
应用场景
- 对象的创建:Builder模式是为对象的创建而设计的模式
- 创建的是一个复合对象:被创建的对象为一个具有复合属性的复合对象
- 关注对象创建的各部分的创建过程:不同的工厂(这里指builder生成器)对产品属性有不同的创建方法
优缺点
优点:
- 建造者独立,易扩展。
- 便于控制细节风险。
缺点: - 产品必须有共同点,范围有限制。
- 如内部变化复杂,会有很多的建造类。
建设者模式与抽象工厂方法模式的区别
- 前者生产对象只有一个实体类,不需要继承接口;后者生产对象实体类需要继承接口
- 前者生产对象的方式通过第三方类(执行者),过程是隐藏的;后者生产对象通过具体的对象工厂直接生成
- 前者构建复杂的对象;后者构建简单或者复杂的对象
- 前者专注一个特定产品;后者强调一套产品
原型模式(Prototype)
概念
原型模式,重点是理解什么是原型。
原型可以理解为模板,最初的。它使用复制对象的方式来创建实例,创建出的实例与原型对象具有相同的数据结构和数据。
这里说到复制,有没有想起Object
的clone()
方法,关键点就是怎么使用这个clone()方法
特点:
- 由原型对象自身创建目标对象。也就是说,对象创建这一动作发自原型对象本身。
- 目标对象是原型对象的一个克隆。也就是说,通过Prototype模式创建的对象,不仅仅与原型对象具有相同的结构,还与原型对象具有相同的值。
- 根据对象克隆深度层次的不同,有浅度克隆与深度克隆。
浅克隆与深克隆
浅克隆
原型对象中存在引用类型的属性,这时我们通过克隆生成的对象也包含这个引用类型的属性。注意:原型中的这个属性存的是引用类型对象的地址,克隆对象中的这个属性存的也是地址。就是说原型对象与克隆对象中的引用类型属性指向的是同一个地址。
一般来说,这就是我们认为的克隆。
深克隆
相比较浅克隆,深克隆则是将原型对象中的引用类型的属性也给克隆了。这样原型对象中的引用类型对象与克隆对象中的引用类型对象指向的是不同的地址,但地址中存的内容还是相同的。
组成部分
- 实现Cloneable接口
- 手写clone()方法
实现代码
浅克隆
public class Person implements Cloneable{
// 姓名
private String name;
// 年龄
private int age;
//朋友
private List<String> list;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person clone() {
try {
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
//测试类
public class Test {
public static void main(String[] args) {
Person p = new Person();
List<String> l = new ArrayList<>();
l.add("小红");
l.add("小光");
p.setList(l);
Person p1 = p.clone();
System.out.println(p+"----"+p.getList());
System.out.println(p1+"===="+p1.getList());
//给p的list加一个值,看p1是否变化
l.add("小夏");
p.setList(l);
System.out.println(p+"----"+p.getList());
System.out.println(p1+"===="+p1.getList());
//显示list的哈希码。两个对象,类型不同哈希码可能相同,类型相同哈希码一定不同
System.out.println("原型对象的哈希码:"+p.getList().hashCode());
System.out.println("克隆对象的哈希码:"+p1.getList().hashCode());
}
}
运行结果
prototype.Person@1b6d3586----[小红, 小光]
prototype.Person@4554617c====[小红, 小光]
prototype.Person@1b6d3586----[小红, 小光, 小夏]
prototype.Person@4554617c====[小红, 小光, 小夏]
原型对象的哈希码:757314328
克隆对象的哈希码:757314328
深克隆
public class Person implements Cloneable{
//这部分与浅克隆重复,就省略了,重点看clone
public Person clone() {
try {
Person person = (Person)super.clone();
List<String> newList = new ArrayList<>();
for(String l : this.getList()) {
newList.add(l);
}
person.setList(newList);
return person;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
//测试类
public class Test {
public static void main(String[] args) {
Person p = new Person();
List<String> l = new ArrayList<>();
l.add("小红");
l.add("小光");
p.setList(l);
Person p1 = p.clone();
System.out.println(p+"----"+p.getList());
System.out.println(p1+"===="+p1.getList());
//给p的list加一个值,看p1是否变化
l.add("小夏");
p.setList(l);
System.out.println(p+"----"+p.getList());
System.out.println(p1+"===="+p1.getList());
//显示list的哈希码
System.out.println(p.getList().hashCode());
System.out.println(p1.getList().hashCode());
}
}
运行结果
prototype.Person@1b6d3586----[小红, 小光]
prototype.Person@4554617c====[小红, 小光]
prototype.Person@1b6d3586----[小红, 小光, 小夏]
prototype.Person@4554617c====[小红, 小光]
原型对象的哈希码:757314328
克隆对象的哈希码:24405192
应用场景
- 在创建对象的时候,我们不只是希望被创建的对象继承其基类的基本结构,还希望继承原型对象的数据。
- 希望对目标对象的修改不影响既有的原型对象(深度克隆的时候可以完全互不影响)。
- 隐藏克隆操作的细节。很多时候,对对象本身的克隆需要涉及到类本身的数据细节。