创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
创建型模式包括:
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
工厂模式
工厂模式就是利用一系列的工厂对象来进行对象创建,对用户隐藏具体的创建细节,什么都由工厂去处理。
要创建哪个对象,只需要告诉工厂需要创建哪个对象,就能让工厂直接返回创建好的对应对象。
应用场景
-
创建对象较简单:当对象的创建比较简单,并不包含复杂业务逻辑时,可以使用简单工厂模式。
-
一个类需要创建多种类的对象:当一个类需要创建多种类的对象,而不想直接暴露创建细节时,可以使用工厂方法模式。
-
复杂对象的创建:当需要创建的对象包含较多的属性、方法和业务逻辑时,可以使用抽象工厂模式,通过对不同类型对象的创建进行抽象和封装,降低复杂度。
-
运行时动态配置对象:当需要在运行时动态配置对象时,可以通过反射等方式来实现工厂模式,实现动态创建对象。
-
依赖倒置和控制反转:当需要实现依赖倒置和控制反转时,可以使用工厂模式来创建对象,并通过其他方式进行依赖注入。
总的来说,适用于需要创建大量、多样化对象的场景,同时需要降低耦合性的应用场景均可以考虑使用工厂模式。
简单工厂模式
通过一个工厂类来创建不同的对象,调用者只需要告诉工厂需要哪种类型的对象即可。
最终的感觉就是
首先创建一个动物工厂
AnimalFactory animalFactory = new AnimalFactory();
然后我告诉这个工厂我需要哪种动物对象
Animal cat = animalFactory.getAnimal("CAT");
这样,我就获得了一个Cat对象。
当然,还可以将能够创建的类型构成一个枚举:
public enum AnimalType {
CAT, DOG, SHEEP
}
然后让AnimalFactory接收一个AnimalType的枚举,并传入:
Animal cat = animalFactory.getAnimal(AnimalType.CAT);
优雅!
工厂方法模式
工厂方法模式就是给每一种类都创建一个对应的工厂。
首先得有一个工厂的接口:
public interface AnimalFactory {
Animal getAnimal();
}
与简单工厂不同的是,抽象工厂的调用者在调用的时候并不需要知道这个工厂能够创建哪些对象。
因为如果是使用简单工厂方法,你还得知道AnimalFactory能够创建哪些对象:Cat,Gog,Sheep;
但是如果你使用的工厂方法模式的话,调用者只需要知道创建的对象是个Animal就好,并不需要知道他拿到的具体是个Cat类,还是个Dog类,这样就实现了客户端与具体产品实现解耦。
Animal animal = animalFactory.getAnimal();
想象一下我把AnimalFactory作为方法的参数:
private void doingSomething(AnimalFactory animalFactory){
Animal animal = animalFactory.getAnimal();
animal.cry();
}
我需要关心我创建的Animal是Cat还是Dog吗?
不需要。
抽象工厂模式
抽象工厂则是对工厂的进一步抽象,实现工厂之间的解耦合,由一个超级工厂来创建其他工厂,利用这个“工厂的工厂”来创建工厂,然后再由创建的工厂来创建所需要的对象。
简单一句话,就是抽象工厂中的具体工厂能够创建多种对象,这些对象往往都是同一种类型的,同一个”族“的。
抽象工厂 = 简单工厂 + 工厂方法
首先来看看工厂的接口是什么样的?
public interface AnimalFactory {
Animal getDog();
Animal getCat();
Animal getSheep();
}
首先现在有两个维度:动物,宠物店,
这两个维度交叉会产生大量的类,比如动物有m种,宠物店有n种,那么总共会有m * n种组合,也就是需要创建m * n个类。
这样的话在创建对象的时候实在是太麻烦了,有没有一种方法可以简化创建对象的过程呢?
答案就是我们的抽象工厂方法。
首先,我的目的是获取动物,那么事实上我所需要了解的只是我需要哪种动物就行,至于这种动物它来自哪个宠物店,关我什么事。
所以,我更多的精力应该是放在我需要的动物类型上,故而AnimalFactory接口具有具有三个方法,分别对应着可以获得的动物种类。
详细见这里
工厂方法和抽象工厂的区别
-
抽象工厂模式强调创建一组相关或者相互依赖的产品,而工厂方法模式强调创建单个产品。
-
在抽象工厂模式中,具体工厂会创建一组产品,这些产品具有相同的主题,例如一个工厂可以创建不同颜色和形状的产品,而在工厂方法模式中,每个具体工厂只能创建单个产品,例如一个工厂只能创建具有指定形状的产品。
-
抽象工厂模式需要增加新的产品族时,需要修改接口和所有具体实现,而工厂方法模式需要增加新的产品时,只需要增加一个新的具体工厂,不会影响其他代码。
-
抽象工厂模式的抽象层级更高,建立在多个工厂方法模式基础之上。工厂方法模式通常只有一个层级的方法。
综上所述,抽象工厂模式适用于需要创建一组相关的产品族的场景,而工厂方法模式适用于需要创建单一产品的场景,并且两种模式在不同的场景下产生不同的效果。
单例模式
有的类只需要一个唯一的实例对象即可,那么就可以需要实现单例模式,单例模式的实现方法有很多种,比如懒汉式、饿汉模式等。
应用场景
-
资源的共享访问:当系统内的多个模块需要使用同一个资源对象时,可以考虑使用单例模式来管理该资源对象,保证其唯一性,避免重复创建或者资源访问冲突的问题。
-
数据库连接池:在Web应用程序中,因为频繁的创建数据库连接对象和释放数据库连接对象会导致系统资源的浪费和系统性能的降低,因此可以使用单例模式来管理数据库连接池,保证连接池中只有一定数量的连接对象,避免创建过多的数据库连接对象,从而提高系统的性能。
-
唯一序列号的生成器:在一些应用场景中,需要保证唯一序列号的生成,例如订单编号、ID号等,这时候可以使用单例模式来管理序列号的生成器,保证序列号的唯一性。
-
日志对象的管理:在一个多线程的系统中,在对日志对象进行并发操作时,可能会导致日志的输出顺序不一致,因此可以使用单例模式来管理日志对象,保证日志的输出顺序一致。
-
配置管理器的设计:在系统中,需要读取配置文件、参数等信息。可以使用单例模式来创建配置管理器,保证全局仅有一个配置管理器,防止配置信息的重复载入和相互覆盖的问题。
饿汉式
我不管你用不用,反正我创建好了等你用
最简单的方式,只需要以下三步即可:
- 将构造方法私有化
- 类的内部创建对象
- 对外暴露获取方法
试试?
class Singleton {
private final static Singleton instance = new Singleton();
private Singleton() {}
public getInstance() {
return instance;
}
}
简单吧?
简单。
好用吧?
不好用。
为什么?
- 系统启动慢:因为单例对象在类初始化时就已经创建好了,所以如果单例对象的初始化需要耗费大量的时间或者是消耗大量的系统资源,就会导致系统启动时间过长。
- 浪费系统资源:类加载的时候就创建了一个实例,但是我不一定这个时候用它,我想要的是我需要用它的时候它才被创建,而不是一开始就创建。
懒汉式
你第一次用的时候我再创建
我刚开始不创建实例,只有当你第一次调用getInstance方法的时候我才创建。
class Singleton {
private static Singleton instance;
private Singleton() {}
public Singleton getInstance() {
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
好像也挺简单的哦,是不是也有什么缺点呢?
对滴,它不能确保线程安全。
什么意思呢?
假设我现在有两个线程同时调用getInstance方法,此时他们俩都对instance是否为null进行判断。
结果呢,两个都发现是个空,然后两个线程都创建了一个新的对象,导致破坏了单例性。
既然你线程不安全,那么加锁不就好了:
class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized Singleton getInstance() {
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
但是,加锁会导致效率大大降低。
双重检查
正确的加锁位置
现在来想一下,上面之所以加锁,是为了防止创建多个实例,也就是说在创建实例的时候是需要同步的,而获取实例并不需要同步。
而创建实例只有一次,获取实例却可以有很多次,上面的代码将二者全部锁住,导致获取实例的时候也需要同步,大大降低了性能。
那么我为什么不只把创建过程锁住呢?
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意需要用到关键字volatile保证instance的创建能被其他线程看到。
静态内部类
用双重检查太繁琐了?
没事,java的静态内部类自带懒加载和线程安全。
class Singleton {
private Singleton() {}
// 创建一个静态内部类来存储唯一实例
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
// 只需要返回静态内部类的实例即可
public Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
静态内部类在外部类装载时不会实例化,当调用的时候才会装载并实例化,且JVM保证了其装载时的线程安全性。
枚举
你以为静态内部类已经够优雅了?
错!
还有更优雅的:
enum Singleton {
INSTANCE;
public void doingSomething() {
System.out.println("doing something...");
}
}
这种方法可以保证所有的要求,既可以保证懒加载,还可以保证线程安全,甚至可以防止反序列化重新创建新的对象。
建造者模式
建造者模式是用来创建一个复杂对象的,一个复杂对象可能需要由一个又一个的简单对象一步步构成最终所需要的复杂对象,往往这个构建的过程是不变的,变的是简单对象。而建造者模式则是隐藏了这一过程,由不同Builder实现对不同型号的简单对象的构造。
如组装一个电脑需要许多配件,组装的过程是固定的,变的只是配件而已,可以使用A公司生产的显卡,也可以使用B公司生产的显卡,那就可以对应地用A公司的Builder和用B公司对应的Builder来进行对电脑的构造。
应用场景
-
当需要创建复杂对象,但又不想让客户端直接调用构造函数时使用。
-
当需要创建多个相似对象时使用,使用建造者模式可以避免代码的重复。
-
当对象的创建过程需要进行多个步骤,且需要灵活的配置时,可以使用建造者模式来更好地实现。
-
当需要创建不同种类的对象时,可以通过扩展建造者模式中的抽象建造者类,从而实现不同种类对象的创建。
-
当需要隔离复杂对象的创建和使用时,可以使用建造者模式。这样可以降低系统的耦合度,提高系统的可维护性和可扩展性。
举例
假如现在有一个电脑对象:
public class PC{
private String videoCard;
private String cpu;
private String memory;
}
那么就需要有一个Builder的抽象类:
public class PCBuilder{
public abstract String makeVideoCard();
public abstract String makeCPU();
public abstract String makeMemory();
}
然后A公司的Builder继承PCBuilder:
public class ABuilder extends PCBuilder{
public abstract String makeVideoCard(){
return "A公司的显卡";
}
public abstract String makeCPU(){
return "A公司的CPU";
}
public abstract String makeMemory(){
return "A公司的内存";
}
}
B公司的Builder继承PCBuilder:
public class BBuilder extends PCBuilder{
public abstract String makeVideoCard(){
return "B公司的显卡";
}
public abstract String makeCPU(){
return "B公司的CPU";
}
public abstract String makeMemory(){
return "B公司的内存";
}
}
再由一个Director类进行构建指导:
public class Director{
private PCBuilder builder;
public Director(PCBuilder builder){
this.builder = builder;
}
public PC construct(){
PC pc = new PC();
pc.setVideoCard(builder.makeVideoCard());
pc.setCPU(builder.makeCPU());
pc.setMemory(builder.makeMemory());
return pc;
}
}
测试:
public class BuilderDemo{
public static void main(String[] args){
// 创建Builder类
PCBuilder builderA = new ABuilder();
PCBuilder builderB = new BBuilder();
// 创建指导类
Director directorA = new Director(builderA);
Director directorB = new Director(builderB);
// 然后分别进行组装
PC pcA = directorA.construct();
PC pcB = directorB.construct();
}
}
当然还有一个比较常用的方法是利用内部类来进行构建,这里就不作实现了,详细看builder-pattern-extend
。
原型模式
为了方便复制对象生成一个克隆方法,必须继承一个Cloneable类。