设计模式-工厂模式

60 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

耦合度

紧密连接程度,

耦合度低,可扩展性和可维护性提高。

接口

 接口能实现拓展功能。

模式意图

将对象的实例化交给工厂,只需要告诉工厂需要什么对象。

让使用者只需要关注自己需要的,而不需要关注这个东西是怎么创建的,让实例创建和实例使用解耦。

用户角度

客户告诉商店需要什么商品,商店提供商品。

简单工厂:输入字符串

工厂方法:定义具体实现类工厂

业务角度

商店根据客户需求,向工厂获取原始商品再进行加工。

简单工厂:将字符串传给工厂,工厂根据字符串值制作商品

工厂方法:传入具体实现类工厂,实现类工厂创建具体商品(工厂与商品一对一)

工厂角度

根据某个标准判断返回的商品

简单工厂:根据字符串判断

工厂方法:具体工厂直接返回具体商品

首先解决业务模块和商品的耦合(即添加商品不需要修改业务的代码),再解除商品和工厂的耦合(添加商品不需要修改工厂的代码)。

接口实现了多态,通过使用接口,消除了原来的判断逻辑,所以可以直接增添新的实现类而不需要修改代码来实现判断逻辑。

引例

public class Factory {
    public static void main(String[] args) {
        CoffeeStore coffeeStore = new CoffeeStore();
        Coffee coffee = coffeeStore.orderCoffee("american");
        System.out.println(coffee.getName());
    }
}

abstract class Coffee{
    public abstract String getName();

    public void addSugar(){
        System.out.println("加糖");
    }

    public void addMilk(){
        System.out.println("加奶");
    }
}

class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}

class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

class CoffeeStore{
    public Coffee orderCoffee(String type){
        //根据不同类型创建不同咖啡
        Coffee coffee = null;
        if("american".equals(type)){
            coffee = new AmericanCoffee();
        }else if("latte".equals(type)){
            coffee = new LatteCoffee();
        }
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }
}

在本例中,如果要再添加新的咖啡品种的话,势必要修改CoffeeStore类中的代码。

也就是商店类和商品类耦合在了一起,不能直接扩展新的商品。

工厂模式的特点就是解耦。

简单工厂

结构

  • 抽象产品
  • 具体产品
  • 具体工厂
public class Client {
    public static void main(String[] args) {
        CoffeeStore coffeeStore = new CoffeeStore();
        Coffee coffee = coffeeStore.orderCoffee("latte");
        System.out.println(coffee.getName());
    }
}
class CoffeeStore{
    public Coffee orderCoffee(String type){
        SimpleFactory factory = new SimpleFactory();
        Coffee coffee = factory.createCoffee(type);
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

class SimpleFactory{
    public Coffee createCoffee(String type){
        Coffee coffee = null;
        if("american".equals(type)){
            coffee = new AmericanCoffee();
        }else if("latte".equals(type)){
            coffee = new LatteCoffee();
        }
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }
}


abstract class Coffee{
    public abstract String getName();

    public void addSugar(){
        System.out.println("加糖");
    }

    public void addMilk(){
        System.out.println("加奶");
    }
}

class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}

class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

虽然通过工厂类解除了商品类和商店的耦合,但是又增加了新的耦合,即商店和工厂耦合,工厂和商品耦合。

如果以后添加新的产品,势必修改工厂类的代码。

与之前代码的区别是,当需要增加多个客户端如甜品店,甜品店内也有咖啡,则只需要修改工厂类就可以,之前的代码则要在店类中修改很多代码,增加很多与咖啡店相同的if判断。

增加新的产品时,不需要在业务部分(商店)修改代码了,但是需要修改工厂类的代码。

静态工厂

将简单工厂中的生产商品的方法修改为静态,在生成商品时不需要创建工厂类了。

工厂方法

结构

  • 抽象产品
  • 具体产品
  • 抽象工厂
  • 具体工厂:具体产品与具体工厂一一对应
public class Client {
    public static void main(String[] args) {
        CoffeeStore coffeeStore = new CoffeeStore();
        coffeeStore.setFactory(new AmericanCoffeeFactory());
        Coffee coffee = coffeeStore.orderCoffee();
        System.out.println(coffee.getName());
    }
}
class CoffeeStore{
    private CoffeeFactory factory;
    public void setFactory (CoffeeFactory factory){
        this.factory = factory;
    }
    public Coffee orderCoffee(){
        Coffee coffee = factory.createCoffee();
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }

}
//抽象工厂
interface CoffeeFactory{
    Coffee createCoffee();
}

class AmericanCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}

class LatteCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

abstract class Coffee{
    public abstract String getName();

    public void addSugar(){
        System.out.println("加糖");
    }

    public void addMilk(){
        System.out.println("加奶");
    }
}

class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}

class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

​编辑

如果想添加一种咖啡的品种,不再需要修改工厂的代码,只需要再创建新的咖啡工厂的实现类。

优点

用户只需要知道具体的工厂就可以得到所需产品。

系统在新增加产品时只需要添加具体产品类和对应的工厂,无需对原工厂进行任何修改。

缺点

