单例模式
本段借鉴于:【Java】设计模式:深入理解单例模式
什么是单例模式
- 定义:保证一个类仅有一个实例,并提供一个访问它的全局接口。
考虑这样一个应用,读取配置文件的内容。很多应用项目,都有与应用相关的配置文件,这些配置文件很多是由项目开发人员自定义的,在里面定义一些应用重要的参数数据。当然,在实际的项目中,这种配置文件多数采用 xml 格式,也有采用 properties 格式的,我们这里假设创建了一个名为 AppConfig 的类,它专门用来读取配置文件内的信息。客户端通过new一个AppConfig的实例来得到一个操作配置文件内容的对象。如果在系统运行中,有很多地方都需要使用配置文件的内容,也就是说很多地方都需要创建 AppConfig 对象的实例。换句话说,在系统运行期间,系统中会存在很多个 AppConfig 的实例对象,这里读者有没有发现有什么问题存在?当然有问题了,试想一下,每一个 AppConfig 实例对象里面都封装着配置文件的内容,系统中有多个AppConfig实例对象,也就是说系统中会同时存在多份配置文件的内容,这样会严重浪费内存资源。如果配置文件内容越多,对于系统资源的浪费程度就越大。事实上,对于AppConfig这样的类,在运行期间只需要一个实例对象就足够了。
单例模式的优势
- 单例类只有一个实例
- 共享资源,全局使用
- 节省创建时间,提高性能
单例模式的七种写法
首先总结一下单例模式的写法特点:
- 单例类自己创建自己的实例
- 不对外提供构造方法,构造方法为private型
- 提供一个获取实例的对外接口
饿汉式
//单例模式——饿汉式
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {
System.out.println("SingletonHungry is created!");
}
public static SingletonHungry getInstance() {
return instance;
}
}
这种方式和名字很贴切,饥不择食,在类装载的时候就创建,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间。
总结:「饿汉式」是最简单的实现方式,这种实现方式适合那些在初始化时就要用到单例的情况,这种方式简单粗暴,如果单例对象初始化非常快,而且占用内存非常小的时候这种方式是比较合适的,可以直接在应用启动时加载并初始化。
但是,如果单例初始化的操作耗时比较长而应用对于启动速度又有要求,或者单例的占用内存比较大,再或者单例只是在某个特定场景的情况下才会被使用,而一般情况下是不会使用时,使用「饿汉式」的单例模式就是不合适的,这时候就需要用到「懒汉式」的方式去按需延迟加载单例。
懒汉式(非线程安全)
//单例模式——懒汉式
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {
System.out.println("SingletonLazy is created!");
}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
懒汉式申明了一个静态对象,在用户第一次获取单例类实例的时候再进行类的初始化,虽然节约了资源,但第一次获取实例时才进行实例化,反映稍慢一些,而且在多线程不能正常工作。在多线程访问的时候,很可能会造成多次new,就不再是单例了。
「懒汉式」与「饿汉式」的最大区别就是将单例的初始化操作,延迟到需要的时候才进行,这样做在某些场合中有很大用处。比如某个单例用的次数不是很多,但是这个单例提供的功能又非常复杂,而且加载和初始化要消耗大量的资源,这个时候使用「懒汉式」就是非常不错的选择。
懒汉式(线程安全)
//单例模式——懒汉式
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {
System.out.println("SingletonLazy is created!");
}
public static synchronized SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
这两种「懒汉式」单例,名字起的也很贴切,一直等到对象实例化的时候才会创建,确实够懒,不用鞭子抽就不知道走了,典型的时间换空间,每次获取实例的时候才会判断,看是否需要创建,浪费判断时间,如果一直没有被使用,就不会被创建,节省空间。
因为这种方式在getInstance()方法上加了同步锁,所以在多线程情况下会造成线程阻塞,把大量的线程锁在外面,只有一个线程执行完毕才会执行下一个线程。
双重校验锁(DCL)
上面的方法「懒汉式(线程安全)」毫无疑问存在性能的问题——如果存在很多次getInstance()的调用,那性能问题就不得不考虑了!
让我们来分析一下,究竟是整个方法都必须加锁,还是仅仅其中某一句加锁就足够了?我们为什么要加锁呢?分析一下出现lazy-loaded的那种情形的原因。原因就是检测null的操作和创建对象的操作分离了。如果这两个操作能够原子地进行,那么单例就已经保证了。于是,我们开始修改代码,就成了下面的双重校验锁(Double Check Lock):
//单例模式——懒汉式
public class SingletonLazy {
/**
* 注意此处使用的关键字 volatile,
* 被volatile修饰的变量的值,将不会被本地线程缓存,
* 所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
*/
private volatile static SingletonLazy instance;
private SingletonLazy() {
System.out.println("SingletonLazy is created!");
}
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
这种写法在getSingleton()方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例。
「双重校验锁」:既可以达到线程安全,也可以使性能不受很大的影响,换句话说在保证线程安全的前提下,既节省空间也节省了时间,集合了「饿汉式」和两种「懒汉式」的优点,取其精华,去其槽粕。
对于volatile关键字,还是存在很多争议的。由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
还有就是在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java1.5及以上的版本。
静态内部类
另外,在很多情况下JVM已经为我们提供了同步控制,比如:
- 在
static {...}
区块中初始化的数据 - 访问final字段时
因为在JVM进行类加载的时候他会保证数据是同步的,我们可以这样实现:采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现「懒汉式」的延迟加载和线程安全。
//单例类Singleton
public class Singleton {
private Singleton() {
System.out.println("Singleton instance is created!");
}
public static Singleton getSingletonInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化instance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化instance,这样不仅能确保线程安全也能保证Singleton类的唯一性,所以推荐使用静态内部类单例模式。
然而这还不是最简单的方式,《Effective Java》中作者推荐了一种更简洁方便的使用方式,就是使用「枚举」。
枚举
《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
public enum Singleton {
//定义一个枚举的元素,它就是 Singleton 的一个实例
INSTANCE;
public void doSomeThing() {
// do something...
}
}
使用方法如下:
public static void main(String args[]) {
Singleton singleton = Singleton.instance;
singleton.doSomeThing();
}
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高,不建议用。
使用容器
public class SingletonManager {
private static Map<String, Object> objectMap = new HashMap<>();
private SingletonManager() {
}
public static void registerInstance(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getInstanceWithKey(String key) {
return objectMap.get(key);
}
}
这种是用SingletonManager将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
观察者模式
什么是观察者模式
观察者模式即 Observer Pattern。观察者模式的主旨是定义对象间的一种一(被观察者 Observable)对多(观察者Observer)的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。因此,观察者模式中主要有观察者(Observer)和被观察者(Observable)2 种对象。其中,被观察者(Observable)是个抽象类或接口,只能被继承或实现,观察者(Observer)并不是一个类而是一个接口,所以观察者可以有多个,实现了该接口的类都属于观察者。观察者也可以同时是被观察者,被观察者同样可以是观察者。
观察者模式用来解决哪些问题
举个例子,在 Android 中,我们希望数据的变化自动更新所有相关UI,那么这里的数据就是一个被观察的对象,所有涉及相关数据的UI就是多个观察者,他们之间就是一对多的依赖关系,并且我们希望做到“一”变化时,依赖这个“一”的“多”也能够同步改变。MVC 就是观察者模式的一个实例,在 Android 中界面控件的事件响应以及 BroadcastReceiver 等都是基于观察者模式来实现的。
如何使用观察者模式
总结起来只有两点:
- 在被观察者接口/抽象类中定义增加观察者、删除观察者、通知观察者等方法
- 在观察者接口中定义被观察者发生变化后通知观察者时触发的方法
观察者模式实例
(1)创建观察者接口:
//观察者接口
public interface Observer {
//被'被观察者'通知后触发的方法
void onClick();
}
(2)创建被观察者接口
//被观察者接口
public interface Observed {
//增加观察者
void addObserver (Observer observer);
//删除观察者
void removeObserver (Observer observer);
//通知观察者
void notifyObserver ();
}
(3)创建实现了被观察者接口的类
//Button是一个继承了被观察者接口Observed的被观察者
public class Button implements Observed{
//存储所有观察Button的观察者
private List<Observer> observers = new ArrayList<>();
public Button() {
System.out.println("我是被观察者,我已创建完成!");
}
@Override
public void addObserver(Observer observer) {
//增加观察者
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
//删除指定观察者
observers.remove(observer);
}
@Override
public void notifyObserver() {
//通知所有观察者
for (Observer observer:observers) {
observer.onClick();
}
}
}
可以看见,在实现了被观察者接口的类中,我们使用了List来存储所有观察它的观察者,接着利用List实现了增加观察者、删除观察者和通知所有观察者的方法。
(4)测试
public class Test {
public static void main (String[] args) {
//创建观察者实体类(匿名)
Observer observer1 = new Observer() {
@Override
public void onClick() {
System.out.println("我是观察者1,我已收到通知!");
}
};
Observer observer2 = new Observer() {
@Override
public void onClick() {
System.out.println("我是观察者2,我已收到通知!");
}
};
Observer observer3 = new Observer() {
@Override
public void onClick() {
System.out.println("我是观察者3,我已收到通知!");
}
};
//创建被观察者类实体
Button button = new Button();
button.addObserver(observer1);
button.addObserver(observer2);
button.addObserver(observer3);
//通知观察者
button.notifyObserver();
}
}
查看程序运行结果:
我是被观察者,我已创建完成!
我是观察者1,我已收到通知!
我是观察者2,我已收到通知!
我是观察者3,我已收到通知!
工厂模式
参考资料:
创建对象与使用对象——谈谈工厂的作用
JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)
为什么要使用工厂模式
与一个对象相关的职责通常有三类:对象本身所具有的职责、创建对象的职责和使用对象的职责。对象本身的职责比较容易理解,就是对象自身所具有的一些数据和行为,可通过一些公开的方法来实现它的职责。在本文中,我们将简单讨论一下对象的创建职责和使用职责。
在Java语言中,我们通常有以下几种创建对象的方式:
- 使用new关键字直接创建对象
- 通过反射机制创建对象
- 通过clone()方法创建对象
- 通过工厂类创建对象
毫无疑问,在客户端代码中直接使用new关键字是最简单的一种创建对象的方式,但是它的灵活性较差,下面通过一个简单的示例来加以说明:
class LoginAction {
private UserDAO udao;
public LoginAction() {
udao = new JDBCUserDAO(); //创建对象
}
public void execute() {
//其他代码
udao.findUserById(); //使用对象
//其他代码
}
}
在LoginAction类中定义了一个UserDAO类型的对象udao,在LoginAction的构造函数中创建了JDBCUserDAO类型的udao对象,并在execute()方法中调用了udao对象的findUserById()方法,这段代码看上去并没有什么问题。下面我们来分析一下LoginAction和UserDAO之间的关系,LoginAction类负责创建了一个UserDAO子类的对象并使用UserDAO的方法来完成相应的业务处理,也就是说LoginAction即负责udao的创建又负责udao的使用,创建对象和使用对象的职责耦合在一起,这样的设计会导致一个很严重的问题:如果在LoginAction中希望能够使用UserDAO的另一个子类如HibernateUserDAO类型的对象,必须修改LoginAction类的源代码,违反了“开闭原则”。如何解决该问题?
最常用的一种解决方法是将udao对象的创建职责从LoginAction类中移除,在LoginAction类之外创建对象,那么谁来负责创建UserDAO对象呢?答案是:工厂类。通过引入工厂类,客户类(如LoginAction)不涉及对象的创建,对象的创建者也不会涉及对象的使用。引入工厂类UserDAOFactory之后的结构如图1所示:
工厂类的引入将降低因为产品或工厂类改变所造成的维护工作量。如果UserDAO的某个子类的构造函数发生改变或者要需要添加或移除不同的子类,只要维护UserDAOFactory的代码,而不会影响到LoginAction;如果UserDAO的接口发生改变,例如添加、移除方法或改变方法名,只需要修改LoginAction,不会给UserDAOFactory带来任何影响。
在所有的工厂模式中,我们都强调一点:两个类A和B之间的关系应该仅仅是A创建B或是A使用B,而不能两种关系都有。将对象的创建和使用分离,也使得系统更加符合“单一职责原则”,有利于对功能的复用和系统的维护。
此外,将对象的创建和使用分离还有一个好处:防止用来实例化一个类的数据和代码在多个类中到处都是,可以将有关创建的知识搬移到一个工厂类中。
使用工厂类还有一个“不是特别明显的”优点,一个类可能拥有多个构造函数,而在Java、C#等语言中构造函数名字都与类名相同,客户端只能通过传入不同的参数来调用不同的构造函数创建对象,从构造函数和参数列表中也许大家根本不了解不同构造函数所构造的产品的差异。但如果将对象的创建过程封装在工厂类中,我们可以提供一系列名字完全不同的工厂方法,每一个工厂方法对应一个构造函数,客户端可以以一种更加可读、易懂的方式来创建对象,而且,从一组工厂方法中选择一个意义明确的工厂方法,比从一组名称相同参数不同的构造函数中选择一个构造函数要方便很多。如图所示:
在图中,矩形工厂类RectangleFactory提供了两个工厂方法createRectangle()和createSquare(),一个用于创建长方形,一个用于创建正方形,这两个方法比直接通过构造函数来创建长方形或正方形对象意义更加明确,也在一定程度上降低了客户端调用时出错的概率。
那么,有人可能会问,是否需要为设计中的每一个类都配备一个工厂类?答案是:具体情况具体分析。如果产品类很简单,而且不存在太多变数,其构造过程也很简单,此时无须为其提供工厂类,直接在使用之前实例化即可,例如Java语言中的String类,我们就无须为它专门提供一个StringFactory,这样做反而有点像杀鸡用牛刀,大材小用,而且会导致工厂泛滥,增加系统的复杂度。
工厂模式的分类
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
用形象生动的语言解释这三种模式的区别:
- 不使用工厂模式:假如还没有工业革命,如果一个客户要一款宝马车,一般的做法是客户去创建一款宝马车,然后拿来用。
- 简单工厂模式:后来出现工业革命。用户不用去创建宝马车。因为客户有一个工厂来帮他创建宝马.想要什么车,这个工厂就可以建。比如想要320i系列车。工厂就创建这个系列的车。即工厂可以创建产品。
- 工厂方法模式:为了满足客户,宝马车系列越来越多,如320i,523i,30li等系列一个工厂无法创建所有的宝马系列。于是由单独分出来多个具体的工厂。每个具体工厂创建一种系列。即具体工厂类只能创建一个具体产品。但是宝马工厂还是个抽象。你需要指定某个具体的工厂才能生产车出来。
- 抽象工厂模式:随着客户的要求越来越高,宝马车必须配置空调。于是这个工厂开始生产宝马车和需要的空调。
- 最终是客户只要对宝马的销售员说:我要523i空调车,销售员就直接给他523i空调车了。而不用自己去创建523i空调车宝马车.
GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。 将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。
两种工厂模式的区别:
工厂方法模式:
- 一个抽象产品类,可以派生出多个具体产品类。
- 一个抽象工厂类,可以派生出多个具体工厂类。
- 每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
- 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
- 一个抽象工厂类,可以派生出多个具体工厂类。
- 每个具体工厂类可以创建多个具体产品类的实例。
区别:
- 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
- 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
简单工厂模式
首先我们不使用简单工厂模式,让客户自己创建宝马车,然后拿来用。
//BWM320.java
public class BMW320 {
public BMW320(){
System.out.println("制造-->BMW320");
}
public void use() {
...
}
}
//BWM523.java
public class BMW523 {
public BMW523(){
System.out.println("制造-->BMW523");
}
public void use() {
...
}
}
//Customer.java
public class Customer {
public static void main(String[] args) {
//自己创建
BMW320 bmw320 = new BMW320();
BMW523 bmw523 = new BMW523();
//自己用
bwm320.use();
bwm523.use();
}
}
这样就有一个问题,客户仅仅是想使用宝马车,制造(创建)宝马车并不是客户所希望学会的,因此我们就需要将创建宝马车的职责交给工厂类,让工厂创建宝马车,客户直接拿来用就行了,客户直接使用工厂的创建工厂方法,传入想要的宝马车型号就行了,而不必去知道创建的细节。
产品类:
public abstract class BWM {
//宝马车抽象类,所有具体型号的宝马车都将继承自该类
}
//BWM320具体车型
public class BWM320 extends BWM {
public BWM320() {
System.out.println("制造-->BMW320");
}
public void use() {
System.out.println("使用BWM320");
}
}
//BWM523具体车型
public class BWM523 extends BWM {
public BWM523() {
System.out.println("制造-->BMW523");
}
public void use() {
System.out.println("使用BWM523");
}
}
工厂类:
public class Factory {
//工厂类内部的"静态"方法,用来创建具体型号的宝马车
public static BWM createBWM(int type) {
switch (type) {
case 320: {
return new BWM320();
}
case 523: {
return new BWM523();
}
}
return null;
}
}
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
先来看看它的组成:
- 工厂类:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
- 抽象产品类:它一般是具体产品继承的父类或者实现的接口。
- 具体产品类:工厂类所创建的对象就是此类的实例
下面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式:当客户不再满足现有的车型号的时候,想要一种速度快的新型车,只要这种车符合抽象产品制定的合同,那么只要通知工厂类知道就可以被客户使用了,所以对产品部分来说,它是符合开闭原则的。但是工厂部分好像不太理想,因为每增加一种新型车,都要在工厂类中增加相应的创建业务逻辑(createBMW(int type)方法需要新增case),这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。对于这样的工厂类,我们称它为全能类或者上帝类。
于是工厂方法模式作为救世主出现了。工厂类定义成了接口,而每新增一个车种类型,就增加该车种类型对应工厂类的实现,这样工厂的设计就可以扩展了,而不必去修改原来的代码。
工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
工厂接口/类:
//工厂接口
public interface FactoryBWM {
//创建宝马具体车型的方法
BWM createBWM();
}
//创建宝马523的具体工厂类
public class FactoryBWM523 implements FactoryBWM{
@Override
public BWM createBWM() {
return new BWM523();
}
}
//创建宝马320的具体工厂类
public class FactoryBWM320 implements FactoryBWM {
@Override
public BWM createBWM() {
return new BWM320();
}
}
产品类:和简单工厂模式一样,不做任何改变。
客户类:
public class Customer {
public static void main(String[] args) {
//通过具体工厂类获取到指定车型
BWM523 bwm523 = (BWM523) new FactoryBWM523().createBWM();
BWM320 bwm320 = (BWM320) new FactoryBWM320().createBWM();
//使用
bwm320.use();
bwm523.use();
}
}
总结工厂方法模式的组成:
- 工厂接口:为具体生产某个具体型号的宝马车的工厂类提供抽象方法
- 实现工厂接口的工厂类:实现工厂接口提供的生产宝马车的方法
- 抽象产品类:为具体产品类提供统一属性或方法
- 具体产品类:略
和简单工厂模式不同的是,工厂方法模式的具体工厂类中的方法不再是静态方法,需要创建具体工厂的实例才能够获取到具体产品实例。
工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口,但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的,所以抽象工厂模式就出现了。
抽象工厂模式
随着客户的要求越来越高,宝马车需要不同配置的空调和发动机等配件。于是这个工厂开始生产空调和发动机,用来组装汽车。这时候工厂有两个系列的产品:空调和发动机。宝马320系列配置A型号空调和A型号发动机,宝马523系列配置B型号空调和B型号发动机。
抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。比如宝马320系列使用空调型号A和发动机型号A,而宝马523系列使用空调型号B和发动机型号B,那么使用抽象工厂模式,在为320系列生产相关配件时,就无需制定配件的型号,它会自动根据车型生产对应的配件型号A。
产品类:
//发动机接口
public interface Engine {
}
//发动机A,实现发动机接口
public class EngineA implements Engine {
public EngineA() {
System.out.println("制造-->EngineA");
}
}
//发动机A,实现发动机接口
public class EngineB implements Engine {
public EngineB() {
System.out.println("制造-->EngineB");
}
}
//空调接口
public interface Air {
}
//空调A,实现空调接口
public class AirA implements Air {
public AirA() {
System.out.println("制造-->AirA");
}
}
//空调B,实现空调接口
public class AirB implements Air {
public AirB() {
System.out.println("制造-->AirB");
}
}
工厂类:
//创建工厂的接口
public interface AbstractFactory {
//制造发动机
public Engine createEngine();
//制造空调
public Air createAir();
}
//为宝马320系列生产配件
public class FactoryBMW320 implements AbstractFactory{
@Override
public Engine createEngine() {
return new EngineA();
}
@Override
public Air createAir() {
return new AirA();
}
}
//宝马523系列
public class FactoryBMW523 implements AbstractFactory {
@Override
public Engine createEngine() {
return new EngineB();
}
@Override
public Air createAir() {
return new AirB();
}
}
客户类:
public class Customer {
public static void main(String[] args){
//生产宝马320系列配件
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();
//生产宝马523系列配件
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();
}
}
可能会有同学有疑惑,工厂模式和抽象工厂模式的区别在哪?不都是通过具体工厂创建具体产品吗?这里涉及到产品和产品族的问题,在上面的例子中,我们一个工厂不再只是生产一个产品,而是生产了引擎A、空调A两种产品,这两种配套的产品被称为产品族,而抽象工厂模式就是和产品族对应的。
无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。
所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。