设计模式—创建者模式之工厂模式

我们常说的 23中设计模式 ,大致可以分为3类:

  • 创建者模式
  • 结构型模式
  • 行为型模式

工厂设计模式 所属于创建者模式

创建者模式

顾名思义,关注点是怎么创建对象,特点是将对象的创建与使用相分离。降低系统耦合度,使用者不关注细节。

工厂设计模式

在JAVA中,万物皆对象,这些对象都需要创建,如果在业务代码中创建的时候直接 new 对象,就会导致该对象严重耦合。举个例子:
现在有两个手机类 华为手机(PhoneHuawei)苹果手机(PhoneApple) ,有一个商店类 Store,商店中提供一个方法,根据传入的参数,售卖不同的手机。如果我们不使用 工厂设计模式,则初期的代码是这个样子。

  • 定义 手机抽象类(Phone)
/**
 * 手机抽象类,描述产品的主要功能
 */
public abstract class Phone {
    // 手机名称
    String name ;
    // 获取手机信息
    public abstract String getName() ;
    // 公共方法
    public void call() {
        System.out.println("我是Phone,我可以打电话");
    }
}
  • 定义华为手机(PhoneHuawei) 类,继承 Phone
/**
 * 华为手机类
 */
public class PhoneHuawei extends Phone{

    public PhoneHuawei() {
        this.name = "华为手机" ;
    }

    @Override
    public String getName() {
        return this.name ;
    }
}
  • 定义苹果手机(PhoneApple) 类,继承 Phone
/**
 * 苹果手机
 */
public class PhoneApple extends Phone{

    public PhoneApple() {
        this.name = "苹果手机" ;
    }
    @Override
    public String getName() {
        return this.name ;
    }
}

  • 定义商店(Store)
/**
 * 商店
 */
public class Store {

    public static Phone getPhone(String type) {
        if ("apple".equals(type)) {
            return new PhoneApple() ;
        } else if ("huawei".equals(type)) {
            return new PhoneHuawei() ;
        } else {
            throw new RuntimeException("本地没有改类型的手机~~~~") ;
        }
    }
}
  • 测试类
/**
 * 测试类
 */
public class Client {
    public static void main(String[] args) {
        Phone phone = Store.getPhone("apple");
        System.out.println("买了手机:" + phone.getName());
        phone.call();
    }
}

输出结果:

买了手机:苹果手机
我是Phone,我可以打电话

通过以上代码可以发现,创建对象的操作,都是在 Store类中完成的。现在业务变更,要求传入 xiaomi 的时候,再创建一个小米手机,根据上面的案例,我们只能在 Store 中再添加一个 if 判断。如果有多个Store 类,我们需要添加很多不必要的代码,而且还违反了软件设计的开闭原则

开闭原则: 对扩展开放,对修改关闭。意思是升级业务时,要对原有代码进行扩展,而非修改。

针对业务中大量 new 对象的耦合性,我们可以把对象的创建提取出来,创建一个专门用来生产对象的类,这种类就叫做 工厂类。根据上方案例,我们可以创建一个 手机工厂类 PhoneFactory 来专门生产手机:

/**
 * 手机工厂类
 */
public class PhoneFactory {

    public static Phone createPhone(String type) {
        if ("apple".equals(type)) {
            return new PhoneApple() ;
        } else if ("huawei".equals(type)) {
            return new PhoneHuawei() ;
        } else {
            throw new RuntimeException("本地没有改类型的手机~~~~") ;
        }
    }
}

改造一下商店的代码,从工厂中获取创建的对象:

/**
 * 商店
 */
public class Store {

    public static Phone getPhone(String type) {
        // 调用工厂方法,获取手机
        return PhoneFactory.createPhone(type) ;
    }
}

通过这种方式,可以把业务方法与对象创建相分离,减少代码的耦合性,即使后期增加手机类型,我们只需要更改工厂代码即可。
优点:

  • 封装了创建对象的过程,C端可以通过参数直接获取对象。把对象的创建和业务逻辑相分离,降低了C端代码修改的可能性,更加容易扩展。

缺点:

  • 增加新手机时,还是需要修改工厂代码,仍然违背了 开闭原则

