设计模式-工厂模式

关注公众号 JavaStorm 获取更多精彩

工厂模式定义

工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

看下 GOF为工厂模式的定义:

“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”(在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。)

工厂模式分类

  • 简单工厂模式 (Simple Factory),又称静态工厂方法模式 (Static Factory Method Pattern)。
  • 工厂方法模式(Factory Method)。
  • 抽象工厂模式(Abstract Factory)。

使用场景

  1. 生产一个发送消息的 产品对象,比如通过邮件、短信、微信公众号等产品发送消息给用户。
  2. Spring 中 FactoryBean 的 getObject();spring 中 各种各样的 bean。就可以通过工厂模式创建并且实现了依赖解耦。

工厂模式的优点

  • 解耦:把对象的创建和使用分开。
  • 降低代码复杂度:如果某个对象的创建比较复杂,或者其过程比较多的步骤。多个地方都会使用就会产生很多重复代码
  • 降低维护成本:创建过程有工厂统一管理,当业务发生变化,不需要去找代码中创建对象 A 的地方组个修改,只要在工厂里面修改即可。开闭原则。

简单工厂模式

其实这个并不算设计模式,适合创建简单对象,创建的对象较少。客户端不关心对象的创建过程。

简单工厂模式角色

  • 工厂角色(Factory):简单工厂模式的核心,负责创建所有实例的内部逻辑,提供外部使用创建所需要的产品。
  • 抽象产品角色(Product):简单工厂所创建的类型。
  • 具体产品(Concrete Product)角色:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

简单工厂代码实现

创建发送器接口,也就是产品角色

public interface Sender {
	
	/**
	 * 发送信息
	 * @param to 收件人
	 * @param msg 消息
	 * @return
	 */
	boolean send(String to, String msg);
	
}

创建具体产品,分别是邮件发送器、短信发送器。

public class MailSender implements Sender {

	@Override
	public boolean send(String to, String msg) {
		System.out.println("MailSender:收件人:" + to + ",消息为:" + msg);
		return true;
	}

}


public class SmsSender implements SenderService {

	@Override
	public boolean send(String to, String msg) {
		System.out.println("SmsSender:收件人:" + to + ",消息为:" + msg);
		return true;
	}

}

创建简单工厂

/**
 * 工厂类,创建实例对象。缺点当字符串输错则得不到对象
 * @author unique
 *
 */
public class SendFactory {
	
	public Sender getObject(String type) {
		Sender sender = null;
		switch (type) {
		case "mail":
			senderService = new MailSender();
			break;
		case "sms":
			senderService = new SmsSender();
			break;
		default:
			System.out.println("请输入正确类型");
			break;
		}
		return sender;
	}

}

单元测试

public class SimpleFactoryTest {
	
	public static void main(String[] args) {
		SendFactory factory = new SendFactory();
		Sender sender = factory.getObject("mail");
		sender.send("大兄弟", "你是最棒的!");
	}

}

缺点

当我们新增产品类的时候,就需要修改工厂类中的 getObject() 方法,不符合 开放-封闭原则。

工厂方法模式

工厂模式中使用最多的一种。

与简单公差个模式最大的区别就是我们不再提供一个统一的工厂来创建所有的产品,二十针对不同的产品提供不同的工厂。也就是每个产品都有一个与之对应的工厂。

适用场景

  • 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
  • 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏原则。
  • 创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

工厂方法模式角色

  • 抽象工厂(Abstract Factory)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
  • 具体工厂(Concrete Factory)角色 :这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
  • 抽象产品(AbstractProduct)角色 :工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
  • 具体产品(Concrete Product)角色 :这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应

代码示例

基于上面的简单工厂,我们改造下。新建一个抽象工厂角色

public interface SenderFactory {
	
	/**
	 * 生成对象
	 * @return
	 */
	public Sender getObject();

}

增加短信、邮件工厂类,实现抽象工厂接口。

public class SendMailFactory implements SenderFactory {

	@Override
	public Sender getObject() {
		return new MailSender();
	}

}


public class SendSmsFactory implements SenderFactory {

	@Override
	public Sender getObject() {
		return new SmsSenderServiceImpl();
	}

}

测试代码

public class Test {

	public static void main(String[] args) {
		SenderFactory senderFactory = new SendMailFactory();
		Sender sender = senderFactory.getObject();
		sender.send("大兄弟", "你是最棒的!");
	}

}

抽象工厂模式

在工厂方法模式中,其实我们有一个潜在意识的意识。那就是我们生产的都是同一类产品。抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一种产品,而是可以创建一组产品。这个产品会依赖多个合成一个。比如我们的电脑厂商 有因特尔和AMD 生产的主板与CPU。这个产品族有CPU跟主板。

抽象工厂模式和工厂方法模式一样,都符合开放-封闭原则。但是不同的是,工厂方法模式在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。也就是说,工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。

适用场景

  • 和工厂方法一样客户端不需要知道它所创建的对象的类。
  • 需要一组对象共同完成某种功能时,并且可能存在多组对象完成不同功能的情况。(同属于同一个产品族的产品)
  • 系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)

