开篇前言
主要围绕几点进行展开说明:
1、什么是抽象工厂?
2、如何实现抽象工厂?
3、为什么用抽象工厂,有哪些价值,优缺点及使用范围?
WHAT?
什么是工厂
生活中非常的普遍,化工厂,自来水厂,发电厂,富士康(富士康科技集团是在大陆投资兴办的高新科技企业)等。
那么这些工厂它们有着共同的目的:生产创建产品。
工厂 | 产品 |
自来水厂 | 自来水 |
发电厂 | 电 |
富士康 | 各类电子产品 |
虽然工厂可以创建产品,但是有部分工厂也仅仅是创建产品,而不是创造产品。
比如拿iphone来说,产品中软件的开发、设计等都由苹果公司设计,富士康仅仅只是依照苹果公司提供的设计图进行iphone机子的批量生产。所以这里要明确一点: 工厂的目的是用来生产产品,产品设计不属于它的范畴。
什么是抽象工厂
提供一种方式来封装一些有着共同的特性的工厂,并且无需暴露各个工厂中具体的实现类。
“无需暴露各个工厂中具体的实现类”:对于初学的同学或许不太了解这句话,举个例子:衣服破了一个洞,我们拿到裁缝店那边补, 那么我们不需要知道裁缝店采用哪种方式去补衣服(可能用针手工缝,也可能用机器进行缝合。这些具体的缝合技术就是具体的实现技术)。所以我们只需要知道,破洞的衣服拿到裁缝店,他们会帮我们修补好。
什么是抽象
抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征。例如苹果、香蕉、梨子、葡萄、桃子等,它们共同的特性就是水果。再例如三星Android系列、Nexus、小米、魅族等它们共同的特性就是Android系统设备。
那么我们假设不同品牌的机子是通过不同的工厂进行创建的。Android就是三星Android系列、Nexus系列等的一种抽象。
如下图所示
那
么我们可以假想现实生活中,它们是如何生产出来的,如下图所示
从上图可以看出不同的Android机型由不同的工厂创建,不同的工厂内部它们采用的创建技术可能不同,有的可能采用电脑自动化,由机器人进行高效创建, 有的工厂可能是用大量的员工手工组装创建。
但这些细节我们可以完全不需要去知道,我们只需要知道,某某工厂可以创建出什么样的产品即可。这也完全满足了抽象工厂的核心思想。虽然这样不同的工厂可以创建不同的具体类型的对象,但是这些对象仍然是有着同样的抽象类型,也就是共同的特性。
此时,我希望刚接触的同学们能停下来,思考一下生活中一个常见的物品,它们的创建过程,我希望你们能有 “...哦,对啊! 就是这样!”这种茅塞顿开的赶脚!
How ?
抽象工厂模式的UML类图分析
在实现编码之前我们先分析一下Wikipedia上提供的抽象工厂模式的UML类图。
从上图可以发现无论是工厂还是产品,它们都是有系列的含义的,如AbstractProductA系列旗下有ProductA1、ProductA2两款产品。 AbstractProductB系列旗下有ProductB1、ProductB2两款产品。
那么当客户Client需要产品时, 通过下图箭头可以看出,客户直接关联的是那些抽象的系列对象,不不会直接关联各个具体的产品。
回到我们之前生活中的案例来看,根据上述模板模型,我们也来设计一个Android机型的案例模式(这个案例仅是作为学习抽象工厂所设想,并不关乎现实生活中的真实性)。
比如下图,可以看出AndroidFactory是一个Android的抽象工厂,它管理着一系列有能力生产Android机型的工厂,不同的工厂将会生产出不同的Android机型。
以下的关系非常简单,富士康工厂可以生产出三星Android系列产品,LG工厂可以生产出Nexus Android系列产品。
抽象工厂模式代码分析
接下来进行编码分析阶段,先来看下代码的结构图: (这个案例的代码并未考虑任何优化问题)
/**
* @ClassName: FactoryTypeEnum
* @Description: 工厂类枚举,用于表示Android工厂的id
* @author YK.Lin
* @date 2014年12月30日 下午2:17:59
*/
public enum FactoryTypeEnum {
FuShiKangFactory(1) {
@Override
public boolean isEquals( int type) {
return 1==type;
}
}, LGFactory(2) {
@Override
public boolean isEquals( int type) {
return 2==type;
}
};
private int type ;
FactoryTypeEnum( int type) {
this .type = type;
}
public int getType() {
return type ;
}
// 用于判断工厂类的ID
public abstract boolean isEquals( int type);
}
工厂类:
Android工厂的抽象类:提供了工厂类相应的操作接口
public abstract class AndroidFactory {
/**
* @return 采用默认的工厂,创建一个相应Android机型
*/
public static Android createAnroid(int factoryId){
AndroidFactory factory;
if(FactoryTypeEnum.FuShiKangFactory.isEquals(factoryId)){
factory = new FoxconnFactory();
return factory.createAndroid();
} else if(FactoryTypeEnum.LGFactory.isEquals(factoryId)){
factory = new LGFactory();
return factory.createAndroid();
} else{
// 默认
return null ;
}
};
/**
* @return 由各个具体工厂类实现创建Android设备的方法,真正起作用的方法
*/
protected abstract Android createAndroid();
}
//富士康工厂:生产三星Android系列机型
public class FoxconnFactory extends AndroidFactory{
@Override
protected Android createAndroid() {
return new SamsungAndroid();
}
}
//LG工厂:生产Nexus系列的机型
public class LGFactory extends AndroidFactory{
@Override
protected Android createAndroid() {
return new NexusAndroid();
}
}
Android设备实体类:
public abstract class Android {
/**
* Android机型
*/
protected String mAndroidType ;
/**
* 打印Android机型
*/
public void printAndroidInfo(){
System. out.println("该Android机型:" +mAndroidType );
}
}
// 三星Android机型对象
public class SamsungAndroid extends Android{
public SamsungAndroid(){
mAndroidType = "三星Android" ;
}
}
// Nexus Android机型对象
public class NexusAndroid extends Android {
public NexusAndroid() {
mAndroidType = "Nexus Android 系列" ;
}
}
测试功能类:
public class TestMain {
public static void main(String[] args) {
// 生产 Nexus Android机型
Android myAndroid = AndroidFactory.createAnroid(FactoryTypeEnum. LGFactory.getType());
myAndroid.printAndroidInfo();
// 生产 Samsung Android机型
Android youAndroid = AndroidFactory.createAnroid(FactoryTypeEnum. FuShiKangFactory.getType());
youAndroid.printAndroidInfo();
}
}
运行结果:
上述的代码仅是说明抽象工厂模式的思想,并未进行任何优化,因为学习知识,能简单尽量简单。优化可以在掌握之后,进行提升学习。
Why ?
为什么要使用抽象工厂模式?
① 首先说明为什么使用设计模式?
1、让代码的逻辑更贴近人类思考问题的逻辑。(面向对象思想)
比如:警察开警车 我们可以用police.drivePoliceCar(); 和 a.b()。 哪个代码好,想必心知肚明。
2、当项目逐渐壮大,我们要能够最快速度定位bug,并解决而且不会影响到别的功能,不会引发新的bug。
比如:春节回家,如果淘宝双11订单功能突然出现bug不能用,如果没有对功能进行细分的话,开发人员如何快速的定位的出问题的代码, 尤其类似双11这样的情况,争分夺秒。
所以在代码开发过程中,必须考虑到后期代码的维护成本,查错的简单性,新员工接受项目的进度等等 众多问题
等等。
② 抽象工厂模式的核心思想:提供一种方式来封装一些有着共同的特性的工厂,并且无需暴露各个工厂中具体的实现类。
假设: 今天三星的Android机型让富士康帮忙生产,明天三星老总不想让富士康来生产了,想让其他工厂来生产,那么此时我们项目中需要 修改的地方范围就会被限制在相关的工厂类上。在业务逻辑上不需要进行修改。之前的代码是这样,我们需要终止只要关掉工厂和具体的机型 类的关系即可:
public class FoxconnFactory extends AndroidFactory{
@Override
protected Android createAndroid() {
// return new SamsungAndroid();
return 其他合作的机型
}
}
如果三星想换成让其他工厂进行生产,那么我们只需要按照下面的步骤改:
1、实现一个相关的工厂对象 OtherFactory 并且加入AndroidFactory系列。
public class OtherFactory extends AndroidFactory{
@Override
protected Android createAndroid() {
return new SamsungAndroid();
}
}
2、在工厂标识枚举类中添加相关工厂的标识
public enum FactoryTypeEnum ... 添加OtherFactory(3);
3、在AndroidFactory系列创建Android机型代码中添加OtherFactory的工厂。
public abstract class AndroidFactory {
/**
* @return 采用默认的工厂,创建一个相应Android机型
*/
public static Android createAnroid(int factoryId){
AndroidFactory factory;
if(FactoryTypeEnum.FuShiKangFactory.isEquals(factoryId)){
factory = new FoxconnFactory();
return factory.createAndroid(); // 此时的富士康创建出来的机型就不是三星的Android系列了。
} else if(FactoryTypeEnum.LGFactory.isEquals(factoryId)){
factory = new LGFactory();
return factory.createAndroid();
} else if(FactoryTypeEnum.OtherFactory.isEquals(factoryId)){// 新的工厂用于生产三星Android机型
factory = new OtherFactory();
return factory.createAndroid();
}else{
//默认
}
};
}
我们会发现,如果需求变更了,我们需要变动的地方非常的少,如果开发过程中文档都更新及时,那么无论是否为新的开发人员,都可以在不知道任何业务逻辑的情况下进行相关的代码修改。
如果没采用抽象工厂设计模式,需求这么变更的话, 我们需要改的地方就非常的宽泛, 一旦要修改的地方超过了人体脑部所能承受的上限,开发人员就会开始混乱,不知道自己改了哪些地方,
没有改哪些地方,无形中加重了各类成本,各类风险。这在商业性项目中绝对不允许出现的。否则你就等着无限期的加班。
抽象工厂的优缺点和使用场景:
优点:
1、抽象工厂可以非常好的应对频繁的"新系列"需求变化
因为抽象工厂帮助我们控制了一个应用创建产品对象的过程和责任。它将客户和类的实现分离,客户通过他们的抽象接口操纵实例,具体的创建过程不会出现在客户的代码中。
2、它使得易于交换产品系列: 理解这个需要理解面向对象的多态性 .
举个例子
class A{} ; class B extends class A{}; class C extends class A{};
main:
第一版需求 A entity = new B();
改天需求更改,要用C对象。那么我们声明的都是基类A。 进行逻辑处理的entity 本质是A类。 所以我们需要改动的代码很少。 只需要将new B() 改成 new C()即可。
3、它有利于产品的一致性:由于产品都是按照系列进行划分,都是面向接口编程,所以一个应用一次只能使用同一个系列中的对象。
4、有助于团队开发,分工详细, 降低了模块间的耦合度,提高了团队的开发效率。
缺点:
1、不能很好的应对"新对象"的需求变动。如果需求大概,或者没有新的产品无法完全贴近某个已经存在的系列特质,那么只能重新开发一个新的系列来支持它。
2、代码会变得极其庞大。
适用性:
1、一个系统不应当依赖于它的产品的创建、组合和表示的细节。
2、一个系统有多于一个的产品系列,而系统只使用其中某一个产品系列并且同一个产品系列的产品是在一起使用的(一致性)
3、当你要强调一系列相关的产品对象的设计以便进行联合使用时。
4、当你提供一个产品类库,而只想显示它们的接口而不是实现时。