设计模式之创建型模式

简介

        设计模式是众多开发人员对于一类或多类问题的思考和总结,针对一系列问题所形成的解决方案,运用好这些设计模式可以使得我们的代码提高代码的可重用性、可维护性、可拓展性和可读性(对于设计模式有一定理解的开发者来说)。

设计模式是由4位作者,合称为GOF所提出,主要基于以下面向对象的设计原则:

        1)对接口编程而不是对实现编程;

        2)优先使用对象组合而不是继承;

出自该书:Design Patterns - Elements of Reusable Object-Oriented Software.

三大分类:

        1)创建型模式:单例模式、工厂模式、抽象工厂模式、原型模式;

        2)结构型模式:常用模式:适配器模式、桥接模式、装饰器模式、代理模式、组合模式、外观模式、享元模式;

        3)行为型模式:责任链模式、迭代器模式、观察者模式、状态模式、策略模式、 模板模式、备忘录模式、命令模式、访问者模式、中介者模式、解释器模式;

创建型模式

        提供了⼀种在创建对象的同时隐藏创建逻辑的⽅式,使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

常用模式:工厂模式、抽象工厂模式、单例模式、建造者模式;

不常用模式:原型模式

单例模式(Singleton)

        确保一个类只有一个实例,并提供一个全局访问点。

懒汉式(懒加载,延迟创建对象

/**
 * 该方法是线程不安全的,但高效
 */
public class SingletonLazy01 {
    // 声明私有静态变量以存储类的实例
    private static SingletonLazy01 instance;

    // 构造函数是私有的,以防止来自类外部的实例化
    private SingletonLazy01() {
    }

    // 获取类实例的公共方法
    public static SingletonLazy01 getInstance() {
        // 如果实例为空,则创建一个新实例
        // 在高并发(纳秒微妙级别)场景下,线程A和B同时进来,都通过了if语句,都创建了实例,这将导致多个实例
        if (instance == null) {
            instance = new SingletonLazy01();
        }
        return instance;
    }

    /**
     * 单例对象方法
     */
    public void print() {
        System.out.println("SingletonLazy01");
    }

}


/**
 * 线程安全但并不高效
 */
public class SingletonLazy02 {
    // 声明私有静态变量以存储类的实例
    private static SingletonLazy02 instance;

    // 构造函数是私有的,以防止来自类外部的实例化
    private SingletonLazy02() {
    }

    // 这个方法通过加锁保证了线程安全,但是性能开销较大,因为在多线程环境下,每次获取实例都要进行同步等待。
    public synchronized static SingletonLazy02 getInstance() {
        // 如果实例为空,则创建一个新实例
        if (instance == null) {
            instance = new SingletonLazy02();
        }
        return instance;
    }


}

/**
 * 线程安全,双重检查锁定
 */
public class SingletonLazy03 {
    // 声明私有静态变量以存储类的实例
    private static SingletonLazy03 instance;

    // 构造函数是私有的,以防止来自类外部的实例化
    private SingletonLazy03() {
    }

    // 缩小颗粒度,提高效率
    // DCL 双重检查锁定,在多线程情况下保持高性能和线程安全
    public static SingletonLazy03 getInstance() {
        // 如果实例为空,则创建一个新实例
        if (instance == null) {
            synchronized (SingletonLazy03.class) {
                // 再次检查,以防止多个线程同时通过第一个检查
                // 如果实例仍然为空,则创建一个新实例
                if (instance == null) {
                    instance = new SingletonLazy03();
                }
            }
        }
        return instance;
    }
}


/**
 * 线程安全。解决指令重排问题
 */
public class SingletonLazy04 {
    // 声明私有静态变量以存储类的实例
    private volatile static SingletonLazy04 instance;

    // 构造函数是私有的,以防止来自类外部的实例化
    private SingletonLazy04() {
    }

    // 深究到内存模型,仍存在问题
    // instance = new SingletonLazy04();并不是原子性操作
    // 创建对象时,涉及三个步骤:1.分配内存空间 2.初始化对象 3.将instance指向刚分配的内存空间
    // 假如线程先按1-》3-》2的顺序执行,
    // 此时instance不为null,但对象可能未完成初始化,
    // 假如线程二按1-》2-》3的顺序执行,
    // 此时instance为null,但对象已初始化完成,
    // 这就是线程不安全的体现
    // 以上就成为指令重排问题
    // 解决方案:使用volatile关键字修饰instance变量,禁止指令重排
    public static SingletonLazy04 getInstance() {
        // 如果实例为空,则创建一个新实例
        if (instance == null) {
            synchronized (SingletonLazy04.class) {
                // 再次检查,以防止多个线程同时通过第一个检查
                // 如果实例仍然为空,则创建一个新实例
                if (instance == null) {
                    instance = new SingletonLazy04();
                }
            }
        }
        return instance;
    }
}

饿汉式(与懒汉相反,提前创建对象

/**
 *  饿汉式
 *  在类加载时就将实例化,不管你用不用
 */
public class SingletonHungry
{
    private static final SingletonHungry INSTANCE = new SingletonHungry();

    private SingletonHungry()
    {
    }

    public static SingletonHungry getInstance()
    {
        return INSTANCE;
    }
}

实现步骤

1)私有化构造函数;
2)提供获取单例的⽅法;

