创建型模式之工厂模式

本文介绍了Java中常用的工厂模式,它属于创建型模式,创建对象时不暴露逻辑。阐述了其意图、优缺点及使用场景,分析了面向接口编程中遇到的问题并给出简单工厂解决方案,还讲解了示例实现、典型问题及新增实现类的处理办法,强调其本质是选择实现,可使客户端和实现解耦。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


文章开头先给大家一张图,看看设计模式的类型

设计模式类型

简介

工厂模式(Factory Pattern)是Java中最常用的设计模式之一.从上图看出它属于创建型模式.

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来执行新创建的对象

介绍

意图: 定义一个创建对象的接口,让其子类自己决定实例化哪一个类,工厂模式使其创建过程延迟到子类进行

何时使用:明确不同条件下创建不同实例

如何解决: 让其子类实现工厂接口,返回一个抽象的产品

关键代码:创建过程在其子类执行

优点: 1. 一个调用者想创建一个对象,只要知道其名称就可以了 2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以了 3 .屏蔽产品的具体实现,调用者只关心产品的接口

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度增加了系统的复杂度,同时也增加了系统具体类的依赖

注意事项:作为一种创建型模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度

引出

面向接口编程是面向对象编程中的一个重要原则
在OC中,通常可以按照三层来划分,UI层,逻辑层,数据层.在每一层里面,可以有很多个小模块,一个小模块对外也应该是一个整体,那么一个模块对外也应该提供接口,其他地方需要使用这个模块的功能,都应该通过此接口来进行调用

比如现在有一个接口API,然后有一个实现类Impl实现了它,在客户端怎么用这个接口呢?
通常都是在客户端创建一个Impl的实例,把它赋值给一个API接口类型的变量,然后客户端就可以通过这个变量来操作接口的功能了
下面的代码是java

/**
   * 某个接口(通用的、抽象的、非具体的功能)
   */  
public interface Api {  
     /**
      * 某个具体的功能方法的定义,用test1来演示一下。
      * 这里的功能很简单,把传入的s打印输出即可
      * @param s 任意想要打印输出的字符串
      */  
     public void test1(String s);  
}  

添加一个实现类

/**
   * 对接口的实现
   */  
public class Impl implements Api{  
    public void test1(String s) {  
        System.out.println("Now In Impl. The input s=="+s);  
    }  
} 
  1. 客户端调用
/**
   * 客户端:测试使用Api接口
   */  
public class Client {  
    public static void main(String[] args) {  
        Api api = new Impl();  
        api.test1("哈哈,不要紧张,只是个测试而已!");  
    }  
}

有何问题

上面的代码大家应该都懂,写的没有错.但是这个会有一个问题

Api api = new Impl(); 

你会发现在客户端调用的时候,客户端不但知道了接口,同时还知道了具体的实现就是Impl.而接口的思想是"封装隔离",Impl这个实现类,应该是被接口API封装并同客户端隔离开的,也就是说,客户端根本就不应该知道具体的实现类是Impl.

有朋友说,那好,我就把Impl从客户端拿掉,让Api真正的对实现进行“封装隔离”,然后我们还是面向接口来编程。可是,新的问题出现了,当他把“new Impl()”去掉过后,发现他无法得到Api接口对象了,怎么办呢?

简单的说,就是只知接口不只实现该怎么办呢?

解决方案

用来解决上述问题的一个合理的解决方案就是简单工厂
分析上面的问题,虽然不能让模块外部知道模块内的具体实现,但是模块内部是可以知道实现类的,而且创建接口是需要具体实现类的。那么干脆在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂,就叫Factory吧。这样一来,客户端就可以通过这个Factory来获取需要的接口对象,然后调用接口的方法来实现需要的功能,而且客户端也不用再关心具体实现了。
在这里插入图片描述

示例实现

我们将创建一个Shape接口和实现Shape接口的实体类,下一步是定义工程类ShapeFactory

我们的演示类使用ShapeFactory来获取Shape对象,它指向ShapeFactory传递信息(CIRCLE/RECTANGLE/SQUARE),以便获取它所需对象的类型
在这里插入图片描述

步骤1:

创建一个接口:

 @interface Shape : NSObject

 - (void)draw;

 @end

步骤2:

创建实现接口的实体类

@interface RectAngle :Shape

@end

@implementation RectAngle
- (void)draw
{
    NSLog(@"draw rect angle");
}
@end
@interface Square : Shape

@end
@implementation Square
- (void)draw
{
    NSLog(@"draw square");
}
@end
@interface Circle : Shape

@end
@implementation Circle
- (void)draw
{
    NSLog(@"draw circle");
    
}
@end

步骤3
创建一个工程,生成基于给定信息的实体类的对象

typedef NS_OPTIONS(NSUInteger, ShapeType){
    ShapeTypeRectAngle,
    ShapeTypeSquare,
    ShapeTypeCircle,
    ShapeTypeUnknown,
};
@class Shape;

