设计模式(7)——对象创建模式(1)——工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)

本文详细介绍了对象创建模式中的三种工厂模式:简单工厂模式、工厂方法模式和抽象工厂模式。通过披萨订购、文件分割器和数据库访问等案例,阐述了各模式的基本介绍、动机、类图以及实际应用,旨在降低对象创建过程中的耦合度,提高代码的可扩展性和可维护性。
摘要由CSDN通过智能技术生成

目录

0.对象创建模式

1.简单工厂模式

1.1 基本介绍

1.2 案例:披萨订购

1.3 JDK源码中Calendar类的创建中对简单工厂的应用

2.工厂方法模式

2.1 基本介绍

2.2 动机

2.3 类图

2.4 案例:文件分割器

2.4 总结:

3.抽象工厂模式

3.1 基本介绍

3.2 动机

3.3 类图

3.4 案例:数据库访问

3.5 总结


0.对象创建模式

  • 通过“对象创建”模式绕开直接new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定,它是接口抽象之后的第一步工作
  • 典型模式:
    • 工厂模式
      • 简单工厂模式(Simple Factory)
      • 工厂方法模式(Factory Method)
      • 抽象工厂模式(Abstract Factory)
    • 原型模式(Prototype)
    • 建造者模式(Builder)

1.简单工厂模式

1.1 基本介绍

定义:

  • 由一个工厂对象决定创建哪一种产品类的实例
  • 定义一个类,由这个类来封装实例化对象的行为
  • 它是工厂模式中最简单的一种,在软件开发中,当我们会用到大量的创建某种、某类或某批对象时,就会使用到工厂模式

1.2 案例:披萨订购

看一个披萨的项目,要便于披萨种类的扩展,要便于维护

  • 1.披萨的种类很多(比如GreekPizz、CheesePizz等)
  • 2.披萨的制作有prepare,bake,cut,box
  • 3.完成披萨店订购功能

传统方式实现:

package cn.cqu.simpleFactory.Pizza;

public abstract class Pizza {
    protected String name;

    //准备原材料,不同的披萨不一样,因此我们做成抽象方法
    public abstract void prepare();

    public void bake(){
        System.out.println(name+"baking;");
    }

    public void cut(){
        System.out.println(name+"cutting;");
    }