小结一下上面的案例,每增加一种机型,就需要多加一条 if 判断,总是会修改原有代码。那有没有一种方式,可以避免修改代码,或者说,有没有一种方式,可以不通过 if 判断来生成对象。
作为C端的消费者,我们能够清晰的知道自己想要什么。所以 我们需要给 Store 传达一个信息,Store 根据这个信息,不需要做出任何判断,直接能返回我们想要的对象。所以我们传达的这个信息,不能再是字符串,而是需要和对象相关的,也就是接下来要提到的:手机生产商(手机工厂),所以就有了 工厂方法模式

工厂方法模式

之前我们是定义了一个 手机工厂 Phonefactory 类,然后在此类中通过if 来生产不同的对象。而 工厂方法模式 也是需要一个 PhoneFactory 工厂,但是此工厂我们定义成 抽象的,也提供生产手机的方法,但是 具体生产什么品牌的手机,需要由具体的子工厂来生产。先上代码:

  • 抽象手机工厂接口:
public interface PhoneFactory {
    /**
     * 生产手机,但是生产什么牌子的,由子工厂写实现方法
     * 有可能是华为,有可能是苹果,所以这个地方的返回值,应该是 所有手机的父类
     * @return
     */
    Phone createPhone() ;
}
  • 具体华为手机工厂类
/**
 * 华为手机工厂
 */
public class PhoneHuaweiFactory implements PhoneFactory {
    // 华为工厂,生产华为的手机
    @Override
    public Phone createPhone() {
        return new PhoneHuawei() ;
    }
}
  • 具体苹果手机工厂类
/**
 * 苹果手机工厂
 */
public class PhoneAppleFactory implements PhoneFactory{
    // 苹果工厂,生产苹果手机
    @Override
    public Phone createPhone() {
        return new PhoneApple();
    }
}

而商店Store 都会有自己的生产商(factory),生产商是啥,商店就生产啥,所以商店里,应该有一个 生产商对象,也就是所谓的 手机工厂

  • Store 代码:
public class Store {

    // 定义商店的生产商对象,有可能是华为,有可能是苹果,所以用他们的父类
    private PhoneFactory factory ;

    // 顾客是上帝,顾客扔什么进来,这个factory 就生产什么
    public void setFactory(PhoneFactory factory) {
        this.factory = factory ;
    }

    public Phone getPhone() {
        return factory == null ? null : factory.createPhone() ;
    }
}

接下来就很明了了,C端在明确知道自己想要什么的情况下,直接给商店里扔 具体的 生产商对象 就可以了。

  • 消费者端:
public class Client {
    public static void main(String[] args) {
        // 1、获取商店对象
        Store store = new Store() ;
        // 2、给商店设置手机类型的生产商,这里是想要获取苹果手机
        store.setFactory(new PhoneAppleFactory());
        // 3、获取手机
        Phone phone = store.getPhone();
        // 4、打印手机信息
        System.out.println("获取了新手机:" + phone.getName());
        phone.call();
    }
}

查看结果:

获取了新手机:苹果手机
我是Phone,我可以打电话

如果后期业务再加其他品牌的手机,只需要再加一个 实体类和对应的工厂类就可以了。避免了修改原有的代码,符合了 开闭原则
优点:

  • C端只需要知道具体的工厂名称,就可以创建对应的产品,无须知道产品的具体创建过程。
  • 符合了开闭原则,无须对原有工厂进行任务修改。

缺点:

  • 每增加一个产品,都需要增加一个实体类 和 对应的工厂类,增加了系统的复杂度。

抽象工厂模式

随着手机店业务的升级,不止卖手机了,为了保护手机及美观,店里还要卖手机套套 TT类
手机套肯定也会分 华为手机套套 TTHuawei苹果手机套套TTApple,我们需要定义一个 TT 抽象类,描述TT的特征,然后创建两个实体类,来继承这个 TT 抽象类。话不多说,上代码。

  • TT抽象类
/**
 * 手机套套
 */
public abstract class TT {
    // 获取手机套的名称
    public abstract void getName() ;

    // 通用的功能
    public void function() {
        System.out.println("保护手机~~");
    }
}
  • 华为TT类
