创建型设计模式
1.创建型模式
1.1单例模式
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 何时使用:当您想控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
- 优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
- 注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
构造器私有
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
-
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
-
在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
-
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
单例模式的应用场景主要有以下几个方面。
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
- 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
饿汉模式
- 一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了
public class Hungry {
//可能造成浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
// singleton作为类变量并且直接得到了初始化,即类中所有的变量都会被初始化。虽然可以实现多线程的唯一实例,但无法进行懒加载
// 饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
DCL懒汉模式
- 类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}
// 避免指令重排(有可能实例对象的变量未完成实例化其他线程去获取到singleton变量)。
private volatile static LazyMan lazyMan;
//DCL懒汉式,双重检测锁的懒汉式单例模式
public static LazyMan getInstance(){
if(lazyMan == null){
//保证类只有一个
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();//不是一个原子操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*
* 123
* 132 A
* B//此时lazyMan还没有完成构造
* 必须加volatile
*/
}
}
}
return lazyMan;
}
//多线程并发时会出错 所以要加锁
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
// 静态内部类
public class OutClass {
// 在Singleton类初始化并不会创建Singleton实例
private OutClass(){}
public static OutClass getInstance(){
return InnerClass.INSTANCE;
}
public static class InnerClass{
private static final OutClass INSTANCE = new OutClass();
}
}
用反射破坏
- 一个用getInstance创建,一个用反射创建
package juc.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazyMan {
private LazyMan(){// 构造器私有
}
// 保证原子性操作
private volatile static LazyMan lazyMan;
// 双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){// 保证这个类只有一个
if(lazyMan == null){
lazyMan = new LazyMan();// 不是原子性操作
}
}
}
return lazyMan;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 反射破坏单例
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);// 会有两个实例
}
}
- 三重检测
- 在构造函数里加锁,但是如果两个实例都是通过反射创建的就又不行了
private LazyMan(){// 构造器私有
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要用反射破坏单例模式");
}
}
}
- 加标志后又可以触发异常
private static boolean flag = false;
private LazyMan(){// 构造器私有
synchronized (LazyMan.class){
if (flag == false){
flag = true;
} else {
throw new RuntimeException("不要用反射破坏单例模式");
}
}
}
- 再用反射破环标志
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 反射破坏单例
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
flag.set(instance, false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
枚举不会被反射破坏
package juc.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
// 枚举本身就是一个类
public enum EnumSingle {
INSTANCE;
// 枚举类型不允许被继承,同样线程安全的,且只能被实例化一次。
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 枚举反编译的源码
public final class EnumSingle extends Enum {
public static EnumSingle[] values() {
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name) {
return (EnumSingle)Enum.valueOf(juc/single/EnumSingle, name);
}
private EnumSingle(String s, int i) {
super(s, i);
}
public EnumSingle getInstance() {
return INSTANCE;
}
private static EnumSingle[] $values() {
return (new EnumSingle[] {
INSTANCE
});
}
public static final EnumSingle INSTANCE = new EnumSingle("INSTANCE", 0);
private static final EnumSingle $VALUES[] = $values();
}
多例模式
1.2工厂模式
静态工厂模式(简单工厂)
-
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
-
主要解决:主要解决接口选择的问题。
-
何时使用:我们明确地计划不同条件下创建不同实例时。
-
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
-
关键代码:创建过程在其子类执行。
-
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
-
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
-
注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
-
将调用者和实现类解耦
-
只有一个工厂
-
不满足开闭原则,有新的类时,必须扩展已有代码
package factorypattern;
public class FactoryPattern {
public static void main(String[] args) {
ShapeFactory.getShape("circle").draw();
ShapeFactory.getShape("rectangle").draw();
ShapeFactory.getShape("square").draw();
}
}
interface Shape{
void draw();
}
class Rectangle implements Shape{
@Override
public void draw() {
System.out.println("Rectangle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("Square");
}
}
class Circle implements Shape{
@Override
public void draw() {
System.out.println("Circle");
}
}
// 简单工厂模式(静态工厂模式)
class ShapeFactory{
public static 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;
}
}
工厂方法模式
- 每多一个类就要加一个工厂
- 不修改已有类的前提下,增加新的工厂实现扩展
package factorypattern;
public class FactoryMethodPattern {
public static void main(String[] args) {
Car car1 = new SanlingFactory().getCar();
Car car2 = new WulingFactory().getCar();
car1.name();
car2.name();
}
}
interface Car{
void name();
}
interface CarFactory{
Car getCar();
}
class Sanling implements Car{
@Override
public void name() {
System.out.println("三菱");
}
}
class Wuling implements Car{
@Override
public void name() {
System.out.println("五菱");
}
}
class SanlingFactory implements CarFactory{
@Override
public Car getCar() {
return new Sanling();
}
}
class WulingFactory implements CarFactory{
@Override
public Car getCar() {
return new Wuling();
}
}
- 单列集合获取迭代器
- Iterator是抽象产品类,ArrayList中的Itr类是具体产品类。Collection是抽象工厂类,ArrayList是具体工厂类。在具体工厂类里创建了具体产品,是工厂方法模式。
- DataFormat类和Calendar类中的getInstance()都是工厂模式
1.3抽象工厂模式
-
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
-
主要解决:主要解决接口选择的问题。
-
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
-
如何解决:在一个产品族里面,定义多个产品。
-
关键代码:在一个工厂里聚合多个同类产品。
-
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
-
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
-
注意事项:产品族难扩展,产品等级易扩展。
-
围绕一个超级工厂创建其他工厂
package designpattern;
public class demo2 {
public static void main(String[] args) {
FactoryProducer factoryProducer = new FactoryProducer();
factoryProducer.getFactory("shape").getShape("circle").draw();
factoryProducer.getFactory("color").getColor("red").fill();
}
}
// 创建接口
interface Shape {
void draw();
}
interface Color{
void fill();
}
// 创建实现接口的实体类
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("长方形");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("正方形");
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("圆形");
}
}
class Red implements Color{
@Override
public void fill() {
System.out.println("红色");
}
}
class Green implements Color{
@Override
public void fill() {
System.out.println("绿色");
}
}
class Blue implements Color{
@Override
public void fill() {
System.out.println("蓝色");
}
}
// 创建抽象工厂类
abstract class AbstractFactory{
public abstract Shape getShape(String shape);
public abstract Color getColor(String color);
}
// 创建扩展了AbstractFactory的两个工厂类
class ShapeFactory extends AbstractFactory{
@Override
public Shape getShape(String shape) {
if (shape == null){
return null;
}
if (shape.equalsIgnoreCase("Rectangle")){
return new Rectangle();
} else if (shape.equalsIgnoreCase("Square")){
return new Square();
} else if (shape.equalsIgnoreCase("Circle")){
return new Circle();
}
return null;
}
@Override
public Color getColor(String color) {
return null;
}
}
class ColorFactory extends AbstractFactory{
@Override
public Shape getShape(String shape) {
return null;
}
@Override
public Color getColor(String color) {
if (color == null){
return null;
}
if (color.equalsIgnoreCase("RED")){
return new Red();
} else if (color.equalsIgnoreCase("GREEN")){
return new Green();
} else if (color.equalsIgnoreCase("BLUE")){
return new Blue();
}
return null;
}
}
// 创建一个工厂生成器类,通过传递形状或颜色信息来获取工厂
class FactoryProducer{
public static AbstractFactory getFactory(String factory){
if (factory.equalsIgnoreCase("SHAPE")){
return new ShapeFactory();
} else if (factory.equalsIgnoreCase("COLOR")){
return new ColorFactory();
}
return null;
}
}
1.4建造者模式
-
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
-
主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
-
何时使用:一些基本部件不会变,而其组合经常变化的时候。
-
如何解决:将变与不变分离开。
-
关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
-
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
-
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
-
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
-
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
常规方法
- Builder.java
// 抽象的建造者
public abstract class Builder {
abstract void buildA();
abstract void buildB();
abstract void buildC();
abstract void buildD();
// 得到产品
abstract Product getProduct();
}
- Worker.java
// 具体的建造者
public class Worker extends Builder{
private Product product;
public Worker(){
this.product = new Product();
}
@Override
void buildA() {
product.setBuildA("面包");
System.out.println("面包");
}
@Override
void buildB() {
product.setBuildB("番茄");
System.out.println("番茄");
}
@Override
void buildC() {
product.setBuildC("牛肉");
System.out.println("牛肉");
}
@Override
void buildD() {
product.setBuildD("芝士");
System.out.println("芝士");
}
@Override
Product getProduct() {
return product;
}
}
- Product.java
package builderpattern;
public class Product {
private String buildA;
private String buildB;
private String buildC;
private String buildD;
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
- Director.java
package builderpattern;
// 指挥:负责构建一个工程
public class Director {
// 控制构建顺序
public Product build(Builder builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}
- Test.java
public class Test {
public static void main(String[] args) {
Director director = new Director();
Product build = director.build(new Worker());
System.out.println(build.toString());
}
}
静态内部类实现无序装配
- 没有指挥者:如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。
- Builder.java
// 抽象的建造者
public abstract class Builder {
abstract Builder buildA(String msg);
abstract Builder buildB(String msg);
abstract Builder buildC(String msg);
abstract Builder buildD(String msg);
// 得到产品
abstract Product getProduct();
}
- Worker.java
// 具体的建造者
public class Worker extends Builder{
private Product product;
public Worker() {
this.product = new Product();
}
@Override
Builder buildA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder buildB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder buildC(String msg) {
product.setBuildC(msg);
return this;
}
@Override
Builder buildD(String msg) {
product.setBuildD(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
- Product.java
public class Product {
private String buildA = "面包";
private String buildB = "番茄";
private String buildC = "牛肉";
private String buildD = "芝士";
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
- Test.java
public class Test {
public static void main(String[] args) {
Worker worker = new Worker();
// 链式编程
Product product = worker.buildA("可乐")
.buildD("全家桶")
.getProduct();
System.out.println(product.toString());
}
}
1.5原型模式
-
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
-
主要解决:在运行期建立和删除原型。
-
何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
-
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
-
关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
-
优点: 1、性能提高。 2、逃避构造函数的约束。
-
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
-
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
-
浅拷贝深拷贝链接