    public void box(){
        System.out.println(name+"boxing;");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package cn.cqu.simpleFactory.Pizza;

public class CheesePizza extends Pizza{
    @Override
    public void prepare() {
        System.out.println("给制作奶酪披萨准备原材料");
    }
}
package cn.cqu.simpleFactory.Pizza;

public class GreekPizza extends Pizza{
    @Override
    public void prepare() {
        System.out.println("给希腊披萨准备原材料");
    }
}
package cn.cqu.simpleFactory.order;

import cn.cqu.simpleFactory.Pizza.CheesePizza;
import cn.cqu.simpleFactory.Pizza.GreekPizza;
import cn.cqu.simpleFactory.Pizza.Pizza;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class OrderPizza {

    public OrderPizza() {
        Pizza pizza = null;
        String orderType;//订购披萨的类型
        do{
            orderType = getType();
            if(orderType.equals("greek"))
            {
                pizza = new GreekPizza();
                pizza.setName("希腊披萨");
            }
            else if(orderType.equals("cheese")){
                pizza = new CheesePizza();
                pizza.setName("奶酪披萨");
            }else
            {
                break;
            }

            //输出Pizza制作过程
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();

        }while(true);
    }

    //写一个方法,可以获取客户希望订购的披萨种类
    private String getType(){

        try {

            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza type:");
            String str = null;
            str = strin.readLine();
            return str;
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }

    }
}
package cn.cqu.simpleFactory;

import cn.cqu.simpleFactory.order.OrderPizza;

public class PizzaStore {
    public static void main(String[] args) {
        new OrderPizza();
    }
}

分析问题:

  • 当我们需要增加新的披萨种类时,我们需要去修改所有使用到Pizza的位置,这违背了OCP原则(“对扩展开放,对修改关闭”),而且这种违背带来的维护量巨大
  • 比如,假设还有OrderPizza2,OrderPizza3使用了Pizza

解决思路:

  • 如果有其他地方也有创建Pizza的代码,就意味着也需要修改,而创建Pizza的代码,往往有多处,于是,我们可以把创建Pizza对象封装到一个类当中(将变化聚集在笼子中(降低耦合,将变化转移到一个类中)),只需要修改该类即可,其他有创建到对象的代码就不需要修改了
  • 即将创建Pizza对象的工作交给SimpleFactory

简单工厂模式实现:

package cn.cqu.simpleFactory.Pizza;

public abstract class Pizza {
    protected String name;

    //准备原材料,不同的披萨不一样,因此我们做成抽象方法
    public abstract void prepare();

    public void bake(){
        System.out.println(name+"baking;");
    }

    public void cut(){
        System.out.println(name+"cutting;");
    }

    public void box(){
        System.out.println(name+"boxing;");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package cn.cqu.simpleFactory.Pizza;

public class CheesePizza extends Pizza{
    @Override
    public void prepare() {
        System.out.println("给制作奶酪披萨准备原材料");
    }
}
package cn.cqu.simpleFactory.Pizza;

public class GreekPizza extends Pizza{
    @Override
    public void prepare() {
        System.out.println("给希腊披萨准备原材料");
    }
}
package cn.cqu.simpleFactory;

import cn.cqu.simpleFactory.Pizza.CheesePizza;
import cn.cqu.simpleFactory.Pizza.GreekPizza;
import cn.cqu.simpleFactory.Pizza.Pizza;

//简单工厂类
public class SimpleFactory {


    //根据orderType返回对应的Pizza实例
    public Pizza createPizza(String orderType){
        System.out.println("使用简单工厂模式");

        Pizza pizza = null;

        if(orderType.equals("greek"))
        {
            pizza = new GreekPizza();
            pizza.setName("希腊披萨");
        }
        else if(orderType.equals("cheese")){
            pizza = new CheesePizza();
            pizza.setName("奶酪披萨");
        }

        return pizza;
    }
}
package cn.cqu.simpleFactory.order;


import cn.cqu.simpleFactory.Pizza.Pizza;
import cn.cqu.simpleFactory.SimpleFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class OrderPizza {

    private SimpleFactory factory;

    public OrderPizza(SimpleFactory factory){
        this.factory = factory;
    }

    public void order() {
        Pizza pizza = null;
        String orderType="";

        do{
            orderType = getType();
            pizza = this.factory.createPizza(orderType);
            if(pizza != null)
            {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else {
                System.out.println("订购披萨失败");
                break;
            }
        }while (true);
    }



    //写一个方法,可以获取客户希望订购的披萨种类
    private String getType(){

        try {

            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza type:");
            String str = null;
            str = strin.readLine();
            return str;
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }

    }
}
package cn.cqu.simpleFactory;

import cn.cqu.simpleFactory.order.OrderPizza;

public class PizzaStore {
    public static void main(String[] args) {
        OrderPizza orderPizza = new OrderPizza(new SimpleFactory());
        orderPizza.order(); 
        System.out.println("~~退出程序~~");
    }
}

  • 现在当我们增加披萨种类时,我们只需要修改简单工厂中的方法
  • 虽然此种写法还是违背开放封闭原则,但是这种修改局限在一个类中,这种修改我们可以接受
  • 简单工厂模式也叫静态工厂模式,因为工厂中createPizza方法有时候会被写成static方法,调用起来更方便些

1.3 JDK源码中Calendar类的创建中对简单工厂的应用

这里就是使用了简单工厂方法,根据传入的参数的不同的值,创建(new)出不同的对象

2.工厂方法模式

2.1 基本介绍

定义:

  • 定义一个创建对象的接口,让子类决定要实例化哪一个类,
  • 工厂方法模式将对象的实例化推迟到了子类

2.2 动机

  • 在软件系统中,经常面临着创建对象的工作,由于需求的变化,需要创建的对象的具体类型经常变化
  • 如何应对这种变化?如何绕过常规的对象创建方式(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

2.3 类图

2.4 案例:文件分割器

传统方式:

public class MainForm {
    public void Button_Click(){

        BinSplitter splitter = new BinSplitter();
        splitter.split();
    }
}

class BinSplitter{
    public void split()
    {
        System.out.println("二进制文件分割");
    }
}

分析上述代码的问题:

可以看到这句创建对象的语句,在MainForm中是高耦合的

随着需求变化,当我们需要去支持TxtSplitter,PictureSplitter,VideoSplitter,就需要去修改MainForm类,违背开放封闭原则,从编译的角度,MainForm的编译必须依赖于BinSplitter(细节),违背了依赖倒置原则

针对上述问题,我们首先想到的是提取出一个接口,代码如下:

public class MainForm {
    public void Button_Click(){

        ISplitter splitter = new BinSplitter();
        splitter.split();
    }
}



interface ISplitter{
    void split();
}

class BinSplitter implements ISplitter{
    public void split()
    {
        System.out.println("二进制文件分割");
    }
}


class TxtSplitter implements ISplitter{
    public void split()
    {
        System.out.println("txt文件分割");
    }
}

class PicSplitter implements ISplitter{
    public void split()
    {
        System.out.println("图片文件分割");
    }
}

class VideoSplitter implements ISplitter{
    public void split()
    {
        System.out.println("视频文件分割");
    }
}

分析上述代码的问题:

可以看到这句左边虽然已经面向接口编程,进行了抽象,但是右边仍然因为new具体的对象是高耦合的,编译时依赖于BinSplitter类(细节),所以还是违背依赖倒置原则的

所以上述问题的造成的原因是:new引起的高耦合

接下来用工厂方法模式进行解耦合,绕开MainForm使用new关键字创建对象的紧耦合:

具体类:

interface ISplitter{
    void split();
}

class BinSplitter implements ISplitter{
    public void split()
    {
        System.out.println("二进制文件分割");
    }
}


class TxtSplitter implements ISplitter{
    public void split()
    {
        System.out.println("txt文件分割");
    }
}

class PicSplitter implements ISplitter{
    public void split()
    {
        System.out.println("图片文件分割");
    }
}

class VideoSplitter implements ISplitter{
    public void split()
    {
        System.out.println("视频文件分割");
    }
}

工厂类:

interface ISplitterFactory{
    ISplitter createSplitter();
}

class BinSplitterFactory implements ISplitterFactory{

    @Override
    public ISplitter createSplitter() {
        return new BinSplitter();
    }
}

class TxtSplitterFactory implements ISplitterFactory{

    @Override
    public ISplitter createSplitter() {
        return new TxtSplitter();
    }
}

class PicSplitterFactory implements ISplitterFactory{

    @Override
    public ISplitter createSplitter() {
        return new PicSplitter();
    }
}

class VideoSplitterFactory implements ISplitterFactory{

    @Override
    public ISplitter createSplitter() {
        return new VideoSplitter();
    }
}
public class MainForm {

    private ISplitterFactory factory;

    public MainForm(ISplitterFactory factory){
        this.factory = factory;
    }

    public void Button_Click(){


        ISplitter splitter = factory.createSplitter();
        splitter.split();
    }
}
public class Client {

    public static void main(String[] args) {

        MainForm mainForm1 = new MainForm(new BinSplitterFactory());
        MainForm mainForm2 = new MainForm(new TxtSplitterFactory());
        MainForm mainForm3 = new MainForm(new PicSplitterFactory());
        MainForm mainForm4 = new MainForm(new VideoSplitterFactory());

        mainForm1.Button_Click();
        mainForm2.Button_Click();
        mainForm3.Button_Click();
        mainForm4.Button_Click();
    }
}

问题:这样的设计,BinSplitterFactory,TxtSplitterFactory,PicSplitterFactory,VideoSplitterFactory不也是通过new的方法来创建对象的吗?这不也是高耦合吗?

回答:其实很多设计模式所说的降低耦合的真正含义是将耦合转移,消除业务类之间的紧耦合(如MainForm不再依赖于具体类,而是依赖于抽象,符合依赖倒置原则),然后将耦合封装到一个局部的地方(如上述的各个具体工厂类),而不可能完全消除耦合,因为我们的程序肯定总要使用到其他类,只要使用,就存在依赖(耦合)。上述的代码当我们当我们要新增具体类的时候,我们也只需要添加对应的具体的工厂即可,而不需要修改MainForm,消除了MainForm(业务类)对具体类的紧耦合,符合开放封闭原则。

2.4 总结:

  • Factory Method模式用于隔离类对象的使用者和具体类之间的耦合关系,面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱
  • Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而事项一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系
  • Factory Method模式解决“单个对象”的需求变化,缺点在于要求创建方法/参数相同

3.抽象工厂模式

3.1 基本介绍

定义:

  • 定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
  • 将工厂抽象成两层,AbstractFactory(抽象工厂)和具体实现的工厂子类,程序员可以根据创建对象类型使用对应的工厂子类,这样将单个的简单工厂类变成工厂簇,更利于代码的维护和扩展

3.2 动机

  • 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?

3.3 类图

3.4 案例:数据库访问

传统方式实现:

/**
 *  假设如下是我们要连接SqlServer数据库连接读取数据,需要创建如下对象
 */
public class EmployeeDao {

    public ArrayList<Employee> getEmployees(){
        SqlConnection connection =new SqlConnection();
        //...

        SqlCommand command = new SqlCommand();
        //...

        SqlDataReader reader = new SqlDataReader();
        //...

    }
}

分析问题:

  • 当我们的需求变化,要使用比如Mysql、Oracle数据库时,上述代码就显现出高耦合
  • 我们的思考是使用面向接口的方式去改进

伪代码如下:

/**
 *  假设如下是我们要连接SqlServer数据库连接读取数据,需要创建如下对象
 */
public class EmployeeDao {

    public ArrayList<Employee> getEmployees(){
        IDBConnection connection =new SqlConnection();
        //...

        IDBCommand command = new SqlCommand();
        //...

        IDataReader reader = new SqlDataReader();
        //...

    }
}

/**
 * 数据库连接所需的几个对象的接口
 */

interface IDBConnection {
    
}

interface IDBCommand{
    
}

interface IDataReader{
    
}


/**
 * SqlServer数据库
 */
class SqlConnection implements IDBConnection{
    
}

class SqlCommand implements IDBCommand{
    
}

class SqlDataReader implements IDataReader{
    
}


/**
 * Mysql数据库
 */
class MysqlConnection implements IDBConnection{

}

class MysqlCommand implements IDBCommand{

}

class MysqlDataReader implements IDataReader{

}

/**
 * Oracle数据库
 */
class OracleConnection implements IDBConnection{

}

class OracleCommand implements IDBCommand{

}

class OracleDataReader implements IDataReader{

}

分析问题:

  • 这时我们又面临像上文工厂方法模式的案例中讲解的问题,右边的new并没有解耦
  • 我们考虑使用工厂方法模式来解耦

工厂方法模式实现伪码:

/**
 *  假设如下是我们要连接SqlServer数据库连接读取数据,需要创建如下对象
 */
public class EmployeeDao {
    
    IDBConnectionFactory connectionFactory;
    IDBCommandFactory commandFactory;
    IDataReaderFactory dataReaderFactory;

    public EmployeeDao(IDBConnectionFactory connectionFactory, 
                       IDBCommandFactory commandFactory, 
                       IDataReaderFactory dataReaderFactory) {
        this.connectionFactory = connectionFactory;
        this.commandFactory = commandFactory;
        this.dataReaderFactory = dataReaderFactory;
    }

    public ArrayList<Employee> getEmployees(){
        IDBConnection connection =connectionFactory.createDBConnection();
        //...

        IDBCommand command = commandFactory.createDBCommand();
        //...

        IDataReader reader = dataReaderFactory.createDataReader();
        //...

    }
}

/**
 * 数据库连接所需的几个对象的接口
 */

interface IDBConnection {

}

interface IDBConnectionFactory {
    IDBConnection createDBConnection();
}


interface IDBCommand{

}

interface IDBCommandFactory{
    IDBCommand createDBCommand();
}

interface IDataReader{

}

interface IDataReaderFactory{
    IDataReader createDataReader();
}


/**
 * SqlServer数据库
 */
class SqlConnection implements IDBConnection{

}

class SqlConnectionFactory implements IDBConnectionFactory{

}

class SqlCommand implements IDBCommand{

}

class SqlCommandFactory implements IDBCommandFactory{

}

class SqlDataReader implements IDataReader{

}

class SqlDataReaderFactory implements IDataReaderFactory{

}


/**
 * Mysql数据库
 */
class MysqlConnection implements IDBConnection{

}

class MysqlConnectionFactory implements IDBConnectionFactory{

}

class MysqlCommand implements IDBCommand{

}

class MysqlCommandFactory implements IDBCommandFactory{

}

class MysqlDataReader implements IDataReader{

}

class MysqlDataReaderFactory implements IDataReaderFactory{

}

/**
 * Oracle数据库
 */
class OracleConnection implements IDBConnection{

}

class OracleConnectionFactory implements IDBConnectionFactory{

}

class OracleCommand implements IDBCommand{

}

class OracleCommandFactory implements IDBCommandFactory{

}

class OracleDataReader implements IDataReader{

}

class OracleDataReaderFactory implements IDataReaderFactory{

}

分析问题:

  • 上述代码的实现表面看起没有问题,但是实际上,不能保证它们三个是同一种类型数据库,比如客户完全可以将connectionFactory赋予SqlConnectionFactory,commandFactory赋予MysqlCommandFactory,dataReaderFactory赋予OracleDataReaderFactory
  • 但事实上,上述的做法是不合理的,我们必须保证它们三个对象指向相同的数据库对象实例,所以我们发现这三个对象具有相关性,我们应该把这三个对象的创建放到一个工厂类中保证它们使用相同的数据库创建,(高内聚(把具有相关性的放到一起))于是使用抽象工厂模式来实现

抽象工厂模式实现伪代码:

/**
 *  假设如下是我们要连接SqlServer数据库连接读取数据,需要创建如下对象
 */
public class EmployeeDao {

    IDBFactory dbFactory;

    public EmployeeDao(IDBFactory dbFactory) {
        this.dbFactory = dbFactory;
    }

    public ArrayList<Employee> getEmployees(){
        IDBConnection connection =dbFactory.createDBConnection();
        //...

        IDBCommand command = dbFactory.createDBCommand();
        //...

        IDataReader reader = dbFactory.createDataReader();
        //...

    }
}

/**
 * 数据库连接所需的几个对象的接口
 */

interface IDBConnection {

}

interface IDBCommand{

}

interface IDataReader{

}

//高内聚
//将三个相关性很强的对象的创建放在一个工厂,
//将本来松散的交给使用者创建对象(用户可以对每个对象自由创建),变成内聚的交给用户创建对象(用户创建的几个对象是同一种类型,具有相关性)
interface IDBFactory {
    IDBConnection createDBConnection();
    IDBCommand createDBCommand();
    IDataReader createDataReader();
}



/**
 * SqlServer数据库
 */
class SqlConnection implements IDBConnection{

}
class SqlCommand implements IDBCommand{

}
class SqlDataReader implements IDataReader{

}

class SqlDBFactory implements IDBFactory{

}



/**
 * Mysql数据库
 */
class MysqlConnection implements IDBConnection{

}

class MysqlCommand implements IDBCommand{

}

class MysqlDataReader implements IDataReader{

}

class MysqlDBFactory implements IDBFactory{

}

/**
 * Oracle数据库
 */
class OracleConnection implements IDBConnection{

}


class OracleCommand implements IDBCommand{

}


class OracleDataReader implements IDataReader{

}

class OracleDBFactory implements IDBFactory{

}

3.5 总结

  • 如果没有应对“多系列对象创建”的需求变化,则没有必要使用Abstract Factory模式,这时使用简单的工厂完全可以
  • “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系,不同系列的对象之间不能相互依赖
  • Abstract Factory模式主要在于应对“新系列”的需求变动,其缺点在于难以应对“新对象”的需求变动,因为在抽象工厂中(稳定的部分)我们规定好了创建指定的对象,设计模式都优缺点,设计模式应用的前提就是稳定部分是不变的,当稳定部分都发生变化(如这里新增对象),这个设计模式就不适用了,因为设计模式是解决稳定中有变化的问题
  • 工厂方法模式是抽象工厂的特例,当我们的抽象工厂类中只创建一个对象时,就是工厂方法模式

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值