工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
一、简单工厂模式
1.1、定义
简单工厂模式(Simple Factory Pattern)属于类的创新型模式,又叫静态工厂方法模式(Static FactoryMethod Pattern),定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
1.2、简单工厂模式的UML图
简单工厂模式中包含的角色及其相应的职责如下:
工厂角色(Creator):这是简单工厂模式的核心,由它负责创建所有的类的内部逻辑。当然工厂类必须能够被外界调用,创建所需要的产品对象。
抽象(Product)产品角色:简单工厂模式所创建的所有对象的父类,注意,这里的父类可以是接口也可以是抽象类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:简单工厂所创建的具体实例对象,这些具体的产品往往都拥有共同的父类。
下面截图来自工厂模式的菜鸟教程,下图详细的说明了工厂模式的UML结构
1.3、场景案例
拿请客吃饭来说,今天要请朋友吃饭,但今天人懒了,不想自己做,所以决定出去吃;那么这时候,餐厅就是一个工厂了,这时候我直接在工厂中选择各种菜品就行了;
//产品的抽象接口 public interface Cuisine { //菜单 void menu(); }
//具体产品 public class Greens implements Cuisine{ @Override public void menu() { System.out.println("点了个青菜"); } }
//具体产品 public class Meat implements Cuisine{ @Override public void menu() { System.out.println("点个盘肉"); } }
没建工厂前的调用方式;看起来好像没有什么问题,但是当我们点的菜多时,调用端不就要写大量代码了
public class Invoking { public static void main(String[] args) { //点了个青菜 Cuisine cuisine=new Greens(); cuisine.menu(); //点了个肉 Cuisine cuisine1=new Greens(); cuisine1.menu(); } }
建立工厂进行优化;
public class SimpleFactory { public Cuisine add(String type){ if (type.equalsIgnoreCase("Greens")) { return new Greens(); } else if (type.equalsIgnoreCase("Meat")) { return new Meat(); } else { System.out.println("没点这个菜"); return null; } } }
然后再次调用;这样后面就算点再多菜,我调用端代码不用再向前面那样大量更改了,只用改变传参就行;代码简洁了很多;但这里又引出了一个新问题;那就是如果具体产品(点的菜)过多,那么工厂类就要改动;这样的代码不是我们想要的;
public class Invoking { public static void main(String[] args) { // //点了个青菜 // Cuisine cuisine=new Greens(); // cuisine.menu(); // //点了个肉 // Cuisine cuisine1=new Greens(); // cuisine1.menu(); SimpleFactory simpleFactory=new SimpleFactory(); Cuisine cuisine=simpleFactory.add("Greens"); cuisine.menu(); } }
那么接下来对工厂类进行修改;
public class SimpleFactory { // public Cuisine add(String type){ // if (type.equalsIgnoreCase("Greens")) { // return new Greens(); // } else if (type.equalsIgnoreCase("Meat")) { // return new Meat(); // } else { // System.out.println("没点这个菜"); // return null; // } // } public Cuisine add(Class<? extends Cuisine> clazz){ if (null!=clazz){ try{ return clazz.newInstance(); }catch (Exception e){ e.printStackTrace(); } } return null; } }
再次调用;这样只是用了反射技术就解决了,代码的扩展性更加完美了
public class Invoking { public static void main(String[] args) { // //点了个青菜 // Cuisine cuisine=new Greens(); // cuisine.menu(); // //点了个肉 // Cuisine cuisine1=new Greens(); // cuisine1.menu(); SimpleFactory simpleFactory=new SimpleFactory(); Cuisine cuisine=simpleFactory.add(Greens.class); cuisine.menu(); } }
下面看下类图
1.4、简单工厂模式在源码中的应用
在JDK源码中 ,java.util.Calendar使用了工厂模式的简单工厂模式;Calendar cal=Calendar.getInstance()
public static Calendar getInstance() { //用了简单工厂,这一步就是在调用工厂 return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT)); }
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; }
是不是发现上面的结构和我写的例子原理很像,其实,每一种设计模式都有自己模板可以复用,只要理解了核心思想就可以灵活运用;上面源码的具体产品类如果大家有兴趣的可以点进去看下,会发现具体产品类都有一个共同的接口或者抽象类
1.5、简单工厂模式的优点
简单工厂模式的结构简单,调用方便。对于外界给定的信息,可以很方便的创建出相应的产品,工厂和产品的职责十分明确。
1.6、简单工厂模式的缺點
简单工厂模式的工厂类单一,负责所有产品的创建,但当产品类型增多时,这个工厂类代码会非常臃肿,违背了高聚合原则;
二、工厂方法模式
2.1、定义
2.2、模式简介
2.3、场景案例
还是拿上面吃饭的例子来说,本来计划是出门去餐厅吃饭的,但是大家都太懒,现在连门都不想出了,那饭总要吃吧,那怎么整,点外卖吧。这时有的朋友想吃湘菜,有的朋友想吃川菜;首先得有个抽象产品角色
//不管是湘菜还是川菜,他们都来自餐厅,可以抽象出来 public interface Food { public void canteen(String type); }
然后需要拿到具体产品,即菜品
//具体产品(湘菜) public class HunanCuisine implements Food{ @Override public void canteen(String type) { System.out.println("点了湘菜"); } }
//点了川菜 public class SichuanCuisine implements Food{ @Override public void canteen(String type) { System.out.println("点了川菜"); } }
这个时候需要一个抽象工厂角色了,即餐厅
public abstract class Factory { /** * 让子类(具体工厂)来实例化具体对象 */ public abstract Food newFood(); //菜品 public void process(String food){ Food food1=newFood(); food1.canteen(food); } }
然后找两家餐厅下单,分别点两种口味的菜品
//湘菜餐厅 public class HunanCuisineFactory extends Factory{ @Override public Food newFood() { return new HunanCuisine(); } }
//川菜餐厅 public class SichuanCuisineFactory extends Factory{ @Override public Food newFood() { return new SichuanCuisine(); } }
好了,餐厅找好了,菜品也选好了,那下面就是下单了,就是操作工厂进行生成
public class Orders { public static void main(String[] args) { Factory factory=new HunanCuisineFactory(); factory.process("湘菜"); Factory factory1=new SichuanCuisineFactory(); factory1.process("川菜"); } }
2.4、工厂方法模式在源码中的应用
Logback在创建Log时,使用的就是工厂方法。它的各个元素都是在同一个类sun.rmi.runtime的Log.java中用内部类实现,但这并不影响工厂方法的结构。它的抽象产品基类就是Log,其中一个产品实现类为LoggerLog,抽象工厂类为LogFactory,工厂实现类为LoggerLogFactory。另一对是LogStreamLog和LogStreamLogFactory。其中关键代码如下,为了更加易懂,我调整了一下源码顺序。
public abstract class Log { ... ... public abstract void log(Level var1, String var2); private interface LogFactory { Log createLog(String var1, String var2, Level var3); } private static class LoggerLog extends Log { public void log(Level var1, String var2) { if (this.isLoggable(var1)) { String[] var3 = Log.getSource(); this.logger.logp(var1, var3[0], var3[1], Thread.currentThread().getName() + ": " + var2); } } } private static class LoggerLogFactory implements Log.LogFactory { public Log createLog(String var1, String var2, Level var3) { Logger var4 = Logger.getLogger(var1); return new Log.LoggerLog(var4, var3); } } private static class LogStreamLog extends Log { public void log(Level var1, String var2) { if (this.isLoggable(var1)) { String[] var3 = Log.getSource(); this.stream.println(unqualifiedName(var3[0]) + "." + var3[1] + ": " + var2); } } } private static class LogStreamLogFactory implements Log.LogFactory { public Log createLog(String var1, String var2, Level var3) { LogStream var4 = null; if (var2 != null) { var4 = LogStream.log(var2); } return new Log.LogStreamLog(var4, var3); } } }
2.5、工厂方法模式的优点
1.灵活性增强了,对于新产品的创建,只需要多写一个对应的工厂类
2.解耦框架,高层模块只需要知道产品的抽象类,不用关心具体实现,满足了迪米特法则、依赖倒置原则及里氏替换原则
2.6、工厂方法模式的缺点
1.类的个数容易过多,增加复杂程度
2.增加了系统抽象性和理解难度
3.抽象产品只能生产一种产品,这个问题可以在下面的抽象工厂模式中解决
三、抽象工厂模式
3.1、定义
抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。使用抽象工厂模式一般要满足以下条件。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
3.2、模式简介
抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
3.3、场景案例
这个场景还是和前面一样,用请朋友吃饭来说事,前面兄弟们已经点吃了两次外卖了,然后聚会时点外卖上头了,这次聚会兄弟们又懒得出去又是点外卖,张三和李四上次吃过湘菜和川菜后就喜欢上这两菜系了,然后这次点外卖这两货说这两菜系的菜必点,请客的兄弟就说,就你俩事多,这么喜欢吃那你们自己点呗,于是张三和李四自己就默默的在角落自己点起了外卖;
//抽象产品:湘菜 public interface HunanCuisine { void order(); }
//具体产品:辣子鸡 public class PepperyChicken implements HunanCuisine { @Override public void order() { System.out.println ("点了辣子鸡"); } }
//具体产品:鱼香肉丝 public class ShreddedMeat implements HunanCuisine{ @Override public void order() { System.out.println ("点了鱼香肉丝"); } }
//抽象产品:川菜 public interface SichuanCuisine { void order(); }
//具体产品:火锅 public class HotPot implements SichuanCuisine { @Override public void order() { System.out.println ("点了火锅"); } }
//具体产品:回锅肉 public class TwiceCookedPork implements SichuanCuisine{ @Override public void order() { System.out.println ("点了回锅肉"); } }
然后写个抽象工厂
//抽象工厂:餐厅 public interface Canteen { HunanCuisine newHunanCuisine(); SichuanCuisine newSichuanCuisine(); }
具体工厂
//具体工厂:湘里湘亲餐厅 public class XiangInHunan implements Canteen{ @Override public HunanCuisine newHunanCuisine() { return new PepperyChicken(); } @Override public SichuanCuisine newSichuanCuisine() { return new HotPot(); } }
//具体工厂:一日三餐餐厅 public class Meals implements Canteen{ @Override public HunanCuisine newHunanCuisine() { return new ShreddedMeat(); } @Override public SichuanCuisine newSichuanCuisine() { return new TwiceCookedPork(); } }
开始点餐
public class Test { public static void main(String[] args) { Canteen canteen=new Meals (); HunanCuisine hunanCuisine=canteen.newHunanCuisine (); hunanCuisine.order (); Canteen canteen1=new Meals (); SichuanCuisine hunanCuisine1=canteen1.newSichuanCuisine (); hunanCuisine1.order (); } }
3.4、抽象工厂在源码中的应用
比如java.sql.Connection就采用抽象工厂模式
Connection接口源码如下所示,其定义了Statement、PreparedStatement、CallableStatement三个产品等级结构。
public interface Connection extends Wrapper, AutoCloseable { //返回普通的sql执行器 Statement createStatement() throws SQLException; //返回具有参数化预编译功能的sql执行器 PreparedStatement prepareStatement(String sql) throws SQLException; //返回可以执行存储过程的sql执行器 CallableStatement prepareCall(String sql) throws SQLException; }
3.5、抽象工厂模式的优点
1.当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品族
2.抽象工厂增强了程序的可扩展性,对于新产品族的增加,只需实现一个新的具体工厂就行,不需要对已有代码进行修改,符合开闭原则。
3.6、抽象工厂模式的缺点
1.规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口
2.增加了系统的抽象性和理解难度
git源码:https://gitee.com/TongHuaShuShuoWoDeJieJu/design_pattern.git