[置顶] 设计模式(一)工厂模式
官方定义:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计经验总结。
使用意义:使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码的可靠性。可复用的面向对象软件系统主要包含两大类:应用程序工具箱和框架(框架是指构成一类特定软件可复用设计的一组相互协作的类)。设计模式有助于对框架的理解,成熟的框架通常使用了多种设计模式,如果你能熟悉这些设计模式,毫无疑问,将能迅速的掌握框架的结构。
工厂模式概念
工厂模式将客户类与工厂类分开,消费者在任何时候需要某种产品只需要向工厂请求,消费者无须修改就能接纳新产品。缺点:产品修改时,工厂类也要做相应的修改。工厂模式是编程过程中最常用的设计模式,因为工厂模式相当于创建实例对象的new, 例如 A a = new A();工厂类也是用来创建实例对象的,那么,new关键字可以创建对象,为什么还要用工厂类创建实例对象呢?由于创建实例时通常伴随有初始化工作,如果只是简单的赋值操作,我们可以使用带参数的构造方法,但如果初始化不仅仅是赋值这样简单的操作,还包含一些逻辑判断等大片段的代码,如果都写进构造函数,会使我们的代码看起来很臃肿,将多个功能放进一个方法,就像将很多鸡蛋放进一个篮子里,这很危险,并且有悖于我们面向对象的设计原则。这时需要我们将长片段的代码进行分割,再封装,以后如果需要修改,只需要修改部分片段,而不会出现牵一动百的情形。
简单工厂模式
简单工厂模式的是想源于java中的接口,通过使用接口可以实现不相关类的相同行为,而不需要考虑这些类的层次关系,接口就是实现类对外的外观。主要使用在业务比较简单的情况下,由三种角色构成。
- 工厂类:是模块的核心,通常包含逻辑实现,在java中往往是一个具体的实现类
- 抽象产品:是具体产品继承的父类或者需要实现的接口,在java中通常是抽象类或者接口
- 具体产品:工厂类创建的对象就是该类的实例,在java中通常是具体的实现类
简单工厂类的基本结构
Client与具体实现Impl之间并无瓜葛,客户端通过Factory获取需要的接口对象,然后调用接口的方法来实现具体的功能。下面举个例子,通过不同的通讯工具来通信,QQ和微信分别实现了API接口。
示例说明
/** * 简单工厂模式 * Created by xian.juanjuan on 2017-7-13 14:07. */ public class SimpleFactory { //根据条件创建具体的实现对象 public static MessageApi createMessageApi(int condition){ MessageApi messageApi = null; if (condition == 1){ messageApi = new QQMessageImpl(); } else if (condition == 2){ messageApi = new WeChatMessageImpl(); } return messageApi; } } class Client{ public static void main(String[] args){ //通过简单工厂获取接口对象 MessageApi messageApi = SimpleFactory.createMessageApi(1); messageApi.sendMessage("测试简单工厂模式"); } } /** * 通信接口可以通过简单工厂来创建 */ interface MessageApi{ //具体的功能方法 void sendMessage(String msg); } /** * 接口的具体实现--QQ通信 */ class QQMessageImpl implements MessageApi{ @Override public void sendMessage(String msg) { System.out.println("通过QQ聊天工具发送消息:"+msg); } } /** * 接口的具体实现--微信通信 */ class WeChatMessageImpl implements MessageApi{ @Override public void sendMessage(String msg) { System.out.println("通过微信聊天工具发送消息"+msg); } }问题1:这里如果再增加一种通讯方式(Email)时,就需要修改工厂类,显然简单工厂方式不能满足OCP设计原则。
问题2:客户端在调用工厂时,需要传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要知道每个参数对应的功能处理,在一定程度上向客户端暴露了内部实现细节。
每次增加一个实现类就要就要修改工厂类的实现,通常用配置文件类解决这个问题。
创建FactoryTest.properties配置文件,内容如下,如果新增实现类,只需要修改下面的配置文件即可。
ImplClass=com.xianjj.pattern.QQMessageImpl
工厂类如下
public class SimpleFactory { public static MessageApi createMessageApi(){ Properties properties = new Properties(); InputStream inputStream; try { inputStream = SimpleFactory.class.getResourceAsStream("FactoryTest.properties"); properties.load(inputStream); } catch (IOException e) { System.out.println("加载工厂配置文件异常"); e.printStackTrace(); } //用反射创建对象 MessageApi messageApi = null; try { messageApi = (MessageApi)Class.forName(properties.getProperty("ImplClass")).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return messageApi; } }
简单工厂模式优缺点
- 帮助封装 非常友好的帮我们实现了组件的封装,让组件外部能真正的面向接口编程
- 解耦 实现客户端和具体实现类的解耦
- 可能增加客户端的复杂度 如何选择具体的实现类需要仔细斟酌
- 不方便扩展子工厂 私有化简单工厂的构造方法,使用静态方法来创建接口,也就不能通过写简单工厂类的子类来改变创建接口的方法的行为了
- 不完全满足OCP
简单工厂通常不用创建简单工厂类的类实例,因此,可以把简单工厂类看做一个工具类,直接使用静态方法就可以了,所以被称作静态工厂。为了防止客户端无谓的创建简单工厂实例,可以把简单工厂的构造方法私有化。
工厂方法模式
为了解决简单工厂模式最大的缺点—不完全满足OCP原则,设计师们提出了工厂方法模式,工厂方法模式相对于简单工厂模式来说最大的不同在于,对于一个项目或者一个独立的模块来说,简单工厂模式只有一个工厂类,儿工厂方法模式有一组实现了相同接口的工厂类。
在工厂方法模式中,核心的工厂类不再负责产品的创建,而是将具体的创建工作交给子类去做。这个核心工厂则变为抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触产品创建的细节。在工厂方法模式中一般都有一个平行的等级结构,也就是说工厂和产品是对应的。
工厂方法模式结构
示例说明
果农起初只有一个小型的苹果园,后来规模扩大,出现了苹果工厂,葡萄工厂,每个工厂独立生产对应的水果,实现专业化和规模化的生产。
/** * 工厂方法模式示例 * Created by xian.juanjuan on 2017-7-14 09:30. */ public class ClientTest { public static void main(String[] args){ //实例化水果工厂 FruitFactory fruitFactory = new AppleFactoryImpl(); FruitFactory fruitFactory1 = new GrapeFactoryImpl(); //从水果工厂生产水果 Fruit fruit = fruitFactory.factory(); Fruit fruit1 = fruitFactory1.factory(); //不同水果的生长过程 fruit.plant(); fruit1.harvest(); } } /** * 水果接口 */ interface Fruit{ void plant();//种植 void harvest();//收获 } /** * 水果工厂的产品:苹果 */ class Apple implements Fruit{ private int treeAget;//树龄 @Override public void harvest() { System.out.println("苹果已经收获。。。"); } @Override public void plant() { System.out.println("苹果已经种植。。。"); } public int getTreeAget() { return treeAget; } public void setTreeAget(int treeAget) { this.treeAget = treeAget; } } /** * 水果工厂的产品:葡萄 */ class Grape implements Fruit{ private boolean seedless;//是否有籽 @Override public void harvest() { System.out.println("葡萄已经收获。。。"); } @Override public void plant() { System.out.println("葡萄已经种植。。。"); } public boolean isSeedless() { return seedless; } public void setSeedless(boolean seedless) { this.seedless = seedless; } } /** * 水果工厂接口 */ interface FruitFactory{ Fruit factory(); } /** * 苹果工厂实现 */ class AppleFactoryImpl implements FruitFactory{ @Override public Fruit factory() { Fruit fruit = new Apple(); System.out.println("水工工厂成功创建一个水果:苹果"); return fruit; } } /** * 葡萄工厂实现 */ class GrapeFactoryImpl implements FruitFactory{ @Override public Fruit factory() { Fruit fruit = new Grape(); System.out.println("水工工厂成功创建一个水果:葡萄"); return fruit; } }
输出结果
水工工厂成功创建一个水果:苹果
水工工厂成功创建一个水果:葡萄
苹果正在生长。。。
葡萄已经收获。。。
Process finished with exit code 0
工厂方法模式优缺点(相对于简单工厂模式)
- 结构复杂度增加
- 代码复杂度增加
- 客户端编程难度增加(客户端编程中需要对需要对工厂类进行实例化,而简单工厂时静态类不需要实例化)
- 管理难度增大
- 满足OCP原则
抽象工厂模式
抽象工厂模式是三个最抽象,最具一般性的。抽象工厂模式的目的是给客户端提供一个接口,可以创建多个产品族中的产品对象,使用抽象工厂模式需要满足以下条件
- 系统中有多个产品族,而系统一次只可能消费其中一族产品
- 同属于一个产品族的产品一起使用
举例说明两个概念
- 产品族:Android产品族和Apple产品族
- 产品等级(产品族的产品):手机和充电器。(Android手机和Android充电器是一个产品族,Apple手机和Apple充电器是另一个产品族;Android手机用Android充电器,苹果手机用苹果充电器)一个等级结构是由结构相同的产品组成。
抽象工厂模式的每个工厂创造出的都是一族的产品,而不是一个或者一组。
产品结构图
示例说明
用户买手机配充电器
首先用简单工厂模式实现
public class AbstractFactoryTest { public static void main(String[] args){ phoneCharging(1, 1); } public static void phoneCharging(int phoneType, int chargerType){ //手机工厂和充电器工厂生产手机和充电器 Phone phone = PhoneFactory.createPhone(phoneType); Charger charger = ChargerFactory.createCharger(chargerType); //买手机配充电器 phone.buyPhone(); charger.configureCharger(); } } /** * 手机接口 */ interface Phone{ void buyPhone();//买手机 } class AndroidPhone implements Phone{ @Override public void buyPhone() { System.out.println("我买了一部Android手机"); } } class ApplePhone implements Phone{ @Override public void buyPhone() { System.out.println("我买了一部Apple手机"); } } /** * 充电器接口 */ interface Charger{ void configureCharger();//配置充电器 } class AndroidCharger implements Charger{ @Override public void configureCharger() { System.out.println("给我配置了Android手机充电器"); } } class AppleCharger implements Charger{ @Override public void configureCharger() { System.out.println("给我配置了Apple手机充电器"); } } /** * 手机工厂类 */ class PhoneFactory{ public static Phone createPhone(int type){ Phone phone = null; if (1 == type){ phone = new AndroidPhone(); } else if (2 == type){ phone = new ApplePhone(); } return phone; } } /** * 充电器工厂类 */ class ChargerFactory{ public static Charger createCharger(int type){ Charger charger = null; if (1 == type){ charger = new AndroidCharger(); } else if(2 == type){ charger = new AppleCharger(); } return charger; } }运行结果
我买了一部Android手机
给我配置了Android手机充电器
Process finished with exit code 0
外部使用的时候,手机与充电器的类型需要匹配,如果phoneCharging(1, 2);那手机和充电器就不兼容;在上述的简单工厂模式中,并没有维护这种关系,为了解决这个问题 需要使用抽象工厂模式。简单工厂模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
用同一个工厂等级结构复杂两个不同产品等级结构中的产品对象的创建,即Apple工厂生产Apple手机和Apple手机充电器。由于这两个产品族的产品等级结构相同,因此使用同一个工厂族也可以处理这两个产品族的创建问题,这就是抽象工厂模式。
使用抽象工厂模式实现
在之前代码的基础上,只需要增加抽象工厂类和实现即可。
interface AbstractFactory{ Phone createPhone(); Charger createCharger(); } class AndroidFactory implements AbstractFactory{ @Override public Charger createCharger() { return new AndroidCharger(); } @Override public Phone createPhone() { return new ApplePhone(); } } class AppleFactory implements AbstractFactory{ @Override public Charger createCharger() { return new AppleCharger(); } @Override public Phone createPhone() { return new ApplePhone(); } } 测试客户端 public class AbstractFactoryTest { public static void main(String[] args){ //客户选择并创建需要的产品对象 AbstractFactory factory = new AppleFactory(); //让店员配套起来即可 phoneCharging(factory); } public static void phoneCharging(AbstractFactory factory){ //店员找相应的工厂获取产品 Phone phone = factory.createPhone(); Charger charger = factory.createCharger(); phone.buyPhone(); charger.configureCharger(); } }由此可见,抽象工厂模式是为一系列相关的对象或者相互依赖的对象创建一个接口。
抽象工厂模式优缺点
- 分离接口和实现(客户端从具体的产品实现中解耦)
- 使切换产品族变的容易(从Android切换到Apple,客户端只需要切换一下具体工厂)
- 不容易扩展新产品(如果给整个产品族新增一个产品,就要修改抽象工厂,导致修改所有的工厂实现类)