使用场景

单例模式的使用随处可见, 例如:业务系统全局只需要⼀个对象实例,⽐如发号器、 redis连接对象等、Spring IOC容器中的bean 默认就是单例、 Spring boot 中的C ontroller 、S ervice 、D ao层中通过 @Autowire的依赖注⼊对象默认都是单例的。全局变量和常量等。

工厂模式(Factory Method)

        定义一个用于创建对象的接口,由子类决定创建哪个类的对象。例如⾥⾯有统⼀下单和⽀付接⼝,具体的⽀付实现可以微信、⽀付宝、银⾏卡等。

简单⼯⼚模式(扩展性差)

        通过传⼊相关的类型来返回相应的类,这种⽅式⽐较单 ⼀,可扩展性相对较差;

//优点:将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
//缺点:
//1)⼯⼚类的职责相对过重,增加新的产品需要修改⼯⼚类的判断逻辑,这⼀点与开闭原则是相违背;
//2)即开闭原则(Open Close Principle)对扩展开放,对修改关闭,程序需要进⾏拓展的时候,不能去修改原有的代码,实现⼀个热插拔的效果;
//3)将会增加系统中类的个数,在⼀定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不⽤模式;
public class SimplePayFactory {
    public static Pay createPay(String payType) {
        if (payType.equalsIgnoreCase("ALI_PAY")) {
            return new AliPay();
        } else if (payType.equalsIgnoreCase("WECHAT_PAY")) {
            return new WechatPay();

        } else {
            // 默认支付宝支付
            return null;
        }
    }

}

public interface Pay {
//    统一下单
   void unifiedPay();
}
public class AliPay implements Pay{
    @Override
    public void unifiedPay() {
        System.out.println("支付宝支付");
    }
}

public class WechatPay implements Pay{
    @Override
    public void unifiedPay() {
        System.out.println("微信支付");
    }
}

public class Main {
    public static void main(String[] args) {
        Pay wechatPay = SimplePayFactory.createPay("WECHAT_PAY");
        wechatPay.unifiedPay();
    }
}

⼯⼚模式(扩展性强)

        通过实现类实现相应的⽅法来决定相应的返回结果,这种⽅式的可扩展性⽐较强;

//优点:符合开闭原则,增加⼀个产品类,只需要实现其他具体的产品类和具体的⼯⼚类;
//     符合单⼀职责原则,每个⼯⼚只负责⽣产对应的产品
//     使⽤者只需要知道产品的抽象类,⽆须关⼼其他实现类,满⾜迪⽶特法则、依赖倒置原则和⾥⽒替换原则 迪⽶特法则:
//        最少知道原则,实体应当尽量少地与 其他实体之间发⽣相互作⽤
//        依赖倒置原则:针对接⼝编程,依赖于抽象⽽不依赖于具体
//        ⾥⽒替换原则:俗称LSP, 任何基类可以出现的地⽅,⼦类⼀定可以出现, 对实现抽象化的具体步骤的规范
//缺点:增加⼀个产品,需要实现对应的具体⼯⼚类和具体产品类;
//        每个产品需要有对应的具体⼯⼚和具体产品类
public interface PayFactory {
    Pay getPay();

}
public class AliPayFactory implements PayFactory {
   @Override
    public Pay getPay() {
        return new AliPay();
    }

}
public class WechatPayFactory implements PayFactory {

    @Override
    public Pay getPay() {
        return new WechatPay();
    }
}
public interface Pay {
//    统一下单
   void unifiedPay();
}

public class AliPay implements Pay{
    @Override
    public void unifiedPay() {
        System.out.println("支付宝支付");
    }
}
public class WechatPay implements Pay {
    @Override
    public void unifiedPay() {
        System.out.println("微信支付");
    }
}
public class Main {
    public static void main(String[] args) {
        AliPayFactory aliPayFactory = new AliPayFactory();
        Pay pay = aliPayFactory.getPay();
        pay.unifiedPay();
    }
}

抽象⼯⼚模式

        基于上述两种模式的拓展,且⽀持细化产品

