设计模式扫荡-创建型模式-单例、工厂、抽象工厂、原型
文章目录
单例模式(Singleton Pattern)
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
饿汉单例模式
类加载到内存后,就实例化一个单例,JVM保证线程安全
唯一缺点:不管用到与否,类装载时就完成实例化
public class Mgr01 {
//将对象设成属性、且静态化、且用final修饰表示不准修改
private static final Mgr01 INSTANCE = new Mgr01();
//私有化构造方法,不让外部new此类
private Mgr01() {};
//通过方法返回实例
public static Mgr01 getInstance() {
return INSTANCE;
}
// 获取实例方式 类+方法
// Mgr01 m2 = Mgr01.getInstance();
}
懒汉单例模式
该模式下类加载的时候不实例化,调用方法的时候再实例化
虽然达到了按需初始化的目的,但却带来线程不安全的问题
public class Mgr03 {
//作为属性,类加载时先不实例化(new)
private static Mgr03 INSTANCE;
//私有构造方法
private Mgr03() {
}
//调用本方法时再进行实例化
public static Mgr03 getInstance() {
//多个线程执行下不安全 会new出多份对象
if (INSTANCE == null) {
INSTANCE = new Mgr03();
}
return INSTANCE;
}
}
懒汉线程安全单例模式
//对该方法添加 synchroized关键字 使多个线程串行化执行这个方法
public static synchroized Mgr03 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Mgr03();
}
return INSTANCE;
}
双重检验锁的单例模式
public static Mgr06 getInstance() {
//第一重检验
if (INSTANCE == null) {
//对Mgr06加锁,线程获得该锁对象才能执行下面代码
synchronized (Mgr06.class) {
//第二重检验
if(INSTANCE == null) {
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
相比方法串行化的方式,该方式减少了同步代码块,提高了效率
在方法串行化模式下,每个线程都得进入方法逻辑判断是否存在对象,在双重检验模式下,线程一旦发现已经存在实例化的对象,直接取用,不用等待
内部类实现单例懒加载
public class Mgr07 {
private Mgr07() {
}
/*
* 内部类
* */
private static class Mgr07Holder {
//内部类里完成实例化
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance() {
return Mgr07Holder.INSTANCE;
}
}
该模式下,加载类Mgr07时不会实例化
同样这种模式线程不安全
工厂模式(Factory Pattern)
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
例如:图形作为接口、其实现类有原形、方形、椭圆形,使用工厂模式获取圆形对象、方形对象、椭圆对象等…
其UML类图如下:
//Shape接口类
public interface Shape {
void draw();
}
//椭圆类 实现Shape接口并重写draw方法
public class Rectangle implements Shape {
@Override
public void draw() {
// ...
}
}
//方形类
public class Square implements Shape {
@Override
public void draw() {
//...
}
}
//圆形类
public class Circle implements Shape {
@Override
public void draw() {
//...
}
}
创建工厂类,用于创建对象
public class ShapeFactory {
//使用 getShape 方法获取调用者想要调用的对象类型
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
客户调用方式
ShapeFactory shapeFactory = new ShapeFactory();
//获取 Circle 的对象,并调用它的 draw 方法
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
//获取 Rectangle 的对象,并调用它的 draw 方法
Shape shape2 = shapeFactory.getShape("RECTANGLE");
shape2.draw();
//获取 Square 的对象,并调用它的 draw 方法
Shape shape3 = shapeFactory.getShape("SQUARE");
shape3.draw();
优点:
-
对象创建的逻辑细节就完成了隐蔽,调用者只需要告诉工厂需要哪个形状的对象即可获得该对象
-
调用者只关心该对象的接口,即面向接口编程
-
如果你想添加一个与”形状“具有相同性质的类,只需要实现Shape接口并重写draw方法即可(这保证了程序的扩展性)
缺点:
- 每次增加一个产品时,都需要增加一个具体类并修改对象创建工厂(例子中的ShapeFactory类),在一定程度上增加了系统的复杂度
注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
抽象工厂(Abstract Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。
该超级工厂又称为其他工厂的工厂。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,每个生成的工厂都能按照工厂模式提供对象。
举例:游戏中的角色 其有一系列武器、一系列食物、一系列交通工具。
武器分为 [现代武器:AK47] [魔法武器:魔法棒]
食物分为 [现代食物:面包] [魔法食物:蘑菇]
交通工具分为 [现代工具:汽车] [魔法道具:扫帚]
如何合理创建这些对象呢?
抽象类及实现类定义(工厂模式):
//抽象类+抽象方法
//食物
public abstract class Food {
abstract void eat();
}
public class Bread extends Food{ //面包
public void printName() {
System.out.println("wdm");
}
}
public class MushRoom extends Food{ //蘑菇
public void printName() {
System.out.println("dmg");
}
}
//交通
public abstract class Vehicle {
abstract void go();
}
public class Broom extends Vehicle{ //扫帚
public void go() {
System.out.println("Car go wuwuwuwuw....");
}
}
public class Car extends Vehicle{ //汽车
public void go() {
System.out.println("Car go wuwuwuwuw....");
}
}
//武器
public abstract class Weapon {
abstract void shoot();
}
public class AK47 extends Weapon{ //AK
public void shoot() {
System.out.println("tututututu....");
}
}
public class MagicStick extends Weapon{ //魔法棒
public void shoot() {
System.out.println("diandian....");
}
}
抽象类与接口:
区别仅仅是语义上的不同:一般归结为 - 名词使用抽象类、形容词使用接口
建立抽象工厂,定义创建不同产品族对象的抽象方法:
public abstract class AbastractFactory {
abstract Food createFood(); //生成食物
abstract Vehicle createVehicle(); //生成武器
abstract Weapon createWeapon(); //生成交通工具
}
创建产品族(现代、魔法)工厂,工厂类要继承抽象工厂类并重写抽象工厂的方法
//现代工厂
public class ModernFactory extends AbastractFactory {
@Override
Food createFood() {
return new Bread();
}
@Override
Vehicle createVehicle() {
return new Car();
}
@Override
Weapon createWeapon() {
return new AK47();
}
}
//魔法工厂
public class MagicFactory extends AbastractFactory {
@Override
Food createFood() {
return new MushRoom();
}
@Override
Vehicle createVehicle() {
return new Broom();
}
@Override
Weapon createWeapon() {
return new MagicStick();
}
}
那么,最终调用代码如下
//使用抽象工厂的具体族实例化来创建对象
AbastractFactory f = new ModernFactory(); //现代工厂
Vehicle c = f.createVehicle();
c.go();
Weapon w = f.createWeapon();
w.shoot();
Food b = f.createFood();
b.printName();
//魔法工厂略...
以上代码的UML表示为
虚线箭头表示为依赖,可以分为创建create依赖与引用依赖
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象,即例子中现代工厂只提供现代产品…
缺点:产品族扩展比较困难
建造者模式(Builder Pattern)
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。
一个 Builder 类会一步一步构造最终的对象。
该 Builder 类是独立于其他对象的。
建造者模式意图将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
建造者模式主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;
由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定
现有一个复杂对象Person
public class Person { int id; String name; int age; double weight; int score; Location loc; // setter getter 略 public Person() {} } //地址对象Info class Location { String street; String roomNo; public Location(String street, String roomNo) { this.street = street; this.roomNo = roomNo; } }
该对象的特征为参数过多,并且一个对象的创建并不需要设置所有的属性,那么如何优雅的构建这个对象呢?
使用建造者模式
package com.mashibing.dp.builder;
public class Person {
//属性 ... 略
//构造方法-设置成私有
private Person() {}
/**
* 由PersonBuider充当构造器
* Prrson作为属性,返回经过加工的person对象
*/
public static class PersonBuilder {
Person p = new Person();
//主属性 非空 返回的是Person
public PersonBuilder basicInfo(int id, String name, int age) {
p.id = id;
p.name = name;
p.age = age;
// return this 代表 personbuilder对象
return this;
}
/*
* 以下为可选属性
* */
//设置重量
public PersonBuilder weight(double weight) {
p.weight = weight;
return this;
}
//设置分数属性
public PersonBuilder score(int score) {
p.score = score;
return this;
}
//设置地址属性
public PersonBuilder loc(String street, String roomNo) {
p.loc = new Location(street, roomNo);
return this;
}
//返回最终设置好的对象
public Person build() {
return p;
}
}
}
class Location {
// ... 同上
}
那么主方法调用:
// 链式调用 与 builder模式 配套使用
Person p = new Person.PersonBuilder()
.basicInfo(1, "zhangsan", 18) //主属性参数只顶你
//.score(20)
.weight(200)
//.loc("bj", "23")
.build();
其中,建造者Buider与实体Person为强关联-组合关系。
建造者模式的经典应用:
- Java中的StringBuilder
注意:与工厂模式对比,建造者更重视于一个复杂对象的装配顺序
原型模式(Prototype Pattern)
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。
当直接创建对象的代价比较大时,或一个对象的属性已经确定,需要产生很多相同对象的时候,则采用这种模式。
例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
在Java中,自带了原型模式的API
其基本原理为:在内存级别上进行对象的复制
实现原型模式需要实现标记型接口Cloneable
一般会重写clone()方法
//实现 Cloneable 接口 该接口作为标记接口 在运行时作为被检查的因素
class Person implements Cloneable {
int age = 8;
int score = 100;
//对象属性
Location loc = new Location("bj", 22);
//重写Object类的clone() method
@Override
public Object clone() throws CloneNotSupportedException {
//使用默认的克隆策略 即Object类的克隆方法
return super.clone();
}
}
class Location {
String street; //街道信息
int roomNo; //门牌号
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
}
区分深克隆和浅克隆:
以上代码在执行完后,内存模型如下
双方引用类型的属性皆指向同一块内存地址
以上成为浅克隆
如果要实现深克隆,需要对Person原型对象的引用类型属性也进行克隆,并将克隆体的引用类型属性指向新的克隆出来的引用类型
代码如下:
class Person implements Cloneable {
int age = 8;
int score = 100;
Location loc = new Location("bj", 22);
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person)super.clone();
//对于引用类型的Location同步进行克隆,并使克隆对象的指针指向它
p.loc = (Location)loc.clone();
return p;
}
}
//Location也需要实现Cloneable接口
class Location implements Cloneable {
String street;
int roomNo;
//含参构造器略...
//覆写clone()方法
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深克隆的内存模型如下:
需要注意的是
String类型在Java中作为引用类型是比较特殊的
String类型的对象一般存放于JVM中的运行时常量池,同值的串在常量池中只有一份,所以不管是深克隆还是浅克隆,String类型都指向同一块内存地址