每增加一个产品就要增加一个产品类和工厂类,增加了系统的复杂度。

 工厂方法模式只考虑生成同级别的产品,如生产咖啡只生成各种各样的咖啡,不会去生产牛奶。

但是现实中,一个工厂往往是生成多种种类的产品。

抽象工厂

抽象工厂模式考虑的是多级别产品的生产,工厂方法是只生成一个等级的产品

​编辑

结构

  • 抽象工厂:包含多个创建产品的方法,每个方法是同一个产品族中不同产品
  • 具体工厂
  • 抽象产品
  • 具体产品
public class Client {
    public static void main(String[] args) {
        CoffeeStore coffeeStore = new CoffeeStore();
        coffeeStore.setFactory(new AmericanFactory());
        Coffee coffee = coffeeStore.orderCoffee();
        System.out.println(coffee.getName());
        Dessert dessert = coffeeStore.orderDessert();
        dessert.show();
    }
}
class CoffeeStore{
    private Factory factory;

    public void setFactory (Factory factory){
        this.factory = factory;
    }
    public Coffee orderCoffee(){
        Coffee coffee = factory.createCoffee();
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }
    public Dessert orderDessert(){
        Dessert dessert = factory.createDessert();
        return dessert;
    }

}
//工厂
interface Factory{
    Coffee createCoffee();
    Dessert createDessert();
}
//美式风味工厂
class AmericanFactory implements Factory{
    //美式咖啡
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
    //抹茶慕斯
    @Override
    public Dessert createDessert() {
        return new MatchaMousse();
    }
}

//意大利风味工厂
class ItalyFactory implements Factory{
    //拿铁咖啡
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
    //提拉米苏
    @Override
    public Dessert createDessert() {
        return new Trimisu();
    }
}



//咖啡
abstract class Coffee{
    public abstract String getName();

    public void addSugar(){
        System.out.println("加糖");
    }

    public void addMilk(){
        System.out.println("加奶");
    }
}
//美式咖啡
class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}
//拿铁咖啡
class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}
//甜品
abstract class Dessert{
    public abstract void show();
}

//提拉米苏
class Trimisu extends Dessert{
    @Override
    public void show() {
        System.out.println("提拉米苏");
    }
}

//抹茶慕斯
class MatchaMousse extends Dessert{
    @Override
    public void show() {
        System.out.println("抹茶慕斯");
    }
}

与工厂方法不同的是,具体的工厂实现是以产品族划分的,而不是以某一类产品划分的,这样知道了某一个工厂,就能生成该工厂能生产的一族产品。

缺点

当需要添加新的产品时,如汉堡,所有的工厂包括工厂接口都需要更新。

使用场景

​编辑

 输入法换皮肤,一换一整套,各种图标都属于一个工厂类。

模式扩展

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

package mode;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;

public class Client {
    public static void main(String[] args) {
        Coffee coffee = CoffeeStore.orderCoffee("latte");
        System.out.println(coffee.getName());
    }
}
class CoffeeStore{
    public static Coffee orderCoffee(String type){
        Coffee coffee = SimpleFactory.createCoffee(type);
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

class SimpleFactory{
    //加载配置文件,获取配置文件中配置的全类名,创建类并储存
    //1.定义容器对象
    private static HashMap<String,Coffee> map = new HashMap<>();
    //2.加载配置文件
    static {
        Properties properties = new Properties();
        //配置文件读入
        InputStream is = SimpleFactory.class.getClassLoader().getResourceAsStream("mode/bean.properties");
        try {
            //加载对应的配置文件
            properties.load(is);
            //遍历配置文件的内容
            Set<Object> keys = properties.keySet();//获取配置文件的所有键名
            for(Object key:keys){
                String className = properties.getProperty((String) key);//获取配置文件值
                Class<?> aClass = Class.forName(className);//获取字节码对象
                Coffee coffee = (Coffee) aClass.newInstance();//创建对象
                map.put((String) key,coffee);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Coffee createCoffee(String name){
        return map.get(name);
    }
}


abstract class Coffee{
    public abstract String getName();

    public void addSugar(){
        System.out.println("加糖");
    }

    public void addMilk(){
        System.out.println("加奶");
    }
}

class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}

class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

american=mode.AmericanCoffee
latte=mode.LatteCoffee

解除了工厂和商品的耦合,当需要添加商品时,在配置文件中添加即可。

创建在静态代码块中的目的就是加载时只执行一次。不需要每次创建类时就创建。

总结

 简单工厂解除业务和产品之间的耦合但有了产品和工厂的耦合,增加产品时不需要修改业务代码,但需要修改工厂类的代码。

工厂方法将一个具体工厂对应一个产品,用接口的多态替代了工厂中生产不同产品的逻辑判断,解除了工厂和产品的耦合,但容易出现类爆炸,同时工厂只能生成同一级别的产品(只有一个工厂接口的情况下)

抽象工厂将工厂的分类以产品族划分,一家工厂可以生成某个风格的系列产品,但增加新产品时,需要修改所有工厂的代码。

源码

工厂方法模式

 ​编辑

产品抽象类  it  =  工厂实现类.产品实现类

 ​编辑

 工厂方法和简单工厂是获取实现同一产品接口的类

抽象工厂可以获取实现不同接口的产品的类