/**
 * 下单
 * 优点:
 * 1.当⼀个产品族中的多个对象被设计成⼀起⼯作时,它能保证使⽤⽅始终只使⽤同⼀个产品族中的对象
 * 2.产品等级结构扩展容易,如果需要增加多⼀个产品等级,只需要增加新的⼯⼚类和产品类即可, ⽐如增加银⾏⽀付、退款
 * 缺点:
 * 1.产品族扩展困难,要增加⼀个系列的某⼀产品,既要在抽象的⼯⼚和抽象产品⾥修改代码,不是很符合开闭原则
 * 2.增加了系统的抽象性和理解难度
 *
 */
package priv.muller.factory;

import priv.muller.factory.ali.AliOrderFactory;
import priv.muller.factory.wechat.WechatOrderFactory;

public class FactoryProducer {

    // 返回一个工厂
    public static IOrderFactory getFactory(String choice){
        if(choice.equalsIgnoreCase("WECHAT")){
            return new WechatOrderFactory();
        } else if(choice.equalsIgnoreCase("ALI")){
            return new AliOrderFactory();
        }
        return null;
    }
}

package priv.muller.factory;

/**
 * 下单
 */
public interface IOrderFactory {
    IPayFactory createPayFactory();
    IRefoundFactory createRefoundFactory();
}

package priv.muller.factory;

/**
 * 支付
 */
public interface IPayFactory {
//    统一下单
   void unifiedPay();
}

package priv.muller.factory;

/**
 * 退款接口
 */
public interface IRefoundFactory {
    void refund();
}

package priv.muller.factory.ali;

import priv.muller.factory.IOrderFactory;
import priv.muller.factory.IPayFactory;
import priv.muller.factory.IRefoundFactory;

public class AliOrderFactory implements IOrderFactory {
    @Override
    public IPayFactory createPayFactory() {
        return new AliPay();
    }

    @Override
    public IRefoundFactory createRefoundFactory() {
        return new AliRefund();
    }
}
package priv.muller.factory.ali;

import priv.muller.factory.IPayFactory;

public class AliPay implements IPayFactory {
    @Override
    public void unifiedPay() {
        System.out.println("支付宝支付");
    }
}
package priv.muller.factory.ali;

import priv.muller.factory.IRefoundFactory;

public class AliRefund implements IRefoundFactory {
    @Override
    public void refund() {

    }
    // 实现支付宝的退款逻辑

}

package priv.muller.factory.wechat;

import priv.muller.factory.IOrderFactory;
import priv.muller.factory.IPayFactory;
import priv.muller.factory.IRefoundFactory;

public class WechatOrderFactory implements IOrderFactory {
    @Override
    public IPayFactory createPayFactory() {
        return new WechatPay();
    }

    @Override
    public IRefoundFactory createRefoundFactory() {
        return new WechatRefund();
    }
}

package priv.muller.factory.wechat;

import priv.muller.factory.IPayFactory;

public class WechatPay implements IPayFactory {
    @Override
    public void unifiedPay() {
        System.out.println("微信支付");
    }
}

package priv.muller.factory.wechat;

import priv.muller.factory.IRefoundFactory;

public class WechatRefund implements IRefoundFactory {
    @Override
    public void refund() {

        // 实现微信退款逻辑
        System.out.println("微信退款");
    }
}

工厂模式和抽象工厂模式的区别

1)工厂方法模式通常只针对一个抽象产品类进行创建,而抽象工厂模式则需要针对多种抽象产品进行创建;

2)使用工厂方法模式时,往往仅需选用所需的工厂方法即可,而使用抽象工厂模式时,则需要创建必须的所有抽象产品对象;

3)工厂方法模式(Factory Method)通过让子类实现工厂接口,来决定具体应该创建哪一个产品类的实例对象。它允许我们在不改变现有代码基础上添加新的产品类型,并且可以将具体产品的实现与调用方分离开来;

4)抽象工厂模式(Abstract Factory)与工厂方法模式类似,也是用于创建一系列相关的对象。不同之处在于,抽象工厂是针对多个产品族而言的,即每个工厂可以创建多种不同类型的产品。这样的话,抽象工厂为创建一组相关或独立的对象提供了一种方式。

原型模式(Prototype)

        通过复制现有的对象来创建新的对象,避免了直接创建对象的开销

浅拷贝(通过实现 Cloneable

1)如果原型对象的成员变量是基本数据类型( int 、 double、 byte boolean char等),将复制⼀份给克隆对象,对象克隆不通过构造函数
2)如果原型对象的成员变量是引⽤类型,则将引⽤对象的地址复制⼀份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址 通过覆盖Object类的clone() ⽅法可以实现浅克隆;


public class Person implements Cloneable {
    private String name;

    private int age;