public class TTHuawei extends TT{
    @Override
    public void getName() {
        System.out.println("华为手机套套");
    }
}
  • 苹果TT类
public class TTApple extends TT{
    @Override
    public void getName() {
        System.out.println("苹果手机套套");
    }
}

按照 工厂方法模式,我们还需要定义一个 抽象TT生产工厂interface TTFactory,再分别创建 具体华为TT工厂class HuaweiTTFactory implements TTFactory具体苹果TT工厂class AppleTTFactory implements TTFactory ,然后再去 商店类Store 中声明一个TT工厂对象,并提供 setter 方法。 在C端调用时,我们除了给 store 对象 setPhoneFactory 之外,还需要额外setTTFactory,部分关键代码如下:

  • 商店代码
public class Store {

    // 定义手机的生产商对象,有可能是华为工厂,有可能是苹果工厂,所以用他们的抽象
    private PhoneFactory phoneFactory ;
    // 定义手机套的生产对象
    private TTFactory ttFactory ;
    
    // 顾客是上帝,顾客扔什么进来,这个factory 就生产什么
    public void setPhoneFactory (PhoneFactory factory) {
        this.phoneFactory = factory ;
    }

    public void setTTfactory (TTFactory factory) {
        this.ttFactory = factory ;
    }

    public Phone getPhone() {
        return phoneFactory == null ? null : phoneFactory.createPhone() ;
    }

    public TT getTT() {
        return ttFactory == null ? null : ttFactory.getTT() ;
    }
}
  • 消费者代码:
public class Client {
    public static void main(String[] args) {
        // 1、获取商店对象
        Store store = new Store() ;
        // 2、给商店设置手机类型的生产商,这里是想要获取苹果手机
        store.setPhoneFactory(new PhoneAppleFactory());
        // 3、获取手机
        Phone phone = store.getPhone();
        
        // 4、给商店设置手机套套生产商,获取套套
        store.setTTfactory(new HuiweTTFactory());
        // 5、获取手机套
        TT tt = store.getTT() ;
    }
}

新加的业务,虽说以上这种方式也可以实现需求,但是有一个很明显的问题:买了苹果手机,但是手机套套却不小心买了华为的。虽然主观上是可以的,但是客观上 是不允许这种情况发生的。
所以在程序设计的时候,我们需要想办法去改变这种情况。首先需要了解两个概念: 产品等级产品族

  • 产品等级:指具有相同特征的一类产品。华为手机和苹果手机,他们都是同一类产品,我们把这种具有相同特征的产品称作产品等级。
  • 产品族:具有多种 产品等级、且多个产品等级之间互相有某种联系的,叫做产品族。比如 华为手机 和 华为手机套,这两个产品等级 都属于华为,所以 华为 就可以叫做产品族。

听起来很抽象,多举几个例子:

  • 商务外套、休闲外套属于一个产品等级,商务裤子、休闲裤子属于一个产品等级,商务风 就属于一个产品族,休闲风 也属于一个产品族。
  • 猫和狗属于一个产品等级,鸡和鸭属于一个产品等级。所以 动物 属于一个产品族,家禽属于一个产品族。

个人的理解,产品族 是更抽象的,根据实际的业务去抽取出来的。一个产品族 可以包含很多 有某种关联的产品等级。
上方的例子中,华为手机、苹果手机属于一个产品等级,华为手机套、苹果手机套属于一个产品等级,华为 属于一个产品族,苹果同理,也属于一个产品族。

所以根据实际业务,我们去商店,只希望说出一个 产品族时,就希望等获得 当前产品族下的,所有的产品等级。比如 去 Store ,说 华为,就会得到 华为手机 和 华为手机套。
我们在设计这类业务的代码时,一般会把 产品族的描述作为最底层的抽象接口,每个具体的产品族工厂类去实现该接口。这句话的意思就是,华为 和 苹果这两个产品族,都可以 制造手机和手机套,所以我们就把 制造手机制造手机套 的功能,抽取出来设计成一个抽象的工厂。然后 创建 华为工厂苹果工厂 去实现这个接口。

  • 抽象工厂
