23种设计模式详解-创建模式篇

文章目录

23种设计模式

设计模式的分类

总体来说设计模式分为三大类:

创建型模式,五种:工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式

结构模式,七种:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式

行为模式,十一种:策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式

创建模式(5种)

工厂模式

简单工厂模式

定义:定义了一个创建对象的类,由这个类来封装实例化对象的行为

举例:

A工厂一共生产三种类型的A:a,b,c

通过这个类实例化这三种类型的对象

cpp 复制代码
public class SimplePizzaFactory {
    public Pizza CreatePizza(String ordertype){
        Pizza pizza = null;
        if (ordertype.equals("chesse"))
            pizza = new CheesePizza();
        else if (ordertype.equals("greek"))
            pizza = new GreekPizza();
        else if (ordertype.equals("pepper"))
            pizza = new PepperPizza();
        return pizza;
    }
}

简单工厂模式的问题:类的创建依赖工厂类,如果想要拓展程序,就必须对工厂类进行修改,这违背了开闭原则。

工厂方法模式

定义:定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类

举例:

添加一个新的产地,如果用简单工厂模式的话,我们要去修改工厂代码,并且会增加一堆的if else语句,而工厂方法模式克服了简单工厂要修改代码的缺点,它会直接创建两个工厂

cpp 复制代码
//OrderPizza中有个抽象的方法
abstract Pizza createPizza();

//两个工厂类继承OrderPizza并实现抽象方法
public class LDOrderPizza extends OrderPizza {
    Pizza createPizza (String ordertype) {
        Pizza pizza = null;
        if (ordertype.equals("cheese")) {
            pizza = new LDCheesePizza();
        } else if (ordertype.equals("pepper")) {
            pizza = new LDPepperPizza();
        }
        return pizza;
    }
}

public class NYOrderPizza extends OrderPizza {
    Pizza createPizza (String ordertype) {
        Pizza pizza = null;
        if (ordertype.equals("cheese")) {
            pizza = new NYCheesePizza();
        } else if (ordertype.equals("pepper")) {
            pizza = new NYPepperPizza();
        }
        return pizza;
    }
}

模式

其实这个模式的好处就是,如果你想要增加一个功能,只需要做一个实现类就ok了,无需去改动现成的代码,这样做,拓展性较好

工厂方法存在的问题:客户端需要创建类的具体的实例,简单来说就是用户要订纽约工厂的披萨,他必须去纽约工厂,想订伦敦工厂的披萨,必须去伦敦工厂。当伦敦工厂和纽约工厂发生变化的时候,用户也要跟着变化,这增加了用户的操作复杂性,为了解决这个问题,我们可以把工厂类抽象为接口,用户只需要去默认的工厂提出自己的需求,便能够得到自己想要的产品,而不是根据产品去寻找不同的工厂,方便用户操作,这也就是我们接下来要说的抽象工厂模式

抽象工厂模式

定义:定义一个接口用于创建相关或者有依赖关系的对象族,而无需明确指定具体类

cpp 复制代码
//工厂的接口
public interface AbsFactory {
    Pizza CreatePizza(String ordertype);
}

//工厂的实现
public class LDFactory implements AbsFactory {
    @Override
    public Pizza CreatePizza (String ordertype) {
        Pizza pizza = null;
        if ("cheese".equals(ordertype))
            pizza = new LDCheesePizza();
        else if ("pepper".equals(ordertype)) {
            pizza = new LDPepperPizza();
        }
        return pizza;
    }
单例模式}

//PizzaStroe的代码
public class PizzaStore {
    public static void main(String[] args) {
        OrderPizza mOrderPizza;
        mOrderPizza = new OrderPizza("London");
    }
}

解决了工厂方法模式的问题,在抽象工厂种PizzaStore种只需要传入参数就可以实例化对象

工厂模式适用的场合

大量的产品需要创建,并且这些产品具有共同的接口

三种工厂模式的使用选择

简单工厂:用来生产同一等级结构种的任意产品(不支持拓展增加产品)

工厂方法:用来生产同一等级结构种的固定产品(支持拓展增加产品)

抽象工厂:用来生产不同产品族的全部产品(支持拓展增加产品,支持增加产品族)

总结一下三种模式

简单工厂模式就是建立一个实例化对象的类,在该类种对于多个对象实例化,工厂方法模式是定义了一个创建对象的抽象方法,由子类决定要实例化的类。这样做的好处就是再有新的类型的对象需要实例化只需要增加子类就可以了,抽象工厂模式定义了一个接口用于创建对象族,而无需明确指定具体类,抽象工厂也是把对象的实例化交给了子类,即支持拓展,同时提供给客户端接口,避免了用户直接操作子类工厂。

开闭原则

开闭原则是面向对象编程的一种基本设计原理,核心思想是,软件种的对象(类,模块,函数等)应该对扩招开放,对修改封闭。

作用

1.提高可维护性,通过减少对已有代码的修改,降低维护成本,并减少引入错误的可能性

2.增强可扩展性,允许在不修改现有代码的前提下,通过添加新的代码或组件来扩展系统的功能

