工厂模式详解

内容定位

不用设计模式并非不可以,但是用好设计模式能帮助我们更好的解决实际问题,设计模式最重要的是解耦。设计模式天天都在用,但自己却无感知。学习设计模式,主要是学习设计模式是如何总结经验的,把经验为自己所用。学习设计模式也是锻炼将业务需求转换技术实现的一种非常有效的方式。总之,学习设计模式,不仅可以让我们写出更优雅的代码,还能更好的重构项目。现在,我们先来了解下工厂模式。

简单工厂

简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类的实例,但他不是属于GOF23种设计模式。简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建的逻辑不需要关心。
接下来我们来看代码,公司有两台打印机,分别是惠普打印机和华硕打印机,现在要用代码来控制打印,首先我们创建一个Iprinter接口:

public interface IPrinter {
    void print();
}

接着是惠普打印机类HPPrinter和华硕打印机类HSPrinter
HPPrinter:

public class HPPrinter implements IPrinter {

    @Override
    public void print() {
        System.out.println("惠普打印机打印");
    }
}

HSPrinter:

public class HSPrinter implements IPrinter {
    @Override
    public void print() {
        System.out.println("华硕打印机打印");
    }
}

应用层代码:

public class Test {
    public static void main(String[] args) {
        IPrinter hpPrinter = new HPPrinter();
        hpPrinter.print();
        IPrinter hsPrinter = new HSPrinter();
        hsPrinter.print();

    }
}

来看下类图:
在这里插入图片描述
看代码,父类Iprinter指向子类HPPrinter和HSPrinter的引用,应用层代码需要依赖HPPrinter和HSPrinter,如果公司继续采购更多品牌打印机,那么我们客户端会变得越来越臃肿。因此,我们要想办法把这种依赖减弱,把创建细节隐藏。虽然目前的代码中,我们创建对的过程并不复杂,但从代码设计角度来讲不易于扩展。现在,我们用简单工厂模式对代码进行优化。
创建一个PrintFactory工厂类:

public class PrintFactory {
    public IPrinter create(String name){
        if(name.equals("HPPrinter")){
            return new HPPrinter();
        } else if(name.equals("HSPrinter")){
            return  new HSPrinter();
        }else {
            return null;
        }
    }
}

修改客户端调用代码:

public class Test {
    public static void main(String[] args) {
        PrintFactory printFactory = new PrintFactory();
        IPrinter hsPrinter = printFactory.create("HSPrinter");
        hsPrinter.print();
        IPrinter hpPrinter = printFactory.create("HPPrinter");
        hpPrinter.print();
    }
}

当然,我们为了调用方便,可将PrintFactory的create()方法改为静态方法,下面来看下类图:
在这里插入图片描述
这样,客户端调用是简单了,但是如果公司继续购买新的打印机,那么工厂中的create()就要根据产品链的丰富每次都要修改代码逻辑。不符合开闭原则。因此,我们对简单工厂还可以继续优化,可以采用反射技术:

