设计模式

设计模式

  • 软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案

  • 这个术语是由埃里希·伽马(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的

拿实际工作经历来说,当一个项目开发完成后,如果客户提出增新功能,怎么办

如果项目开发完后,原来程序员离职,你接收维护该项目怎么办?(维护性,可读性,规范性)

目前开发人员门槛越来越高,一线IT公司(大厂),都会问你在实际项目中使用过什么

设计模式,怎样使用的,解决了什么问题

设计模式哪里使用

基于面向对象

功能模块 [设计模式+算法(数据结构)]

=> 框架 [使用到多种设计模式]

=>架构 [服务器集群]

设计模式的目的

编写软件过程中,开发人员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好的代码重用性、可读性、可扩展性、可靠性、使程序呈现高内聚,低耦合

设计模式七大原则

1.单一职责原则

​ 对类来说,即一个类应该只负责一项职责,如类A负责两个不同职责:职责1,职责2

​ 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2

一个类去做一类事,一个方法去做一件事

降低类的复杂度,一个类只负责一项职责

提高类的可读性,可维护性

降低变更引起的风险

通常情况下,我们应当遵循单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则

2.接口隔离原则

(Interface Segregation Principle)

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接 口上

类A通过接口Interface依赖类B,类C通过接口Interface1依赖类D,如果接口Interface对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法

按隔离原则应当这样处理:将接口Interface拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系,也就是采用接口隔离原则

在接口中只去定义需要被实现类所需要的方法

​ 以后在设计程序时,一个类实现的接口应该是最小接口,这个接口中的方法应该是这个类都需要用到的

3.依赖倒转原则

(Dependence Inversion Principle)

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象

  • 抽象不应该依赖细节,细节应该依赖抽象

  • 依赖倒转(倒置)的中心思想面向接口编程

  • 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架构比以细节为基础的架构要稳定的多,在java中,抽象指的是接口或抽象类细节就是具体的实现类

  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

多态:面向多态、面向抽象、面向接口

低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好

变量声明类型尽量是抽象类接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化

4.里氏替换原则

  • 里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的以为姓里的女士提出的

  • 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型,换句话说,所有引用基类的地方必须能透明地使用其子类的对象

  • 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法

  • 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合(依赖)来解决问题

继承包含这样层含义:

​ 父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏

​ 继承在给程序设计带来便利的同时,也带来了弊端,比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障

在子类和父类中如果存在一个同名方法,默认会构成重写

所以说默认类B就重写了类A的方法,数据会错乱,性质就变了

继承会使得,类与类之间产生耦合性,同时也会给程序带来侵入性

所以说以后如果我们在一个类中需要使用另外一个类的资源,我们通常情况下会采用组合、聚合的方式来引入这个类的实例,而尽量不要采用继承的方式(使用组合、聚合等方式替换继承

组合、聚合:

class B{
   
    //聚合:set、构造
    private A a;
    
    //组合
    private A a = new A();
    
    public B(A a){
        this.a = a;
    }
    
    public void setA(A a){
        this.a = a;
    }

}

5.开闭原则(OCP)

  • 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则

  • 一个软件实体如类,模块和函数应该对扩展开放(提供方)对修改关闭(使用方),用抽象构建框架,用实现扩展细节

  • 当软件需要变化时,尽量通过扩展软件实体行为来实现变化,而不是通过修改已有的代码来实现变化

  • 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则

6.迪米特法则*

  • 一个对象应该对其他对象保持最少的了解

  • 类与类关系越密切,耦合度越大

  • 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息

  • 迪米特法则还有个更简单的定义:只与直接的朋友通信

我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友,也就是说陌生的类最好不要局部变量的形式出现在类的内部

迪米特法则的核心降低类之间的耦合

但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

一个类对于他所依赖的实例知道的越少越好

耦合只能降低,不可能完全消除

7.合成复用原则

原则是尽量使用合成(组合)/聚合的方式,而不是使用继承

设计原则核心思想

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 为了交互对象之间的松耦合(解耦)设计而努力

设计模式介绍

设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern) 代表了最佳的实践,这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的

设计模式的本质是提高软件的维护性,通用性和扩展性,并降低软件的复杂度

设计模式类型

1.创建型模式:

单例模式

抽象工厂模式

原型模式

建造者模式

工厂模式

2.结构型模式:

适配器模式

桥接模式

装饰模式

组合模式

外观模式

享元模式

代理模式

3.行为型模式:

模板方法模式

命令模式

访问者模式

迭代器模式

观察者模式

中介者模式

备忘录模式

解释器模式(Interpreter模式)

状态模式

策略模式

职责链模式(责任链模式)

★单例设计模式介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

就是在软件系统中,对于某个类,在任何位置,只能存在一份对象实例

解耦:

​ 类A 在项目中的很多模块都需要用到,这个时候如果正常的去获取该类的实例,需要用到new A(),每使用一个new关键字都会去开辟一份空间,占用系统的资源,指向不同的内存地址

​ 我们能不能想一种解决方案,在模块需要调用A的资源的时候,不需要反复的去创建对象

单例模式:这个软件系统中你获得的A的实例指向的是一份内存地址

1.饿汉式:

public class SingletonDemo{
    
    //对本类进行构造方法私有化,禁止外界new本类实例
    private SingletonDemo(){}
    
    //在本类中创建本类的实例(静态的,具有唯一内存地址)
    private static final SingletonDemo singletonDemo = new SingletonDemo();
    
    //为外界提供访问本类实例的静态方法
    public static SingletonDemo getInstance(){
        
        return singletonDemo;
        
    }
    
}

静态代码块的方式

public class SingletonDemo02 {

    //1.构造方法私有化,不希望在外界可以实例化对象
    private SingletonDemo02(){}

    //2.在本类实例化本类的实例(静态的,不会在每次调用重新加载实例)
    private static SingletonDemo02 singletonDemo02;

    static {

        singletonDemo02 = new SingletonDemo02();

    }

    //3.提供唯一一个获取这个类实例的方法(静态的)
    public static SingletonDemo02 getInstance(){

        return singletonDemo02;

    }

}

2.懒汉式:

懒汉式会有线程安全的问题,如果t1线程和t2线程同时访问这个类,并且同时判断实例为空,两个线程就会同时去创建实例,就不再是单例

如果直接在方法上加锁确实可以解决线程安全问题,但是效率却不是最好的

在保证线程安全的同时也要考虑效率问题

在实例还没有创建的时候,多线程确实需要同步执行,但是实例已经创建出来的时候,多线程可以并发的去获取实例

volatile关键字修饰引用,实现多线程之间的可见性,在t1线程创建实例时及时的去通知t2线程

最终解决方案:通过双重校验来实现当实例存在的情况下,多线程可以并发的去获取,实例不存在,多线程要同步的去创建

不会直接去创建实例,先去判断实例是否为空,如果实例为空再去创建

public class SingletonDemo03{
    
    //构造方法的私有化
    private SingletonDemo03(){}
    
    //在本类中聚合本类的引用
    private volatile static SingletonDemo03 singletonDemo03;
    
    //提供获取本类中实例的方法
    public static SingletonDemo03 getInstance(){
        
        //检查是否创建实例,如果创建了则直接获取实例,否则创建实例对象
        if(singletonDemo03 == null){
            
            //提供类锁
            synchronized(SingletonDemo03.class){
                
                //为上次被阻塞的线程添加一次判断是否在之前有线程创建过实例
                if(singletonDemo03 == null){
                    
                    singletonDemo03 = new SingletonDemo03();
                    
                }
                
            }
            
        }
        
        //返回本类的实例
        return singletonDemo03;
        
    }
    
    
}

★代理模式(Proxy)介绍

代理模式:

  • 为一个对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能

  • 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

  • 代理模式有不同的形式,主要有三种 静态代理动态代理(JDK代理、接口代理)和 Cglib代理(可以在内存多态的创建对象,而不需要实现接口,他是属于动态代理的范畴)

代理模式是干什么的?

代理模式就是为了完成对目标类的增强,完成对目标类的织入

代理模式增强的是切入点(目标类方法)

静态代理:

​ 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类

优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展

缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护

目标类(被代理类),代理类,逻辑发起点 :

​ 类似于中介(代理类)和房东(目标类)的关系

​ 逻辑发起点:客户要找房

​ 代理类:负责帮客户选房

​ 目标类:签合同


逻辑发起点

package com.os467.proxy;

/**
 * 客户(逻辑发起点)
 * 有找房需求:客户有中介的联系方式
 * 客户选好了房子,中介可以联系到房东
 */
public class ProxyApp {

    public static void main(String[] args) {

        //创建中介对象
        ProxyDao proxyDao = new ProxyDao(new ProxyDaoImpl());

        //调用找房子的方法
        proxyDao.findHouse();


    }

}

代理类与目标类需要实现的接口

package com.os467.proxy;

/**
 * 代理类与目标类需要实现的接口
 */
public interface ProxyInterface {

    /**
     * 找房的方法
     */
    void findHouse();


}

代理类

package com.os467.proxy;

/**
 * 代理类(中介)
 */
public class ProxyDao implements ProxyInterface{

    //在中介这个类中要得有房东的引用,但是房东是有很多的是模糊的,
    //但是房东实现了这个接口,所以我们在类中聚合接口
    private ProxyInterface proxyInterface;

    //提供一个有参构造
    public ProxyDao(ProxyInterface proxyInterface) {
        this.proxyInterface = proxyInterface;
    }

    /**
     * 在不修改目标方法逻辑的前提下,完成对目标方法的增强
     * 代理类中对目标类方法逻辑的增强
     * 对目标类的织入
     */
    @Override
    public void findHouse() {

        //前置通知(增强的业务逻辑)
        System.out.println("中介带着客户开始找房");

        //找到房东,然后签合同,目标方法所有的逻辑
        //代理模式可以通过代理类去增强目标类,增强的一般是切入点(方法)
        proxyInterface.findHouse();

        //后置通知(增强的业务逻辑)
        System.out.println("中介会告诉客户小区周围的环境,买菜在哪,逛街在哪");

    }
}

目标类

package com.os467.proxy;

/**
 * 目标类(房东)
 */
public class ProxyDaoImpl implements ProxyInterface{

    @Override
    public void findHouse() {

        System.out.println("客户选好了房子,中介直接带着客户来签合同");
        System.out.println("合同签署完毕");

    }
}

动态代理:

JDK动态代理

代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象

实现步骤:需要调用JDK提供的相关方法

创建的对象:目标对象、代理对象(不需要我们创建,通过反射去创建)、创建代理对象的对象

目标类和代理类实现了相同的接口,那么可以使用两者接口的方法字节码对象来调用两者的对应方法


代理类创建类

package com.os467.jdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 这个类不是代理类,是创建代理类的类
 */
public class CreateJdkProxy {

    //目标对象
    private Object object;

    public CreateJdkProxy(Object object){

        this.object = object;

    }

    /**
     * 通过反射机制来创建代理对象
     * @return
     */
    public Object getProxy(){

        /**
         * ClassLoader loader :目标类的类加载器对象
         * Class[] interfaces :目标类所实现的接口字节码对象
         * InvocationHandler h :是一个接口对象,这个接口提供了invoke方法
         * 方法里面完成对目标对象的增强
         */

        //目标类的类加载器对象
        ClassLoader classLoader = object.getClass().getClassLoader();

        //目标类所实现的接口字节码对象
        Class[] interfaces = object.getClass().getInterfaces();

        //接口实例,匿名内部类
        InvocationHandler invocationHandler = new InvocationHandler() {

            /**
             * 这个方法的作用就是完成对目标对象的增强,对目标方法增强的整个过程都在这个方法内
             * @param proxy :代理对象
             * @param method :代理对象的与目标对象实现的共同接口方法字节码对象
             * @param args :代理方法字节码对象对应的参数
             * @return 返回目标对象方法返回值
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("前置通知----");

                //调用目标对象的方法,接口方法字节码对象调用目标对象的方法
                Object invoke = method.invoke(object, args);

                System.out.println("后置通知----");

                return invoke;
            }
        };

        //这个方法返回的就是一个代理对象
        Object obj = Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);

        return obj;
    }

}

目标类

package com.os467.jdkProxy;

/**
 * 目标类
 */
public class ProxyDao implements ProxyInterface {

    /**
     * 目标类重写的方法
     * @param food
     * @return
     */
    @Override
    public String eat(String name,String food) {

        System.out.println("目标方法中的逻辑");

        return name+"正在吃"+food;

    }

}

共同接口

package com.os467.jdkProxy;

/**
 * 代理类和目标类需要实现的共同接口
 */
public interface ProxyInterface {

    /**
     * 吃的方法
     * @param food
     * @return
     */
    String eat(String name,String food);

}

逻辑发起点

package com.os467.jdkProxy;

public class Test {

    public static void main(String[] args) {

        //创建目标对象
        ProxyInterface proxyInterface = new ProxyDao();

        //用于获取代理对象创建类的实例
        CreateJdkProxy createJdkProxy = new CreateJdkProxy(proxyInterface);

        //调用方法,获取到代理对象
        ProxyInterface proxy = (ProxyInterface)createJdkProxy.getProxy();

        //调用方法
        System.out.println(proxy.eat("张三","饭"));

    }

}

InvocationHandler接口 (调用处理程序对象)

用于返回目标对象方法调用后的返回值

需要在创建代理对象的时候作为参数使用,通过匿名内部类方式创建实例

需要重写invoke方法,此方法回返值即目标对象方法的返回值

区分使用method字节码对象与使用invocationHandler实例调用invoke方法所需要的参数

假设在代理类中聚合了invocationHandler实例:

提供目标类实例参数列表

Object invoke1 = method.invoke(ProxyDao.class.newInstance(), new Object[]{});

提供代理类实例方法字节码对象参数列表

Object invoke = invocationHandler.invoke(this,method,new Object[]{});

模拟动态代理在底层的调用

/**
 * 模拟jdk动态代理在底层调用过程
 */
public class Proxy implements ProxyInterface {

    //聚合调用处理程序对象
    private InvocationHandler invocationHandler;

    public Proxy(InvocationHandler invocationHandler) {

        this.invocationHandler = invocationHandler;

    }

    @Override
    public void study() {

        try {

            //先定义目标类方法字节码对象,我们创建接口的方法字节码对象
            Method method = ProxyInterface.class.getDeclaredMethod("study");

            //调用目标类的方法
            Object invoke = invocationHandler.invoke(this, method, new Object[]{});

            System.out.println(invoke);


        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } 

    }
}
Cglib动态代理
  • 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可以使用目标对象子类来实现代理 (这就是Cglib代理)

  • Cglib代理也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理

  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截

  • 在AOP编程过程中如何选择代理模式:

    ​ 1.目标对象需要实现接口,用JDK代理

    ​ 2.目标对象不需要实现接口,用Cglib代理

  • Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

目标类

package com.os467.Cglib;

/**
 * 目标类
 */
public class CglibDao {

    public void getMethod(){

        System.out.println("需要增强方法的主题内容");

    }

}

创建代理类

package com.os467.Cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CreateCglib implements MethodInterceptor {

    //聚合目标类
    private Object object;

    public CreateCglib(Object object) {
        this.object = object;
    }

    /**
     * 获取代理对象
     * @return
     */
    public Object getCglibProxy(){

        //创建cglib对象
        Enhancer enhancer = new Enhancer();

        //设置代理对象的父类
        enhancer.setSuperclass(object.getClass());

        //设置回调函数
        enhancer.setCallback(this);

        //创建代理对象并且返回
        return enhancer.create();
    }

    /**
     * 增强目标类
     * @param o 代理类对象 基本用不到
     * @param method 代理类方法字节码对象
     * @param objects 传递的参数
     * @param methodProxy 基本用不到
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println("前置通知---");

        //调用目标方法
        Object invoke = method.invoke(object, objects);

        System.out.println("后置通知---");

     return invoke;
    }
}

测试类

package com.os467.Cglib;

public class Test {

    public static void main(String[] args) {

        //创建目标对象
        CglibDao cglibDao = new CglibDao();

        //创建代理对象
        CreateCglib createCglib = new CreateCglib(cglibDao);

        CglibDao cglibProxy = (CglibDao)createCglib.getCglibProxy();

        cglibProxy.getMethod();

    }

}

总结JDK动态代理和Cglib动态代理

相同点:都是在内存中去创建对象,功能也是一样的,都是完成对目标方法的增强

不同点

jdk代理:要求目标类必须实现一个接口,内存中创建出来的代理类也会实现相同的接口

Cglib代理:目标类不用实现接口,可以只是一个普通的类,在内存中创建出来的代理类是目标类的子类

总结:动态代理实际上都是用于增强目标方法,如果需要被增强的类实现了接口,我们需要采用JDK代理,如果没有实现接口,我们采用Cglib代理

动态代理在以后开发中的作用

​ SpringAop(面向切面编程:底层就是通过代理模式实现的):

​ 模块1:

​ 模块2:

​ 模块3:

​ 假设某项目有很多模块,每个模块封装了不同的业务逻辑,我们以后使用框架进行开发,目的可以提高开发效率,可以让开发人员只去关注核心业务,不去关注一些非核心业务

提一个需求:希望每个模块都能完成日志的收集,当任意一个模块的代码发生了异常,这个时候希望整理成日志文档,方便开发人员排错

注意:日志收集的代码不属于核心业务,日志收集的代码是每个模块都需要的

​ SpringAop可以完成对核心模块的增强:

​ 日志收集的代码(需要对每个模块增强的部分)

增强的步骤

​ 1、获取每个模块的目标方法

​ 2、配置SpringAop的切面 ==> 切面(指的就是增强的过程)

切面

​ spring会去校验核心模块中的核心类是否实现了接口,如果实现了,在底层使用jdk代理来完成,如果没有实现,则使用Cglib代理来完成

核心类实现了接口需要在 invoke 方法当中完成对目标方法的增强

invoke{

日志收集的代码1

通过反射机制调用目标方法

日志收集的代码2

}

所以说,通过这种方式可以让非核心代码脱离核心模块,而且方便维护,易于管理

★工厂模式

普通工厂模式

是指由一个工厂对象决定创建出哪一种产品类的实例,属于创建型模式

1、适用于工厂类负责创建的对象较少的场景

2、且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心

简单工厂模式缺点

1、工厂类的职责相对过重,增加新的产品时需要修改工厂类的逻辑判断违背开闭原则(ocp)

2、不易于扩展过于复杂的产品结构

由工厂来为我们生产对象

生产手机的工厂Phone_Factory:华为、苹果、小米

普通工厂模式:根据标识去生产实例

/**
 * 生产手机:我们通过普通工厂模式来写这个类
 */
public class PhoneFactory {

    /**
     * 获取手机实例的方法
     * @return
     */
    public Object getPhone(String flag){

        if (flag.equals("mi")){

            //返回小米的实例
            return new Mi();

        }else if (flag.equals("huawei")){

            //返回的是苹果的实例
            return new HuaWei();

        }else if (flag.equals("iphone")){

            //返回的是苹果的实例
            return new Iphone();

        }

        return null;
    }

}
/**
 * 需求:通过工厂获取手机实例
 */
public class FactoryTest01 {

    public static void main(String[] args) {

        //创建工厂对象
        PhoneFactory phoneFactory = new PhoneFactory();

        //获取华为实例
        HuaWei huawei = (HuaWei)phoneFactory.getPhone("huawei");

        huawei.make();

        //获取小米的实例
        Mi mi = (Mi)phoneFactory.getPhone("mi");

        mi.make();

    }

}

工厂模式为了避免程序过多的使用new关键字来创建对象,这样会产生耦合


工厂方法模式

  • 是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行

  • 用户只需要关心所需产品对应的工厂,无需关心创建的细节,而且加入新的产品符合开闭原则

/**
 * 生产手机:我们通过工厂方法模式来写这个类
 */
public class PhoneFactory2 {

    /**
     * 获取小米手机实例的方法
     * @return
     */
    public Phone getMi(){

        return new Mi();
    }

    /**
     * 获取华为手机实例的方法
     * @return
     */
    public Phone getHuaWei(){

        return new HuaWei();
    }

    /**
     * 获取苹果手机实例的方法
     * @return
     */
    public Phone getIphone(){

        return new Iphone();
    }

}

产品需要实现的接口

public interface Phone {

    void make();

}

测试类

/**
 * 需求:通过工厂获取手机实例
 */
public class FactoryTest02 {

    public static void main(String[] args) {

        //创建工厂对象
        PhoneFactory2 phoneFactory2 = new PhoneFactory2();

        //获取华为实例
        Phone huaWei = phoneFactory2.getHuaWei();

        huaWei.make();

        //获取华为实例
        Phone iphone = phoneFactory2.getIphone();

        iphone.make();

    }
}

静态工厂

将工厂类中的方法加上static关键字,这样就不需要创建工厂对象


抽象工厂模式

是指提供一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类

抽象工厂模式使用场景

1、客户端(应用层)不依赖于产品类实例如何被创建,实现等细节

2、强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码

3、提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体的实现

抽象工厂模式优点

1、具体产品在应用层代码隔离,无需关系创建细节

2、将一个系列的产品族统一到一起创建

抽象工厂模式要求工厂和产品都需要基于接口开发

工厂类需要实现的工厂接口

/**
 * 所有工厂都需要去实现的接口
 *
 *  工厂是用来生产产品的
 */
public interface Factory {

    /**
     * 制作手机的方法
     */
    Phone makePhone(String className);

}

产品需要实现的接口

public interface Phone {

    void make();

}

创建手机工厂类实现工厂接口

/**
 * 生产手机:通过抽象工厂模式来写这个类
 */
public class PhoneFactory3 implements Factory{

    /**
     * 用于生产手机的方法
     * @param className
     * @return
     */
    @Override
    public Phone makePhone(String className) {

        if (className != null){

            try {

                //获取字节码对象
                Class<?> aClass = Class.forName(className);

                //通过反射机制来创建实例
                Object o = aClass.newInstance();

                if (o instanceof Phone){

                    return (Phone)o;

                }

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }


        }

        return null;
    }
}

测试类

/**
 * 需求:通过抽象工厂获取手机实例
 */
public class FactoryTest03 {

    public static void main(String[] args) {

        Factory factory = new PhoneFactory3();

        Phone phone = factory.makePhone("com.os467.phone.Mi");

        phone.make();

    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:设计模式

字数:7.5k

本文作者:Os467

发布时间:2022-07-18, 19:21:50

最后更新:2022-09-05, 00:06:40

原始链接:https://os467.github.io/2022/07/18/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