抽象工厂方法模式的角色与工厂方法模式一致

我们的电脑有主板、CPU、内存…组成。这些都是产品,现在提供一个工厂,直接同时生产出 CPU与对应的主板,避免单独去创建。也就是所谓的一套,一个系列。

比如 有因特尔工厂出品的 CPU 与 主板,AMD 工厂出品的 CPU 与主板。

代码示例

定义CPU 与主板两个产品

public interface CPU {
    void calculate();
}

public interface Mainboard {
    void installCPU();
}

以及产品的具体实现,比如 AMD 公司出品的、因特尔公司出品的 CPU 与主板

public class AmdCpu implements CPU {
    /**
     * CPU的针脚数
     */
    private int pins = 0;

    public AmdCpu(int pins) {
        this.pins = pins;
    }

    @Override
    public void calculate() {
        System.out.println("AMD CPU的针脚数:" + pins);
    }
}


public class IntelCPU implements CPU {
    /**
     * CPU的针脚数
     */
    private int pins = 0;

    public IntelCPU(int pins) {
        this.pins = pins;
    }

    @Override
    public void calculate() {
        System.out.println("Intel CPU的针脚数:" + pins);
    }

}

public class AmdMainboard implements Mainboard {
    /**
     * CPU插槽的孔数
     */
    private int cpuHoles = 0;

    /**
     * 构造方法,传入CPU插槽的孔数
     *
     * @param cpuHoles
     */
    public AmdMainboard(int cpuHoles) {
        this.cpuHoles = cpuHoles;
    }

    @Override
    public void installCPU() {
        // TODO Auto-generated method stub
        System.out.println("AMD主板的CPU插槽孔数是:" + cpuHoles);
    }
}

public class IntelMainboard implements Mainboard {
    /**
     * CPU插槽的孔数
     */
    private int cpuHoles = 0;

    /**
     * 构造方法,传入CPU插槽的孔数
     *
     * @param cpuHoles
     */
    public IntelMainboard(int cpuHoles) {
        this.cpuHoles = cpuHoles;
    }

    @Override
    public void installCPU() {
        System.out.println("Intel主板的CPU插槽孔数是:" + cpuHoles);
    }

}

接着我们先创建一个抽象工厂角色,能生产 CPU 与主板系列产品的工厂方法定义。

public interface MainboardCPUFactory {
    /**
     * 创建CPU对象
     *
     * @return CPU对象
     */
    public CPU createCpu();

    /**
     * 创建主板对象
     *
     * @return 主板对象
     */
    public Mainboard createMainboard();
}

因特尔工厂定义

public class IntelFactory implements MainboardCPUFactory {

    @Override
    public CPU createCpu() {
        return new IntelCPU(755);
    }

    @Override
    public Mainboard createMainboard() {
        return new IntelMainboard(755);
    }

}

AMD 工厂

public class AmdFactory implements MainboardCPUFactory {

    @Override
    public CPU createCpu() {
        return new AmdCpu(938);
    }

    @Override
    public Mainboard createMainboard() {
        return new AmdMainboard(938);
    }

}

最后我们来测试

public class AbstactFactoryTest {
    public static void main(String[] args) {
        //使用因特尔工厂生产
        MainboardCPUFactory intelFactory = new IntelFactory();
        CPU cpu = intelFactory.createCpu();
        Mainboard mainboard = intelFactory.createMainboard();

        mainboard.installCPU();
        cpu.calculate();
    }
}

打印

Intel主板的CPU插槽孔数是:755
Intel CPU的针脚数:755

关注公众号 JavaStorm 给你更多精彩。

热门文章

暂无图片
编程学习 ·

mogodb日常工作记录

查询相关 db.getCollection(Examda_News_VisitLog).find({"newsId":"20061109480192959"})db.getCollection(Examda_News_VisitLog).find({"from":"xcx","share":{"$gt":0}}).limit(10); # status: "A"…
暂无图片
编程学习 ·

Less 基础

1. 维护CSS的弊端 CSS是一门非程序式语言,没有变量、函数、SCOPE(作用域)等概念。CSS需要书写大量看似没有逻辑的代码,CSS冗余度是比较高的。 不方便维护及扩展,不利于复用。 CSS没有很好的计算能力 非前端开发工程师来讲,往往会因为缺少CSS编写经验而很难写出组织良好且易…
暂无图片
编程学习 ·

Vue动态缓存页面