3.提高重用性,遵循开闭原则设计的代码种的抽象和接口可以被多个模块或组件共享和重用

4.提升可测试性,由于对现有代码的修改较少,可以更容易地编写和运行测试

5.增强系统的稳定性,新功能的添加不会破坏现有的功能,从而保持系统的稳定性

如何实现

1.使用抽象和接口,通过定义抽象类或接口,将代码的通用行为抽象出来,而具体的实现则交给不同的子类或实现类,这样,当需要添加新功能的时候,只需要编写新的实现类,而不需要修改现有的抽象或接口

2.利用多样性,通过多态,可以将不同的对象视为相同的类型,从而在不修改原有的代码的情况下,通过传入不同的对象来实现不同的行为

3.使用设计模式,设计模式入策略模式,工厂模式和观察者模式等,都可以帮助设计可扩展的代码结构,使得新的功能可以通过添加新的策略,工厂或观察者来实现,而不需要修改原有的代码。

4.划分职责边界,在设计类或者模块时,应该明确划分各个部分的职责边界,一个类或模块应该只关注自己的一部分职责,当需求发生变化的时候,只需要扩展与职责相关的部分,而不需要修改其他部分的代码

案例

在一个电商系统种,如果需要添加一种新的支付方式,遵循开闭原则的设计会允许你通过添加一个新的支付类(实现支付接口)来完成,而不需要修改现有的支付处理逻辑,这种设计方式使得系统在面对变化时更加灵活和稳定,同时也提高了代码的可维护性和可扩展性。

单例模式

定义:确保一个类最多只有一个实例,并且提供一个全局访问点

分类:预加载和懒加载

预加载

定义:还没有使用该单例对象,但是,该单例对象就已经被加载到内存了

缺点:会造成内存的浪费

cpp 复制代码
public class PreloadSingleton {
    public static PreloadSingleton instance = new PreloadSingleton();
    //其他类无法实例化单例类的对象
    private PreloadSingleton() {};

    public static PreloadSingleton getInstance() {
        return instance;
    }
}
懒加载

为了避免内存浪费,我们采用懒加载,即用到该单例对象的时候再创建