public class PrintFactory {
    public IPrinter create(String className){
        try{
            if(!(null==className || "".equals(className))){
                return (IPrinter) Class.forName(className).newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

修改客户端调用代码:

public class Test {
    public static void main(String[] args) {
        PrintFactory printFactory = new PrintFactory();
        IPrinter hsPrinter = printFactory.create("org.koro.factoryPatten.simpleFactory.v3.HPPrinter");
        hsPrinter.print();
        IPrinter hpPrinter = printFactory.create("org.koro.factoryPatten.simpleFactory.v3.HPPrinter");
        hpPrinter.print();
    }
}

优化之后,即使公司再继续购买多少品牌打印机,都不需要在修改PrintFactory中的代码。但是,有个问题,方法参数是字符串,可控性有待提升,而且还需要强制转型。我们在修改下代码:

public class PrintFactory {
    public IPrinter create(Class<? extends IPrinter> clazz){
        try{
            if(null!=clazz){
                return clazz.newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

优化客户端代码:

public class Test {
    public static void main(String[] args) {
        PrintFactory printFactory = new PrintFactory();
        IPrinter hsPrinter = printFactory.create(HPPrinter.class);
        hsPrinter.print();
        IPrinter hpPrinter = printFactory.create(HSPrinter.class);
        hpPrinter.print();
    }
}

再看下类图:
在这里插入图片描述
简单工厂模式再JDK源码也是无处不在,现在我们来举个例子,例如Calendar类,看Calendar.getInstance()方法,下面是Calendar的具体创建类:

public static Calendar getInstance(TimeZone zone,
                                   Locale aLocale)
{
    return createCalendar(zone, aLocale);
}

private static Calendar createCalendar(TimeZone zone,
                                       Locale aLocale)
{
    CalendarProvider provider =
        LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                             .getCalendarProvider();
    if (provider != null) {
        try {
            return provider.getInstance(zone, aLocale);
        } catch (IllegalArgumentException iae) {
            // fall back to the default instantiation
        }
    }

    Calendar cal = null;

    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype != null) {
            switch (caltype) {
            case "buddhist":
            cal = new BuddhistCalendar(zone, aLocale);
                break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone, aLocale);
                break;
            case "gregory":
                cal = new GregorianCalendar(zone, aLocale);
                break;
            }
        }
    }
    if (cal == null) {
        // If no known calendar type is explicitly specified,
        // perform the traditional way to create a Calendar:
        // create a BuddhistCalendar for th_TH locale,
        // a JapaneseImperialCalendar for ja_JP_JP locale, or
        // a GregorianCalendar for any other locales.
        // NOTE: The language, country and variant strings are interned.
        if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
            cal = new BuddhistCalendar(zone, aLocale);
        } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                   && aLocale.getCountry() == "JP") {
            cal = new JapaneseImperialCalendar(zone, aLocale);
        } else {
            cal = new GregorianCalendar(zone, aLocale);
        }
    }
    return cal;
}

还有一个大家经常使用的logback,我们看到LoggerFactory中有多个重载的方法getLogger().
简单工厂也有它的缺点:工厂类的职责相对过重,不易于扩展过于复杂的产品结构。

工厂方法模式

工厂方法模式(Factory Method Pattern)是指定义一个创建对象的接口,但让实现这个接口的类决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加如新的产品符合开闭原则。
工厂方法主要解决产品扩展的问题,在简单工厂中,随着产品链的丰富,如果每个打印机的创建逻辑有区别的话,工厂的职责会越来越多,有点像万能工厂,并不便维护。根据单一职责原则,我们将职能拆分,专人干专事。接下来我们来看个例子,还是上面打印机的例子,不过惠普打印机为了要与其他打印机区别,在创建对象时,要接收个入参,当前的系统时间。这时,我们就不能再用简单工厂了。我们将创建惠普打印机对象和华硕打印机对象分离开来,惠普打印机由惠普工厂创建,华硕打印机由华硕工厂创建,对工厂本身也做一个抽象。
我们把HPPrinter的代码进行修改:

public class HPPrinter implements IPrinter {

    private Date time;

    public HPPrinter(Date time){
        this.time = time;
        System.out.println("当前时间:"+time);
    }

    public void setTime(Date time){
        this.time = time;
    }

    public Date getTime(){
        return time;
    }

    @Override
    public void print() {
        System.out.println("惠普打印机打印");
    }
}

接着给工厂本身也做个抽象IPrinterFactory:

public interface IPrinterFactory {
    IPrinter create();
}

然后创建HPPrinterFactory和HSPrinterFactory
HPPrinterFactory:

public class HPPrinterFactory implements IPrinterFactory {
    @Override
    public IPrinter create() {
        return new HPPrinter(new Date());
    }
}

HSPrinterFacotry:

public class HSPriterFactory implements IPrinterFactory {
    @Override
    public IPrinter create() {
        return new HSPrinter();
    }
}

看测试代码:

public class Test {
    public static void main(String[] args) {
        IPrinterFactory hpfactory = new HPPrinterFactory();
        hpfactory.create().print();
        IPrinterFactory hsfactory = new HSPriterFactory();
        hsfactory.create().print();

    }
}

现在再来看下啊类图:
在这里插入图片描述
工厂方法适用于一下场景:

  1. 创建对象需要大量重复的代码。
  2. 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
  3. 一个类通过其子类来指定创建哪个对象。
    工厂方法也有缺点:
    1、 类的个数容易过多,增加复杂度。
    2、 增加了系统的抽象性和理解难度。

抽象厂模式

抽象工厂模式(Abastract Factory Patten)是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。客户端(应用层)不依赖于产品类实例如何被创建、实现等细节,强调的是一系列相关的产品对象(属于通一产品族)一起使用创建对象需要大量重复的代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
简介抽象工厂之前,我们要了解两个概念,产品族和产品等级结构,看下面的图:
在这里插入图片描述
从上面图中看出有正方形,圆形和菱形三种图形,相同颜色深浅的就代表同一个产品族,相同形状的代表同一个产品等级结构。同样可以从生活中来举例,比如,美的电器生产多种家用电器。那么上图中,颜色最深的正方形就代表美的的洗衣机,颜色最深的圆形代表美的空调、颜色最深的菱形代表美的的热水器,颜色最深的一排都属于美的的品牌,都是美的电器这个产品族。再看最右侧的菱形,颜色最深的我们指定了代表美的热水器,那么第二排颜色稍微浅点的菱形,代表海信的热水器。同理,同一个产品结构下还有格力热水器,格力空调,格力洗衣机。
再看下面的这张图,最左侧的小房子我们就认为是具体的工厂,有美的工厂,有海信工厂,有格力工厂。每个品牌的工厂都生产洗衣机、热水器和空调。
在这里插入图片描述
通过上面两张图的对比理解,相信大家对抽象工厂有了非常形象的理解。接下来我们来看一个具体的业务场景而且用代码来实现。还是以打印机为例,打印机不仅有打印的功能,还有扫描的功能。每一个品牌的打印机就是一个产品族,而打印和扫描就是这个产品族的产品等级:
IPrint接口:

public interface IPrint {
    void print();
}

IScan接口:

public interface IScan {
    void scan();
}

然后创建一个抽象工厂PrinterFactory类,这个类可以是接口,也可以是抽象类,具体看再创建对象时,有没有共同的操作,现在我们先用抽象类,每次创建时,都要进行开机操作:

public abstract class PrinterFactory {
    public void init(){
        System.out.println("开机");
    }

    protected abstract IPrint createPrint();

    protected abstract IScan createScan();
}

接下来创建惠普打印机的产品族,惠普打印机打印HPPrinterPrint类:

public class HPPrinterPrint implements IPrint {
    @Override
    public void print() {
        System.out.println("惠普打印机打印");
    }
}

惠普打印机扫描HPPrinterScan类:

public class HPPrinterScan implements IScan {
    @Override
    public void scan() {
        System.out.println("惠普打印机扫描");
    }
}

然后时惠普打印机具体的工厂HPPrinterFactory:

public class HPPrinterFactory extends PrinterFactory {
    @Override
    public IPrint createPrint() {
        super.init();
        return new HPPrinterPrint();
    }

    @Override
    public IScan createScan() {
        super.init();
        return new HPPrinterScan();
    }
}

现在创建华硕打印机产品族,华硕打印机打印HSPrinterPrint:

public class HSPrinterPrint implements IPrint {
    @Override
    public void print() {
        System.out.println("华硕打印机打印");
    }
}

华硕打印机扫描HSPrintScan:

public class HSPrinterScan implements IScan {
    @Override
    public void scan() {
        System.out.println("华硕打印机扫描");
    }
}

接着时华硕打印机的具体工厂HSPrinterFactory:

public class HSPrinterFactory extends PrinterFactory {
    @Override
    public IPrint createPrint() {
        super.init();
        return new HSPrinterPrint();
    }

    @Override
    protected IScan createScan() {
        super.init();
        return new HSPrinterScan();
    }
}

来看客户端调用:

public class Test {
    public static void main(String[] args) {
        PrinterFactory hpPrinter = new HPPrinterFactory();
        hpPrinter.createPrint().print();
        hpPrinter.createScan().scan();

        PrinterFactory hsPrinter = new HSPrinterFactory();
        hsPrinter.createScan().scan();
        hsPrinter.createPrint().print();
    }
}

我们来看下类图:
在这里插入图片描述
上面的代码完整的描述了两个产品族惠普打印机和华硕打印机,也描述了两个产品等级打印和扫描。抽象工厂非常完美清晰的描述这样一层复杂的关系。但是,不知道大家有没有发现,如果我们再继续扩展产品等级,将复印也加入到产品族中,那么我们的代码从抽象工厂到具体工厂都要全部调整,很显然不符合开闭原则。因此抽象工厂也是由缺点的:
1、 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
2、 增加了系统的抽象性和理解难度。
但在实际应用中,我们千万不能犯强迫症甚至有洁癖。在实际需求中产品等级结构升级时非常正常的一件事情。我们可以根据实际情况,只要不是频繁升级,可以不遵循开闭原则。
最后简单总结:简单工厂是产品的工厂、工厂方法模式是工厂的工厂、抽象工厂是复杂产品的工厂

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值