NS_ASSUME_NONNULL_BEGIN

@interface ShapeFactory : NSObject

- (Shape *)getShape:(ShapeType)shapeType;

@end

@implementation ShapeFactory

- (Shape *)getShape:(ShapeType)shapeType
{
    if (shapeType == ShapeTypeCircle) {
        return [[Circle alloc] init];
    } else if (shapeType == ShapeTypeSquare){
        return [[Square alloc] init];
    } else if (shapeType == ShapeTypeRectAngle){
        return [[RectAngle alloc] init];
    }
    return nil;
}
@end

步骤4:
使用该工厂,通过传递类型信息来获取实体类的对象

    ShapeFactory *factory = [[ShapeFactory alloc] init];
    Shape *shape1 = [factory getShape:ShapeTypeRectAngle];
    [shape1 draw];
    
    Shape *shape2 = [factory getShape:ShapeTypeSquare];
    [shape2 draw];
    
    Shape *shape3 = [factory getShape:ShapeTypeCircle];
    [shape3 draw];

显示结果:
结果

如同上面的示例,客户端通过简单工程创建了一个实现接口的对象,然后面向接口编程,从客户端来看,它根本就不知道具体的实现是什么,也不知道如何实现的,它只知道通过工程获得了一个接口对象,然后就能通过这个接口来获取想要的功能

Demo地址

讲解

典型问题

可能有的人认为,上面示例中的简单工厂看起来不就是把客户端里面的实现类移动到工程里面吗?不还是通过new一个实现类来得到接口吗?
理解这个问题的重点在于理解简单工厂所处的位置
我们都知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现.工厂的位置是位于封装体内的,也就是工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以工厂知道具体的实现类是没有关系的。整理一下简单工厂的结构图,新的图如图所示:
在这里插入图片描述

图中虚线框,就好比是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件,在这个封装体里面,只有接口和工厂是对外的,也就是让外部知道并使用的,所以故意漏了一些在虚线框外,而具体的实现类是不对外的,被完全包含在虚线框内。
对于客户端而言,只是知道了接口Api和简单工厂Factory,通过Factory就可以获得Api了,这样就达到了让Client在不知道具体实现类的情况下获取接口Api。所以看似简单的把“new Impl()”这句话从客户端里面移动到了工厂里面,其实是有了质的变化的。

新增实现类问题

现在已经学会经过简单工厂来选择具体的实现类了,可是还有问题.比如:在现在的示例中,再新增一种实现,会怎么样呢?

那么就需要修改工厂类,才能把心的实现添加到现有系统中.那么类似下面这样来修改工厂:

- (Shape *)getShape:(ShapeType)shapeType
{
    if (shapeType == ShapeTypeCircle) {
        return [[Circle alloc] init];
    } else if (shapeType == ShapeTypeSquare){
        return [[Square alloc] init];
    } else if (shapeType == ShapeTypeRectAngle){
        return [[RectAngle alloc] init];
    } else if(shapeType == ShapeTypeRect){
        return [[Rect alloc] init];
    }
    return nil;
}

每次新增加一个实现类都来修改工厂类的实现,肯定不是一个好的实现方式,那么现在希望新增加了实现类后不修改工程类,该怎么办呢?

解决办法:

  1. 使用配置文件,当有了新的实现类过后,只要在配置文件里面配置上新的实现类就好了
- (Shape *)getShape:(ShapeType)shapeType
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"shape.plist" ofType:nil];
    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path];
    NSString *shapeName = dic[@(shapeType)];
    Shape *shape = (Shape *)NSClassFromString(shapeName);
    if ([shape isKindOfClass:[Shape class]] && shape) {
        return  shape;
    }else{
        return nil;
    }
}

如果新添加了实现类,修改这里的配置文件就可以了,就不需要修改工厂类了,遵循了"开闭原则"

  1. 直接使用实现类字符串
-(Shape *)selectCpuWithType:(NSString *)type
{
    Shape *shape = (Shape *)[NSClassFromString(type)new];
    if ([shape isKindOfClass:[Shape class]] && shape) {
        return  shape;
    }else{
        return nil;
    }
}

虽然上面的实现会有硬编的问题,但优点也很明显,一点硬编的问题还是可以接受的

当然上面的两种实现方式,有一个共同的缺点:由于是从客户端在调用工厂的时候,传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理,这就要求必须在一定程度上,想客户暴露一定的内部实现细节,没有实现完全的"傻瓜式"调用

结论

工程模式的好处在于提供了统一的接口,外部并不知道具体的实现类,也无需关心其实现

工厂模式的本质:选择实现.其重点在于选择,实现是已经做好的,就算实现再简单,也要具体的实现类来实现,而不是在工厂里面来实现.工厂的目的在于为客户端来选择相应的实现,从而使得客户端和实现之间解耦,这样一来,具体实现发生了变化,就不用变动客户端了,这个变化会被工厂吸收和屏蔽掉

Demo地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值