原理通过keep-alive标签的include属性及vuex完成 需求说明:A页面到B页面需要缓存,A页面到C页面不需要缓存 所要缓存页面的顶级出口 <keep-alive :include="kpAlive"><router-view/> </keep-alive><script> export default {computed: {/**…
暂无图片
编程学习 ·

APP免费渠道,运营者不可不知的基础常识

​随着移动终端的迅速普及,各类APP如雨后春笋般涌现出来,但是真正的运营成功的产品却寥寥无几。从瓜分渠道资源到抢占用户的过程中,很多同行都明显的感觉到,渠道平台所带来的量日益减少,但是刊例价格却一再攀升。就像圈内的朋友和我在聊天的提到的:“现在渠道的大腿真心越…
暂无图片
编程学习 ·

3、【STM32F0系列学习】之—中断和事件

【STM32F0系列学习】之—中断和事件1、什么是“中断”2、中断优先级3、中断嵌套4、嵌套向量中断控制器 (NVIC)5、中断与事件的区别和主要特性6、外部中断(EXTI)配置6.1【标准库】的配置方式6.2【HAL库】的配置方式 1、什么是“中断”CPU执行程序时,由于发生了某种随机的事件…
暂无图片
编程学习 ·

Docker学习(一)

一、docker安装环境Ubuntu16.04 x64二、docker安装安装过程需要获取外网资源包,因此首先需要配置本地服务器DNS追加这两个DNS nameserver 8.8.8.8 nameserver 8.8.4.4Ubuntu配置DNS参考: https://blog.csdn.net/deep_kang/article/details/79599796 https://blog.csdn.net/wa…
暂无图片
编程学习 ·

git命令大全

Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 Git官方网站:https://git-scm.com/ 原理图Workspace:工作区 Index / Stage:暂存区 Repository:仓库…
暂无图片
编程学习 ·

CDH Hue连接Hbase报错

问题描述 安装好CDH集群后,在Hue中查看HBase信息时,Hue界面报错如下: Api 错误:TSocket read 0 bytesHue日志报错: exceptions_renderable ERROR Potential trace: [(/opt/cloudera/parcels/CDH-6.3.2-1.cdh6.3.2.p0.1605554/lib/hue/apps/hbase/src/hbase/api.py, 46,…
暂无图片
编程学习 ·

jvm-回收对象(二)

总所周知,Java将程序员从内存管理中解放出来,使得我们在编写代码的时候不用手动的分配和释放内存,内存管理的任务由JVM承担起来。本文就将讲解JVM在回收对象之前,如何判断一个对象是否应该被回收。 在此之前,我们先来复习一个和Java对象回收有关的知识,那便是finalize方法…
暂无图片
编程学习 ·

C#中String字符串去空格的问题

1.Trim() 最常见的就是trim,trim是清除字符串前,后的空格. " A BC “被TRIM之后是"A BC” 2.LTrim(),RTrim() 分别是清除字符串前面的空格,和清除字符串后面的空格. L = Left左边 R = Right右边 3.replace() s=s.replace(" “,”") 第三种方…
暂无图片
编程学习 ·

CentOS Liniux 使用john进行弱口令检测

什么是弱口令? 弱口令(weak password) 没有严格和准确的定义,通常认为容易被别人(他们有可能对你很了解)猜测到或被破解工具破解的口令均为弱口令。弱口令指的是仅包含简单数字和字母的口令,例如“123”、“abc”等,因为这样的口令很容易被别人破解,从而使用户的计算机面…
暂无图片
编程学习 ·

[指南]-DeepFaceLab 2.0说明和教程(推荐)

DeepFaceLab 2.0指南/教程此教程机翻自:https://mrdeepfakes.com/forums/thread-guide-deepfacelab-2-0-explained-and-tutorials-recommended什么是DeepFaceLab 2.0?DeepFaceLab 2.0是利用机器学习来交换视频中人脸的工具/应用程序。1.0和2.0有什么区别?DFL 2.0有什么新功能…
暂无图片
编程学习 ·

开启阿里云对象存储OSS防误删新功能,保护您珍贵的数据。

一、背景阿里云对象存储 OSS 是保存海量数据的平台,支持丰富的应用。在使用过程中难免会遇到误操作、程序 Bug、覆盖写等导致数据被删除的场景,对于数据的丢失会非常着急,后果也非常严重,甚至某些情况下还会影响你的职业生涯。OSS 每年都会遇到多起客户误删除数据的事件,为…
暂无图片
编程学习 ·

Redis持久化

Redis的持久化1. RDB持久化2. AOF持久化 ​ Redis是内存数据库,里面存储的是自己的数据库状态,因此为了保证在意外情况下数据库状态的一致性,Redis提供了持久化功能。 1. RDB持久化 ​ 该功能就是将某个时间点上的数据库状态保存到一个RDB文件中,RDB文件是一个经过压缩的二…
暂无图片
编程学习 ·

为什么使用Google搜索的时候会连接不到服务器?

为什么使用Google搜索的时候会连接不到服务器?DNS缓存投毒说明方法PS DNS缓存投毒 说明 在未使用付业成设计的DNSCrypt(一种网络协议,用来抵御针对DNS的DNS缓存投毒攻击)之前,使用代理访问google一直打不开,原因就在于一般系统默认会从ISP提供的域名查询服务器查询海外服…
暂无图片
编程学习 ·

Git上传项目到GitHub

windows上传文件到github的方法 文章目录windows上传文件到github的方法一、创建github的账号:二、安装git:三、上传简单来说就是以下几个命令:Git常见错误与操作:error: src ref spec master does not match any以及failed to push some refs to 。。。解决办法,再来一遍…
暂无图片
编程学习 ·

2007

All you need to do is upload the files on to your web space.