Java设计模式(5:设计模式的分类及工厂模式详解)

一、设计模式的分类

总的来说,设计模式可以分为三大类:创建型模式结构型模式行为型模式,具体如下图:

 

二、工厂模式

工厂模式分为简单工厂模式工厂方法模式抽象工厂模式。其中简单工厂模式并不属于23种设计模式,但并不影响它的广泛使用。在JDK的源码当中,就存在着许多这样的例子。

2.1 简单工厂模式

我们先来看一段代码:

public static void main(String[] args) {
    // 日历类
    Calendar calendar = Calendar.getInstance();
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println("当前时间为:" + simpleDateFormat.format(calendar.getTime()));
    calendar.add(Calendar.HOUR,2);
    System.out.println("当前时间加了两个小时后,时间是: " + simpleDateFormat.format(calendar.getTime()));
}

这段代码,大家应该比较熟悉,通过对Calendar的一系列操作,打印出当前时间和当前时间加两个小时后的时间,这里我们来看看结果:

 

结果正和我们想象的一样,两次打印出来的时间相隔两个小时。但我们今天的重点是Calendar calendar = Calendar.getInstance()这段代码,通过getInstance()方法拿到了Calendar类的实例。来看看具体的源代码:

public static Calendar getInstance(){
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
  }

//  代码不全,有兴趣的朋友可以去看JDK源码
 private static Calendar createCalendar(TimeZone zone, Locale aLocale){
        // 中间的代码省略.....
        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;
                }
            }
        }
        // 中间的代码省略.....
        return cal;
 }

可以看出,getInstance()方法里面调用了createCalendar()方法来得到Calendar类的实例,最后返回给调用者。而createCalendar()方法中通过switch(){case}的判断来返回所对应的Calendar类的实例,这其实就是简单工厂模式的一种应用。

看完简单工厂模式JDK中的应用之后,我们来设计一下自己的例子:

小明家新开了一家小工厂,接了一单生意,帮助海尔(Haier)集团生产冰箱,并需要设计相应的方案。小明本身也是程序员出身,思考一会后就写出了下面的代码:

/**
 * 冰箱
 */
public interface IFridge {

    // 生产冰箱
    public void createFridge();

}
/**
 * 海尔
 */
public class Haier implements IFridge {
    @Override
    public void createFridge() {
        System.out.println("生产海尔冰箱...");
    }
}

客户端调用代码:

public static void main(String[] args) {
    IFridge iFridge = new Haier();
    iFridge.createFridge();
}

看上面的代码,父类IFridge类指向子类Haier类的引用,应用层需要依赖于Haier。如果业务扩展,后续增加格力(Gree)甚至更多,那么客户端这里的代码会越来越臃肿。所以,我们要想办法将这种依赖减弱,将创建IFridge对象的细节隐藏掉。我们用简单工厂模式优化一下:

创建Gree格力类

/**
 * 格力
 */
public class Gree implements IFridge {
    @Override
    public void createFridge() {
        System.out.println("生产格力冰箱...");
    }
}

创建FridgeFactory工厂类

/**
 * 冰箱工厂
 */
public class FridgeFactory {

    // 创建对应的 IFridge 实例
    public static IFridge createFridge(String name){
        if ("haier".equals(name)){
            return new Haier();
        } else if ("gree".equals(name)){
            return new Gree();
        }
        return null;
    }
}

修改客户端调用的代码:

public static void main(String[] args) {
    // 海尔
    IFridge haier = FridgeFactory.createFridge("haier");
    haier.createFridge();

    // 格力
    IFridge gree = FridgeFactory.createFridge("gree");
    gree.createFridge();
}

这样来看,虽然代码多了,但维护起来以及扩展起来就方便很多,来看一看类图:

 

当然,上面的FridgeFactory代码中依旧有些问题,如果我们需要增加生产美的(Midea)冰箱,那么我们就需要去修改createFridge()方法的代码,显然违背了开闭原则,我们来改造一下:

修改FridgeFactory工厂类

/**
 * 冰箱工厂
 */
public class FridgeFactory {