/**
 * 生产手机和套套的抽象
 */
public interface PhoneAndTTFactory {
    /**
     * 生产手机
     * @return
     */
    Phone createPhone() ;
    /**
     * 生产套套
     * @return
     */
    TT createTT() ;
}
  • 华为工厂
/**
 * 华为工厂
 * 生产 华为手机 和 华为手机套
 */
public class HuaweiFactory implements PhoneAndTTFactory{

    @Override
    public Phone createPhone() {
        return new PhoneHuawei() ;
    }

    @Override
    public TT createTT() {
        return new TTHuawei() ;
    }
}
  • 苹果工厂
/**
 * 苹果工厂
 * 生产苹果手机 和 苹果套套
 */
public class AppleFactory implements PhoneAndTTFactory{

    @Override
    public Phone createPhone() {
        return new PhoneApple() ;
    }

    @Override
    public TT createTT() {
        return new TTApple() ;
    }
}
  • 商店类
/**
 * 商店
 */
public class Store {

    private PhoneAndTTFactory factory ;

    /**
     * 因为 华为工厂 和 苹果工厂都实现了 PhoneAndTTFactory 接口
     * 所以 这里可以接受 任何一个工厂
     * 所以返回的对象,也就是各个工厂生产的对象
     * @param factory
     */
    public void setFactory(PhoneAndTTFactory factory) {
        this.factory = factory ;
    }

    public Phone getPhone() {
        return factory.createPhone() ;
    }

    public TT getTT() {
        return factory.createTT() ;
    }
}
  • 消费者
public class Client {
    public static void main(String[] args) {
        Store store = new Store() ;

        // 传递产品族工厂即可
        store.setFactory(new AppleFactory());

        Phone phone = store.getPhone();
        TT tt = store.getTT();

        System.out.println("手机:" + phone.getName());
        tt.getName();
    }
}

结果:

手机:苹果手机
苹果手机套套

优点
当一个产品族中,多个对象被设计成在一起工作时,它可以保证客户端始终只使用同一个产品族中的对象。
缺点
当产品族中需要添加一个产品的时候,所以的工厂类都需要进行修改。比如 再增加 华为电脑 和 苹果电脑,需要同时修改 华为工厂 和 苹果工厂的代码。
使用场景
当需要创建的对象是一系列相关产品或相互依赖的产品族时,比如 输入法(更换输入法,背景图、颜色、字体等全部发生变化),游戏皮肤(更改皮肤后,台词、特效等都会发生变化)。

工厂模式扩展(重要)

简单工厂 + 配置文件接触耦合
可以通过工厂模式+配置文件的方式接触工厂对象和产品的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果使用对象,直接进行获取即可。

第一步:定义配置文件
在maven项目的 resources下,创建bean.properties 文件
在这里插入图片描述

huawei: cn.lzc.demo5.entity.PhoneHuawei
apple: cn.lzc.demo5.entity.PhoneApple

第二步:改进工厂类


public class CustomFactory {

    // 存放对象
    private static Map<String, Phone> phoneMap = new HashMap<String, Phone>(0) ;

    static {
        // 获取配置文件输入流
        Properties properties = new Properties() ;
        InputStream inputStream = CustomFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            properties.load(inputStream);
            // 获取定义的key 的集合
            Set<Object> keys = properties.keySet();
            // 遍历keys,获取对象
            for (Object key : keys) {
                // 获取全类名
                String className = properties.getProperty((String) key) ;
                Class clazz = Class.forName(className) ;
                Phone phone = (Phone) clazz.newInstance() ;
                phoneMap.put((String) key, phone) ;
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取对象
     * @param name
     * @return
     */
    public static Phone createPhone (String name) {
        return phoneMap.get(name) ;
    }
}

第三步:消费者代码

public class Client {
    public static void main(String[] args) {
        // 根据配置文件中的key,来获取对象
        Phone phone = CustomFactory.createPhone("apple") ;
        System.out.println(phone.getName());
        phone.call();
    }
}

打印

苹果手机
我是Phone,我可以打电话

此方法也符合开闭原则,如果需要添加一种咖啡,则只需要 在配置文件中 添加一个操作即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值