cpp 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton(){};

    public static Singleton getInstance()
    {
        if(instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}
单例模式和线程安全
  1. 预加载只有一条语句,return instance,这个是线程安全的,但是,我们知道预加载会造成内存的浪费
  2. 懒加载不会浪费内存,但是无法保证线程的安全,首先,if判断以及内存执行代码是非原子性的,其次,new Singleton()无法保证执行的顺序性,如果不满足原子性或者顺序性,线程肯定是不安全的,为什么new Singleton()无法保证顺序性,我们知道创建一个对象分为三步:
cpp 复制代码
memory = allocate();//初始化内存空间

ctorInstance(memory);//初始化对象

instance = memory();//设置instance指向刚分配的内存地址

jvm为了提高程序执行性能,会对没有依赖关系的代码就行重排序,上面2和3行的代码可能会被重新排序,我们用两个线程来说明线程是不安全的,线程A和线程B都创建对象,其中,A2和A3的重排序,将导致线程B在B1处判断instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象(线程不安全)

时间 线程A 线程B
t1 A1:分配对象的内存空间
t2 A3:设置instance指向内存空间
t3 B1:判断instance是否为空
t4 B2:由于instance不为null,线程B将访问instance引用的对象
t5 A2:初始化对象
t6 A4:访问instance引用的对象
保证懒加载的线程安全

首先想到的就是synchronized关键字,synchronized加载getInstace()函数上确实保证了线程安全,但是,如果要经常调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程,为了避免线程上下文切换消耗大量时间,如果对象已经实例化了,我们就没有必要再使用synchronized加锁,直接返回对象。

cpp 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {};
    public status synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

我们把sychronized加在if(instance == null)判断语句里面,保证instance未实例化的时候才能加锁

cpp 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {};
    public static synchronized Singleton getInstance() {
        if (instance == null){
            synchronized (Singleton.class) {
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

前面说过new一个对象的代码是无法保证顺序性的,因此,我们需要使用另一个关键字volatile保证对象实例化过程的顺序性。

cpp 复制代码
public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){};
    public static synchronized Singleton getInstance() {
        if (instance == null){
            synchronized (instance) {
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

到此,我们就保证了懒加载的线程安全

生成器模式

定义:封装一个复杂对象构造过程,并允许按步骤构造

定义解释:我们可以将生成器模式理解为,假设我们有一个对象需要创建,这个对象是由多个组件组和而成的,每个组件的建立都是比较复杂的,但运用组件来建立所需要的对象非常简单,所以我们可以将构建复杂步骤与运用组件构建分离,使用builder模式可以建立。

生成器模式结构中包括四种角色:

(1)产品,具体生产器要构造的复杂对象

(2)抽象生成器,抽象生成器是一个接口,该接口除了为创建一个Product对象的各个组件定义了若干个方法之外,还要定义返回Product对象的方法

(3)具体生产器,实现Builder接口的类,具体生成器将实现Builder接口所定义的方法

(4)指挥者,指挥者是一个类,该类需要含有Builder接口声明的变量,指挥者的职责是负责向用户提供具体生成器,即指挥者将请求具体生成器类来构造用户所需要的Product对象,如果所请求的具体生成器成功地构造出Product对象,指挥者就可以让该具体生成器返回所构造的Product对象

cpp 复制代码
//ComputerBuilder类定义构造步骤
public abstract class ComputerBuilder {
    protected Computer computer;

    public Computer getComputer(){
        return computer;
    }
    
    public void buildComputer(){
        computer = new Computer();
        System.out.println("生成一个电脑");
    }
    public abstract void buildMaster();
    public abstract void buildScreen();
    public abstract void buildKeyboard();
    public abstract void buildMouse();
    public abstract void buildAudio();
}

//HPComputerBuilder定义各个组件
public class HPComputerBuilder extends ComputerBuilder {
    @Override
    public void buildMaster(){
        // TODO Auto-generated method stub
        computer.setMaster("i7,16g,512SSD,1060");
        System.out.println("(i7,16g,512SSD,1060)的惠普主机");
    }
    
    @Override
    public void buildScreen() {
        // TODO Auto-generated method stub
        computer.setScreen("1080p");
        System.out.println("(1080p)的惠普显示屏");
    }
    @Override
    public void buildKeyboard() {
        // TODO Auto-generated method stub
        computer.setKeyboard("cherry 青轴机械键盘");
        System.out.println("(cherry 青轴机械键盘)的键盘");
    }  
    @Override
    public void buildMouse() {
        // TODO Auto-generated method stub
        computer.setMouse("MI 鼠标");
        System.out.println("(MI 鼠标)的鼠标");
    }
    @Override
    public void buildAudio() {
        // TODO Auto-generated method stub
        computer.setAudio("飞利浦 音响");
        System.out.println("(飞利浦 音响)的音响");
    }
}

//Director类对组件进行组装并生成产品
public class Director {
   
    private ComputerBuilder computerBuilder;
    public void setComputerBuilder(ComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }
   
    public Computer getComputer() {
        return computerBuilder.getComputer();
    }
   
    public void constructComputer() {
        computerBuilder.buildComputer();
        computerBuilder.buildMaster();
        computerBuilder.buildScreen();
        computerBuilder.buildKeyboard();
        computerBuilder.buildMouse();
        computerBuilder.buildAudio();
    }
}
生成器的优缺点

优点

  1. 将一个对象分解为各个组件
  2. 将对象组件的构造封装起来
  3. 可以控制整个对象的生成过程

缺点

  1. 对不同类型的对象需要实现不同的具体构造器的类,这可能回答大大增加类的数量
生成器模式与工厂模式的不同

生成器模式构建对象的时候,对象通常构建的过程中需要多个步骤,就像我们例子种的先有主机,再有显示器,再有鼠标等等,生成器模式的作用就是将这些复杂的构建过程封装起来,工厂模式构建对象的时候通常只有一个步骤,调用一个工厂方法就可以生成一个对象。

原型模式

定义:通过复制现有实例来创建新的实例,无需知道相应类的信息。

简单地理解,其实就是当需要创建一个指定的对象时,我们刚好有一个这样的对象,但是又不能直接使用,我会clone一个一样的新对象来使用

深拷贝和浅拷贝

浅拷贝:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原来对象所指向的。

深拷贝:将一个对象复制后,不论是基本数据类型还是引用类型,都是重新创建的,简单的说,就是深复制进行了完全彻底的复制,而浅复制不彻底。clone是深复制,clone出来的对象是不能去影响原型对象的

总结

原型模式的本质就是clone,可以解决构建复杂对象的资源消耗问题,能在某些场景中提升构建对象的效率,还有一个重要的用途就是保护性拷贝,可以通过返回一个拷贝对象的形式,实现只读的限制。

相关推荐
我码玄黄8 小时前
JS 的行为设计模式:策略、观察者与命令模式
javascript·设计模式·命令模式
会敲代码的小张9 小时前
设计模式-观察者模式
java·开发语言·后端·观察者模式·设计模式·代理模式
宗浩多捞9 小时前
C++设计模式(更新中)
开发语言·c++·设计模式
秦哈哈9 小时前
【软件设计】常用设计模式--观察者模式
观察者模式·设计模式
蔚一16 小时前
Java设计模式—面向对象设计原则(四) ----->接口隔离原则(ISP) (完整详解,附有代码+案例)
java·后端·设计模式·intellij-idea·接口隔离原则
严文文-Chris18 小时前
【设计模式-外观】
android·java·设计模式
yyqzjw19 小时前
【设计模式】观察者模式
算法·观察者模式·设计模式
Tech_gis19 小时前
C++ 常用设计模式
设计模式
java_heartLake20 小时前
设计模式之抽象工厂模式
设计模式·抽象工厂模式
codefly-xtl1 天前
单例模式详解
java·单例模式·设计模式