    // 创建对应的 IFridge 实例
    public static IFridge createFridge(String className){
        try {
            if (null != className && !"".equals(className)){
                // 反射
                return (IFridge)Class.forName(className).newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

修改客户端调用的代码

public static void main(String[] args) {
    // com.xxx.Haier  换成  自己 项目中 Haier 所在的位置  海尔
    IFridge haier = FridgeFactory.createFridge("com.xxx.Haier");
    haier.createFridge();

    // com.xxx.Gree  换成  自己 项目中 Gree 所在的位置  格力
    IFridge gree = FridgeFactory.createFridge("com.xxx.Gree");
    gree.createFridge();
}

优化之后,我们再也不需要随着业务的提升而去修改FridgeFactory类中的代码了。但是依旧有一个问题,createFridge()方法中的参数是字符串,如果有人乱填怎么办,那不就报错了,所以再来优化一下:

修改FridgeFactory工厂类

/**
 * 冰箱工厂
 */
public class FridgeFactory {

    // 创建对应的 IFridge 实例
    public static IFridge createFridge(Class<? extends IFridge> clazz){
        try {
            if (clazz != null){
                return clazz.newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

修改客户端调用的代码

public static void main(String[] args) {
    // 海尔
    FridgeFactory.createFridge(Haier.class).createFridge();

    // 格力
    FridgeFactory.createFridge(Gree.class).createFridge();
}

再来看一下类图:

 

简单工厂模式虽然好用,但也有它的局限性:工厂类的职责过重,不利于扩展更为复杂产品结构。

2.2 工厂方法模式

定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。

在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符合开闭原则

随着小明家新工厂的生意火爆,各类的订单都纷涌而至,各个牌子的厂家都想让小明家的工厂生产冰箱,小明无奈只能开了分工厂,并根据客户的品牌名给工厂取了对应的名字,其中海尔工厂生产海尔的冰箱,格力工厂生产格力的冰箱,美的工厂生产美的的冰箱。用代码演化就是下面这般:

IFridgeFactory类接口

public interface IFridgeFactory {
    public IFridge createIFridge();
}

海尔

// 海尔  工厂
public class HaierFactory implements IFridgeFactory {
    @Override
    public IFridge createIFridge() {
        return new Haier();
    }
}

格力

// 格力  工厂
public class GreeFactory implements IFridgeFactory {
    @Override
    public IFridge createIFridge() {
        return new Gree();
    }
}

美的

/**
 * 美的
 */
public class Midea implements IFridge {
    @Override
    public void createFridge() {
        System.out.println("生产美的冰箱...");
    }
}
// 美的
public class MideaFactory implements IFridgeFactory {
    @Override
    public IFridge createIFridge() {
        return new Midea();
    }
}

客户端调用:

public static void main(String[] args) {
    // 格力
    new GreeFactory().createIFridge().createFridge();

    // 海尔
    new HaierFactory().createIFridge().createFridge();

    // 美的
    new MideaFactory().createIFridge().createFridge();
}

这里其实就是细化了工厂,将业务拆分,利用了设计模式原则中的单一职责原则,让每个品牌对应工厂只干一件事,不去掺和其他品牌的事情。来看一看类图:

 

工厂方法模式适用于一下场景:

  • 创建对象需要大量重复的代码
  • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
  • 一个类通过其子类来指定创建哪个对象

工厂方法模式也有缺点:

  • 类的个数容易过多,增加复杂度
  • 增加了系统的抽象性和理解难度

2.3 抽象工厂模式

定义:提供一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类。

这个定义读起来相当的拗口,很抽象,不好理解。还是和上面的例子结合来说明:

在生产完一批冰箱并上市售卖之后,美的、格力、海尔等公司非常满意,慢慢的将自己家的空调、热水器也交给小明家的工厂去生产了。小明为此在对应的品牌工厂有开辟了对应的生产设备的空间(这里为了大家看的方便,我将所有的代码都放上去):

冰箱、空调、热水器接口

// 冰箱
public interface IFridge {
    // 生产冰箱
    public void createFridge();
}

// 空调
public interface IAirConditioner {
    // 生产空调
    public void createAirConditioner();
}

// 热水器
public interface IWaterHeater {
    // 生产热水器
    public void createWaterHeater();
}

海尔

/**
 * 海尔 冰箱
 */
public class HaierFridge implements IFridge{
    @Override
    public void createFridge() {
        System.out.println("生产海尔冰箱...");
    }
}

// 海尔 空调
public class HaierAirConditioner implements IAirConditioner {
    @Override
    public void createAirConditioner() {
        System.out.println("生产海尔空调...");
    }
}

// 海尔热水器
public class HaierWaterHeater implements IWaterHeater {

    @Override
    public void createWaterHeater() {
        System.out.println("生产海尔热水器...");
    }
}

格力

/**
 * 格力 冰箱
 */
public class GreeFridge implements IFridge {
    @Override
    public void createFridge() {
        System.out.println("生产格力冰箱...");
    }
}

// 格力 空调
public class GreeAirConditioner implements IAirConditioner {
    @Override
    public void createAirConditioner() {
        System.out.println("生产格力空调...");
    }
}

// 格力热水器
public class GreeWaterHeater implements IWaterHeater {
    @Override
    public void createWaterHeater() {
        System.out.println("生产格力热水器...");
    }
}

美的

/**
 * 美的 冰箱
 */
public class MideaFridge implements IFridge{
    @Override
    public void createFridge() {
        System.out.println("生产美的冰箱...");
    }
}

// 美的 空调
public class MideaAirConditioner implements IAirConditioner {
    @Override
    public void createAirConditioner() {
        System.out.println("生产美的空调...");
    }
}

// 美的热水器
public class MideaWaterHeater implements IWaterHeater {
    @Override
    public void createWaterHeater() {
        System.out.println("生产美的热水器...");
    }
}

工厂接口

12345678public interface IFactory {
	// 冰箱
    public IFridge createIFridge();
	// 空调
    public IAirConditioner createIConditioner();
	// 热水器
    public IWaterHeater createIWaterHeater();
}

海尔工厂

// 海尔  工厂
public class HaierFactory implements IFactory {
    // 冰箱
    @Override
    public IFridge createIFridge() {
        return new HaierFridge();
    }

    // 空调
    @Override
    public IAirConditioner createIConditioner() {
        return new HaierAirConditioner();
    }

    // 热水器
    @Override
    public IWaterHeater createIWaterHeater() {
        return new HaierWaterHeater();
    }
}

格力工厂

// 格力
public class GreeFactory implements IFactory {
    // 冰箱
    @Override
    public IFridge createIFridge() {
        return new GreeFridge();
    }
	// 空调
    @Override
    public IAirConditioner createIConditioner() {
        return new GreeAirConditioner();
    }
	// 热水器
    @Override
    public IWaterHeater createIWaterHeater() {
        return new GreeWaterHeater();
    }
}

美的工厂

public class MideaFactory implements IFactory {
    // 冰箱
    @Override
    public IFridge createIFridge() {
        return new MideaFridge();
    }
	// 空调
    @Override
    public IAirConditioner createIConditioner() {
        return new MideaAirConditioner();
    }
    // 热水器
    @Override
    public IWaterHeater createIWaterHeater() {
        return new MideaWaterHeater();
    }
}

客户端调用

public static void main(String[] args) {
	// 海尔工厂
    HaierFactory haierFactory = new HaierFactory();
    haierFactory.createIFridge().createFridge();
    haierFactory.createIConditioner().createAirConditioner();
    haierFactory.createIWaterHeater().createWaterHeater();

	// 格力工厂
    GreeFactory greeFactory = new GreeFactory();
    greeFactory.createIFridge().createFridge();
    greeFactory.createIConditioner().createAirConditioner();
    greeFactory.createIWaterHeater().createWaterHeater();

    // 美的工厂
    MideaFactory mideaFactory = new MideaFactory();
    mideaFactory.createIFridge().createFridge();
    mideaFactory.createIConditioner().createAirConditioner();
    mideaFactory.createIWaterHeater().createWaterHeater();
}

类图

 

从上面一大堆的代码,尤其是类图,我们可以很明显的感觉到,抽象工厂可以完美清晰的描述海尔、格力、美的三个品牌的冰箱、空调、热水器的庞大体系。但也正因为如此,抽象工厂给我们的视觉冲击有些大,能很明显的感觉到系统的复杂性抽象性以及系统的极难扩展性;并且这里还隐藏着一个违背开闭原则的问题:

在工厂接口IFactory类中,如果在日后的产品升级当中,需要增加生产洗衣机的业务,那这里修改之后,所有实现IFactory接口的类都需要变动,很大程度增加了系统的不稳定性。

也正因为如此,在实际的业务开发中,我们不应该有着强烈的强迫症和洁癖,认为一个系统的结构设计必须要完美的符合各种原则。要结合实际的业务去思考,如果系统结构的等级更新不频繁的话,不遵守某些原则也是有必要性的,毕竟所有的技术都是为业务而服务的

作者:蔡小明

链接:https://www.cnblogs.com/caimm/p/14899740.html

全部评论

相关推荐

1.&nbsp;自我介绍2.&nbsp;项目都是自己写的吗?3.&nbsp;我看你用&nbsp;koa2&nbsp;写后端,为什么选择它,能讲讲吗?4.&nbsp;那你提到&nbsp;koa2&nbsp;它是不提供中间件的,你是怎么解决的?5.&nbsp;中间件的原理是什么?(洋葱模型)6.&nbsp;你刚刚说碰到&nbsp;next()&nbsp;就进入下一个中间件,那&nbsp;next&nbsp;只能执行同步,如果是异步的话,你是怎么处理的?(async/await,但是我发现,有的中间件需要在异步中间件之前执行,所以我用&nbsp;try/catch&nbsp;来处理异步中间件的异常)7.&nbsp;JS&nbsp;异步发展史,以及它们的优缺点说一下&nbsp;(回调函数--Promise--Generator--async/await)8.&nbsp;你刚刚说&nbsp;Promise&nbsp;状态不能更改,那如果我要设计一个能修改&nbsp;Promise&nbsp;状态的函数,你会怎么设计?9.&nbsp;CSS&nbsp;水平垂直居中的方法(flex、grid、绝对定位&nbsp;+&nbsp;margin:auto、绝对定位&nbsp;+&nbsp;负&nbsp;margin、绝对定位&nbsp;+&nbsp;transform、table-cell)10.&nbsp;你刚刚说到&nbsp;flex&nbsp;布局,那&nbsp;flex:1&nbsp;是什么意思?(flex:&nbsp;flex-grow&nbsp;&nbsp;flex-shrink&nbsp;&nbsp;flex-basis;等价&nbsp;flex:1&nbsp;1&nbsp;0%表示元素可以均分剩余空间,可拉伸、可压缩,不依赖内容宽度,自动自适应填充布局。)11.&nbsp;父容器宽是&nbsp;500px,然后它左右各有两个子容器是&nbsp;100px,如果设置&nbsp;flex:&nbsp;1,那它的宽度是多少?(500-100-100=300px)12.&nbsp;说说你对浏览器缓存的理解(强缓存、协商缓存)13.&nbsp;如果一个用户,他怎么去刷新都无法刷到最新版的代码,你能说下可能的原因吗?(版本号、hash等)还有吗?(我说我不知道了,面试官说还有&nbsp;CDN&nbsp;没有同步,我说企业才会这么干,自己写项目一般不会,我知道&nbsp;cdn&nbsp;是用来解决高并发的手段)14.&nbsp;React你熟吗?说下&nbsp;React&nbsp;函数组件和类组件的区别15.&nbsp;怎么避免&nbsp;Hooks&nbsp;导致组件重新渲染?(使用&nbsp;useCallback、useMemo、React.memo、useRef等等)16.&nbsp;谈一下我对&nbsp;React&nbsp;的状态管理的理解(Redux、Mobx、Zustand,我说&nbsp;Zustand&nbsp;用的最多)17.&nbsp;React&nbsp;常见的&nbsp;hooks&nbsp;有哪些?(useState、useEffect、useRef、useCallback、useMemo、useReducer、useContext、useImperativeHandle、useLayoutEffect、useDebugValue)18.&nbsp;TS&nbsp;你熟吗?我们引进&nbsp;TS&nbsp;的目的是为什么?19.&nbsp;interface&nbsp;和&nbsp;type&nbsp;的区别20.&nbsp;说下&nbsp;TS&nbsp;里的泛型21.&nbsp;我现在有十个字段,比如十个字段就要&nbsp;A&nbsp;B&nbsp;C&nbsp;D&nbsp;E&nbsp;F&nbsp;G&nbsp;这种。那我现在另有另外一个方法,这个方法接受的参数呢,必须是这个&nbsp;interface&nbsp;A&nbsp;里面的这个&nbsp;K。就比如说你可以是&nbsp;A&nbsp;B&nbsp;C&nbsp;可以&nbsp;A&nbsp;B&nbsp;C&nbsp;D&nbsp;任何组合都可以,但是必须是这个&nbsp;interface&nbsp;里面的&nbsp;A&nbsp;里面的定义的。这个&nbsp;K&nbsp;这种类型的话是怎么去定义呢?(说实话我有点不太理解啥意思,反正我说了&nbsp;keyof)```&nbsp;TypeScriptinterface&nbsp;Obj&nbsp;{A:&nbsp;stringB:&nbsp;stringC:&nbsp;stringD:&nbsp;stringE:&nbsp;string//&nbsp;其他字段...}```22.&nbsp;vite&nbsp;用过吗?说说和&nbsp;webpack&nbsp;的区别。vite&nbsp;的优缺点是什么23.&nbsp;说说&nbsp;Tree&nbsp;shaking(树摇)&nbsp;和&nbsp;Code&nbsp;Splitting&nbsp;(代码分割)的区别24.&nbsp;Git&nbsp;你熟吗?说说&nbsp;git&nbsp;merge&nbsp;和&nbsp;git&nbsp;rebase&nbsp;的区别,什么时候用&nbsp;git&nbsp;merge,什么时候用&nbsp;git&nbsp;rebase?25.&nbsp;web3&nbsp;你熟吗?(不太熟,听说过而已)26.&nbsp;我看你自我介绍说了&nbsp;AI,你是怎么用的?27.&nbsp;除了提示词,还有什么能让&nbsp;AI&nbsp;更聪明?28.&nbsp;AI&nbsp;的优缺点你说一下29.&nbsp;AI&nbsp;发展这么快,你觉得我们以后会扮演什么角色?30.&nbsp;反问基本都答上来了。面了我80分钟,我还以为稳过的
查看29道真题和解析
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务