    public Person() {
        System.out.println("调用了无参构造方法");
    }

    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;
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
 public static void main(String[] args) throws CloneNotSupportedException {
            // 创建一个具体的原型对象
            Person prototype = new Person();
            prototype.setAge(25);
            prototype.setName("原型对象");

            // 克隆一个具体的原型对象
            Person clone = prototype.clone();
            clone.setName("克隆的原型对象");

            // 比较原对象和克隆对象是否相等
            System.out.println("原对象:" + prototype);
            System.out.println("新对象:" + clone);
    }

深拷贝(通过实现 Serializable 读取⼆进制流

        ⽆论原型对象的成员变量是基本数据类型还是引⽤类型, 都将复制⼀份给克隆对象,如果需要实现深克隆,可以通过序列化(Serializable)等⽅式来实现
//优点:
//    当创建新的对象实例较为复杂时,使⽤原型模式可以简化对象的创建过程,可以提⾼新实例的创建效率可辅助实现撤销操作
//    使⽤深克隆的⽅式保存对象的状态,使⽤原型模式将对象复制⼀份并将其状态保存起来,以便在需要的时候使⽤恢复到历史状态
//缺点:
//    需要为每⼀个类配备⼀个克隆⽅法,对已有的类进⾏改造时,需要修改源代码,违背了“开闭原则”
//    在实现深克隆时需要编写较为复杂的代码,且当对象之间存在多重的嵌套引⽤时,需要对每⼀层对象对应的类都必须⽀持深克隆
public class Person implements Cloneable, Serializable {
    private String name;

    private int age;

    private List<String> hobbies = new ArrayList<>();

    public Person() {
        System.out.println("调用了无参构造方法");
    }

    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;
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobbies=" + hobbies.toString() +
                '}';
    }

    public Person deepClone() {
        try {
            //输入序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            //输入反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            Person person = (Person) ois.readObject();
            return person;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

public class DeepCloneClient {

    public static void main(String[] args) {
            // 创建一个具体的原型对象
            Person prototype = new Person();
            prototype.setAge(25);
            prototype.setName("原型对象");
            prototype.getHobbies().add("篮球");

            // 克隆一个具体的原型对象
            Person clone = prototype.deepClone();
            clone.setName("克隆的原型对象");
            clone.getHobbies().add("吉他");

            // 比较原对象和克隆对象是否相等
            System.out.println("原对象:" + prototype);
            System.out.println("新对象:" + clone);
    }
}

建造者模式(Builder)

        将一个复杂对象的构建过程分离出来,使同样的构建过程可以创建不同的表示。

//优点:
//  1)客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦
//  2)每⼀个具体建造者都相对独⽴,⽽与其他的具体建造者⽆关,更加精细地控制产品的创建过程
//  3)增加新的具体建造者⽆须修改原有类库的代码,符合开闭原则
//  4)建造者模式结合链式编程来使⽤,代码上更加美观缺点
//  5)建造者模式所创建的产品⼀般具有较多的共同点,如果产品差异⼤则不建议使⽤
//缺点
//  1)建造者模式所创建的产品⼀般具有较多的共同点,如果产品差异⼤则不建议使⽤
public class Director {
    public Computer create(Builder builder) {
        builder.buildCpu();
        builder.buildMemory();
        builder.buildPower();
        builder.buildDisk();
        return builder.createComputer();
    }

}
public interface Builder {
    void buildCpu();

    void buildDisk();

    void buildPower();

    void buildMemory();

    Computer createComputer();
}
public class HighComputerBuilder implements Builder{
    private Computer computer = new Computer();
    @Override
    public void buildCpu() {
        computer.setCpu("高配CPU");
    }


    @Override
    public void buildDisk() {
        computer.setHardDisk("高配硬盘");
    }

    @Override
    public void buildPower() {
        computer.setPower("高配电源");
    }

    @Override
    public void buildMemory() {
        computer.setMemory("高配内存");
    }

    @Override
    public Computer createComputer() {
        return computer;
    }
}
public class LowComputerBuilder implements Builder{
    private Computer computer = new Computer();
    @Override
    public void buildCpu() {
        computer.setCpu("低配CPU");
    }


    @Override
    public void buildDisk() {
        computer.setHardDisk("低配硬盘");
    }

    @Override
    public void buildPower() {
        computer.setPower("低配电源");
    }

    @Override
    public void buildMemory() {
        computer.setMemory("低配内存");
    }

    @Override
    public Computer createComputer() {
        return computer;
    }
}
public class Computer {
    // 需要构建的属性
    private String cpu;
    private String memory;
    private String hardDisk;
    private String power;

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }

    public String getPower() {
        return power;
    }

    public void setPower(String power) {
        this.power = power;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", memory='" + memory + '\'' +
                ", hardDisk='" + hardDisk + '\'' +
                ", power='" + power + '\'' +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值