1.设计模式的六大原则
单一职责原则
一个类,应当只有一个引起它变化的原因;即一个类应该只有一个职责。
就一个类而言,应该只专注于做一件事和仅有一个引起变化的原因,这就是所谓的单一职责原则。该原则提出了对对象职责的一种理想期望,对象不应该承担太多职责,正如人不应该一心分为二用。唯有专注,才能保证对象的高内聚;唯有单一,才能保证对象的细粒度。对象的高内聚与细粒度有利于对象的重用。一个庞大的对象承担了太多的职责,当客户端需要该对象的某一个职责时,就不得不将所有的职责都包含进来,从而造成冗余代码。
里氏替换原则
在面向对象的语言中,继承是必不可少的、优秀的语言机制,它主要有以下几个优点:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的可重用性;
- 提高代码的可扩展性;
- 提高产品或项目的开放性。
相应的,继承也存在缺点,主要体现在以下几个方面:
- 继承是入侵式的。只要继承,就必须拥有父类的所有属性和方法;
- 降低代码的灵活性。子类必须拥有父类的属性和方法,使子类受到限制;
- 增强了耦合性。当父类的常量、变量和方法修改时,必须考虑子类的修改,这种修改可能造成大片的代码需要重构。
从整体上看,继承的“利”大于“弊”,然而如何让继承中“利”的因素发挥最大作用,同时减少“弊”所带来的麻烦,这就需要引入“里氏替换原则”。里氏替换原则的定义有以下两种:
- 如果对一个类型为S的对象o1,都有类型为T的对象o2,使得以S定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T是类型S的子类型。
- 所有引用基类的地方必须能透明地使用其子类对象。清晰明确地说明只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道父类还是子类;但是反过来则不可以,有子类的地方,父类未必就能适应。
依赖倒置原则
依赖倒置原则包括三种含义:
- 高层模块不应该依赖低层模块,两者都依赖其抽象;
- 抽象不依赖细节;
- 细节应该依赖于抽象。
传统的过程性系统的设计办法倾向于高层次的模块依赖于低层次的模块;抽象层次依赖于具体层次。“倒置”原则将这个错误的依赖关系倒置了过来,如下图所示,由此命名为“依赖倒置原则”。
在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是具体的实现类,实现类实现了接口或继承了抽象类,其特点是可以直接被实例化。依赖倒置原则在Java语言中的表现是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生;
- 接口或抽象类不依赖于实现类;
- 实现类依赖于接口或抽象类。
依赖倒置原则更加精确的定义就是“面向接口编程”——OOD(Object-Oriented Design)的精髓之一。依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。依赖倒置原则是JavaBean、EJB和COM等组件设计模型背后的基本原则。
接口隔离原则
接口隔离原则有如下两种定义:
- 客户端不应该依赖它不需要的接口。
- 类间的依赖关系应该建立在最小的接口上。
接口隔离原则的具体含义如下:
- 一个类对另外一个类的依赖性应当是建立在最小的接口上的。
- 一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。因此使用多个专门的接口比使用单一的总接口要好。
- 不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构,即不要强迫客户使用它们不用的方法,否则这些客户就会面临由于这些不使用的方法的改变所带来的改变。
迪米特法则
迪米特法则又叫最少知识原则,意思是一个对象应当对其他对象尽可能少的了解。迪米特法则不同于其他的OO设计原则,它具有很多种表述方式,其中具有代表性的是以下几种表述:
- 只与你直接的朋友们通信;
- 不要跟“陌生人”说话;
- 每一个软件单位对其他的单位都只有最少的了解,这些了解仅局限于那些与本单位密切相关的软件单位。
按照迪米特法则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用;如果一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
开闭原则
开闭原则的定义是:一个软件实体应当对扩展开放,对修改关闭。这个原则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即应当可以在不必修改源代码的情况下改变这个模块的行为。
在面向对象的编程中,开闭原则是最基础的原则,起到总的指导作用,其他原则(单一职责、里氏替换、依赖倒置、接口隔离、迪米特法则)都是开闭原则的具体形态,即其他原则都是开闭原则的手段和工具。开闭原则的重要性可以通过以下几个方面来体现。
- 开闭原则提高复用性。在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑,代码粒度越小,被复用的可能性就越大,避免相同的逻辑重复增加。开闭原则的设计保证系统是一个在高层次上实现了复用的系统。
- 开闭原则提高可维护性。一个软件投产后,维护人员的工作不仅仅是对数据进行维护,还可能对程序进行扩展,就是扩展一个类,而不是修改一个类。开闭原则对已有软件模块,特别是最重要的抽象层模块要求不能再修改,这就使变化中的软件系统有一定的稳定性和延续性,便于系统的维护。
- 开闭原则提高灵活性。所有的软件系统都有一个共同的性质,即对系统的需求都会随时间的推移而发生变化。在软件系统面临新的需求时,系统的设计必须是稳定的。开闭原则可以通过扩展已有的软件系统,提供新的行为,能快速应对变化,以满足对软件新的需求,使变化中的软件系统有一定的适应性和灵活性。
- 开闭原则易于测试。测试是软件开发过程中必不可少的一个环节。测试代码不仅要保证逻辑的正确性,还要保证苛刻条件(高压力、异常、错误)下不产生“有毒代码”(Poisonous Code),因此当有变化提出时,原有健壮的代码要尽量不修改,而是通过扩展来实现。否则,就需要把原有的测试过程回笼一遍,需要进行单元测试、功能测试、集成测试,甚至是验收测试。开闭原则的使用,保证软件是通过扩展来实现业务逻辑的变化,而不是修改。因此,对于新增加的类,只需新增相应的测试类,编写对应的测试方法,只要保证新增的类是正确的就可以了。
2.单例模式
基本介绍
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,提供了一种创建对象的最佳方式
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式
饿汉式在类加载的过程导致该单实例对象被创建,虚拟机会保证类加载的线程安全,但是如果只是为了加载该类不需要实例,则会造成内存的浪费。
静态变量的方式:
public final class Singleton { // 私有构造方法 private Singleton() {} // 在成员位置创建该类的对象 private static final Singleton instance = new Singleton(); // 对外提供静态方法获取该对象 public static Singleton getInstance() { return instance; } // 解决序列化问题 protected Object readResolve() { return INSTANCE; } }加 final 修饰,所以不会被子类继承,防止子类中不适当的行为覆盖父类的方法,破坏了单例
防止反序列化破坏单例的方式:
对单例声明 transient,然后实现 readObject(ObjectInputStream in) 方法,复用原来的单例
条件:访问权限为 private/protected、返回值必须是 Object、异常可以不抛
实现 readResolve() 方法,当 JVM 从内存中反序列化地组装一个新对象,就会自动调用 readResolve 方法返回原来单例
构造方法设置为私有,防止其他类无限创建对象,但是不能防止反射破坏
静态变量初始化在类加载时完成,由 JVM 保证线程安全,能保证单例对象创建时的安全
提供静态方法而不是直接将 INSTANCE 设置为 public,体现了更好的封装性、提供泛型支持、可以改进成懒汉单例设计
静态代码块的方式:
public class Singleton { // 私有构造方法 private Singleton() {} // 在成员位置创建该类的对象 private static Singleton instance; static { instance = new Singleton(); } // 对外提供静态方法获取该对象 public static Singleton getInstance() { return instance; } }枚举方式:枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式
public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } } public static void main(String[] args) { Singleton.INSTANCE.doSomething(); }- 问题1:枚举单例是如何限制实例个数的?每个枚举项都是一个实例,是一个静态成员变量
- 问题2:枚举单例在创建时是否有并发问题?否
- 问题3:枚举单例能否被反射破坏单例?否,反射创建对象时判断是枚举类型就直接抛出异常
- 问题4:枚举单例能否被反序列化破坏单例?否
- 问题5:枚举单例属于懒汉式还是饿汉式?饿汉式
- 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做?添加构造方法
反编译结果:
public final class Singleton extends java.lang.Enum<Singleton> { // Enum实现序列化接口 public static final Singleton INSTANCE = new Singleton(); }
懒汉式
线程不安全
public class Singleton { // 私有构造方法 private Singleton() {} // 在成员位置创建该类的对象 private static Singleton instance; // 对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance == null) { // 多线程环境,会出现线程安全问题,可能多个线程同时进入这里 instance = new Singleton(); } return instance; } }双端检锁机制
在多线程的情况下,可能会出现空指针问题,出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作,所以需要使用
volatile关键字public class Singleton { // 私有构造方法 private Singleton() {} private static volatile Singleton instance; // 对外提供静态方法获取该对象 public static Singleton getInstance() { // 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例 if(instance == null) { synchronized (Singleton.class) { // 抢到锁之后再次判断是否为null if(instance == null) { instance = new Singleton(); } } } return instance; } }静态内部类方式
public class Singleton { // 私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } // 对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }内部类属于懒汉式,类加载本身就是懒惰的,首次调用时加载,然后对单例进行初始化
类加载的时候方法不会被调用,所以不会触发 getInstance 方法调用 invokestatic 指令对内部类进行加载;加载的时候字节码常量池会被加入类的运行时常量池,解析工作是将常量池中的符号引用解析成直接引用,但是解析过程不一定非得在类加载时完成,可以延迟到运行时进行,所以静态内部类实现单例会延迟加载
没有线程安全问题,静态变量初始化在类加载时完成,由 JVM 保证线程安全
3.代理模式
静态代理
代理模式:由于某些原因需要给某对象提供一个代理以控制对该对象的访问,访问对象不适合或者不能直接引用为目标对象,代理对象作为访问对象和目标对象之间的中介
Java 中的代理按照代理类生成时机不同又分为静态代理和动态代理,静态代理代理类在编译期就生成,而动态代理代理类则是在 Java 运行时动态生成,动态代理又有 JDK 代理和 CGLib 代理两种
代理(Proxy)模式分为三种角色:
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,可以访问、控制或扩展真实主题的功能
买票案例,火车站是目标对象,代售点是代理对象
卖票接口:
public interface SellTickets { void sell(); }火车站,具有卖票功能,需要实现SellTickets接口
public class TrainStation implements SellTickets { public void sell() { System.out.println("火车站卖票"); } }代售点:
public class ProxyPoint implements SellTickets { private TrainStation station = new TrainStation(); public void sell() { System.out.println("代理点收取一些服务费用"); station.sell(); } }测试类:
public class Client { public static void main(String[] args) { ProxyPoint pp = new ProxyPoint(); pp.sell(); } }测试类直接访问的是 ProxyPoint 类对象,也就是 ProxyPoint 作为访问对象和目标对象的中介
JDK
使用方式
Java 中提供了一个动态代理类 Proxy,Proxy 并不是代理对象的类,而是提供了一个创建代理对象的静态方法 newProxyInstance() 来获取代理对象
static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
参数一:类加载器,负责加载代理类。传入类加载器,代理和被代理对象要用一个类加载器才是父子关系,不同类加载器加载相同的类在 JVM 中都不是同一个类对象
参数二:被代理业务对象的全部实现的接口,代理对象与真实对象实现相同接口,知道为哪些方法做代理
参数三:代理真正的执行方法,也就是代理的处理逻辑
代码实现:
代理工厂:创建代理对象
public class ProxyFactory { private TrainStation station = new TrainStation(); //也可以在参数中提供 getProxyObject(TrainStation station) public SellTickets getProxyObject() { //使用 Proxy 获取代理对象 SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance( station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { System.out.println("代理点(JDK动态代理方式)"); //执行真实对象 Object result = method.invoke(station, args); return result; } }); return sellTickets; } }测试类:
public class Client { public static void main(String[] args) { //获取代理对象 ProxyFactory factory = new ProxyFactory(); //必须时代理ji SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } }
实现原理
JDK 动态代理方式的优缺点:
- 优点:可以为任意的接口实现类对象做代理,也可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,提高了软件的可扩展性,Java 反射机制可以生成任意类型的动态代理类
- 缺点:只能针对接口或者接口的实现类对象做代理对象,普通类是不能做代理对象的
- 原因:生成的代理类继承了 Proxy,Java 是单继承的,所以 JDK 动态代理只能代理接口
ProxyFactory 不是代理模式中的代理类,而代理类是程序在运行过程中动态的在内存中生成的类,可以通过 Arthas 工具查看代理类结构:
- 代理类($Proxy0)实现了 SellTickets 接口,真实类和代理类实现同样的接口
- 代理类($Proxy0)将提供了的匿名内部类对象传递给了父类
- 代理类($Proxy0)的修饰符是 public final
// 程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);//InvocationHandler对象传递给父类
}
static {
m3 = Class.forName("proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
}
public final void sell() {
// 调用InvocationHandler的invoke方法
this.h.invoke(this, m3, null);
}
}
// Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
}
执行流程如下:
- 在测试类中通过代理对象调用 sell() 方法
- 根据多态的特性,执行的是代理类($Proxy0)中的 sell() 方法
- 代理类($Proxy0)中的 sell() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke 方法
- invoke 方法通过反射执行了真实对象所属类(TrainStation)中的 sell() 方法
源码解析
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h){
// InvocationHandler 为空则抛出异常
Objects.requireNonNull(h);
// 复制一份 interfaces
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 从缓存中查找 class 类型的代理对象,会调用 ProxyClassFactory#apply 方法
Class<?> cl = getProxyClass0(loader, intfs);
//proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory())
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取代理类的构造方法,根据参数 InvocationHandler 匹配获取某个构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 构造方法不是 pubic 的需要启用权限,暴力p
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 设置可访问的权限
cons.setAccessible(true);
return null;
}
});
}
// cons 是构造方法,并且内部持有 InvocationHandler,在 InvocationHandler 中持有 target 目标对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {}
}
Proxy 的静态内部类:
private static final class ProxyClassFactory {
// 代理类型的名称前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 生成唯一数字使用,结合上面的代理类型名称前缀一起生成
private static final AtomicLong nextUniqueNumber = new AtomicLong();
//参数一:Proxy.newInstance 时传递的
//参数二:Proxy.newInstance 时传递的接口集合
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 遍历接口集合
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
try {
// 加载接口类到 JVM
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
// 如果 interfaceClass 不是接口 直接报错,保证集合内都是接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
// 保证接口 interfaces 集合中没有重复的接口
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
// 生成的代理类的包名
String proxyPkg = null;
// 【生成的代理类访问修饰符 public final】
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 检查接口集合内的接口,看看有没有某个接口的访问修饰符不是 public 的 如果不是 public 的接口,
// 生成的代理类 class 就必须和它在一个包下,否则访问出现问题
for (Class<?> intf : interfaces) {
// 获取访问修饰符
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
// 获取当前接口的全限定名 包名.类名
String name = intf.getName();
int n = name.lastIndexOf('.');
// 获取包名
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 获取唯一的编号
long num = nextUniqueNumber.getAndIncrement();
// 包名+ $proxy + 数字,比如 $proxy1
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 【生成二进制字节码,这个字节码写入到文件内】,就是编译好的 class 文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
try {
// 【使用加载器加载二进制到 jvm】,并且返回 class
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) { }
}
}
CGLIB
CGLIB 是一个功能强大,高性能的代码生成包,为没有实现接口的类提供代理,为 JDK 动态代理提供了补充($$Proxy)
CGLIB 是第三方提供的包,所以需要引入 jar 包的坐标:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>代理工厂类:
public class ProxyFactory implements MethodInterceptor { private TrainStation target = new TrainStation(); public TrainStation getProxyObject() { //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer = new Enhancer(); //设置父类的字节码对象 enhancer.setSuperclass(target.getClass()); //设置回调函数 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)"); Object o = methodProxy.invokeSuper(obj, args); return null;//因为返回值为void } }); //创建代理对象 TrainStation obj = (TrainStation) enhancer.create(); return obj; } }
CGLIB 的优缺点
- 优点:
- CGLIB 动态代理不限定是否具有接口,可以对任意操作进行增强
- CGLIB 动态代理无需要原始被代理对象,动态创建出新的代理对象
- JDKProxy 仅对接口方法做增强,CGLIB 对所有方法做增强,包括 Object 类中的方法,toString、hashCode 等
- 缺点:CGLIB 不能对声明为 final 的类或者方法进行代理,因为 CGLIB 原理是动态生成被代理类的子类,继承被代理类
方式对比
三种方式对比:
动态代理和静态代理:
动态代理将接口中声明的所有方法都被转移到一个集中的方法中处理(InvocationHandler.invoke),在接口方法数量比较多的时候,可以进行灵活处理,不需要像静态代理那样每一个方法进行中转
静态代理是在编译时就已经将接口、代理类、被代理类的字节码文件确定下来
动态代理是程序在运行后通过反射创建字节码文件交由 JVM 加载
JDK 代理和 CGLIB 代理:
JDK 动态代理采用
ProxyGenerator.generateProxyClass()方法在运行时生成字节码;CGLIB 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类。在 JDK1.6之前比使用 Java 反射效率要高,到 JDK1.8 的时候,JDK 代理效率高于 CGLIB 代理。所以如果有接口或者当前类就是接口使用 JDK 动态代理,如果没有接口使用 CGLIB 代理
代理模式的优缺点:
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
- 代理对象可以增强目标对象的功能,被用来间接访问底层对象,与原始对象具有相同的 hashCode
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
缺点:增加了系统的复杂度
代理模式的使用场景:
远程(Remote)代理:本地服务通过网络请求远程服务,需要实现网络通信,处理其中可能的异常。为了良好的代码设计和可维护性,将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能
防火墙(Firewall)代理:当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网,当互联网返回响应时,代理服务器再把它转给你的浏览器
保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限
4.工厂模式
工厂模式的用意是定义一个创建产品对象的工厂接口,将实际创建性工作推迟到子类中。工厂模式可分为简单工厂、工厂方法和抽象工厂模式。注意,我们常说的23种经典设计模式,包含了工厂方法模式和抽象工厂模式,而并未包含简单工厂模式。另外,我们平时说的工厂模式,一般默认是指工厂方法模式。
简单工厂
简单工厂模式其实并不算是一种设计模式,更多的时候是一种编程习惯。简单工厂的实现思路是,定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。简单工厂的适用场景是:
- 需要创建的对象较少。
- 客户端不关心对象的创建过程。
简单工厂包含如下角色:
- 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品 :实现或者继承抽象产品的子类
- 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
示例:
创建一个可以绘制不同形状的绘图工具,可以绘制圆形,正方形,三角形,每个图形都会有一个draw()方法用于绘图,不看代码先考虑一下如何通过该模式设计完成此功能。
由题可知圆形,正方形,三角形都属于一种图形,并且都具有draw方法,所以首先可以定义一个接口或者抽象类,作为这三个图像的公共父类,并在其中声明一个公共的draw方法:
public interface Shape {
void draw();
}
下面就是编写具体的图形,每种图形都实现Shape接口:
// 圆形
class CircleShape implements Shape {
public CircleShape() {
System.out.println("CircleShape: created");
}
@Override
public void draw() {
System.out.println("draw: CircleShape");
}
}
// 正方形
class RectShape implements Shape {
public RectShape() {
System.out.println("RectShape: created");
}
@Override
public void draw() {
System.out.println("draw: RectShape");
}
}
}
下面是工厂类的具体实现:
class ShapeFactory {
public static Shape getShape(String type) {
Shape shape = null;
if (type.equalsIgnoreCase("circle")) {
shape = new CircleShape();
} else if (type.equalsIgnoreCase("rect")) {
shape = new RectShape();
}
return shape;
}
}
为工厂类传入不同的type可以new不同的形状,返回结果为Shape 类型,这个就是简单工厂核心的地方了。
缺点:增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
工厂方法
工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说每个对象都有一个与之对应的工厂。
工厂方法的实现思路是,定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
工厂方法模式的主要角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
我们用工厂方法的设计模式改造上面的代码
定义一个抽象工厂接口ShapeFactory:
public interface ShapeFactory {
Shape getShape();
}
里面有一个方法返回产品,下面是具体工厂
// Circle工厂
public class CircleShapeFactory implements ShapeFactory{
@Override
public Shape getShape(){
return new CircleShape();
}
}
// 圆形工厂
public class CircleShapeFactory implements ShapeFactory{
@Override
public Shape getShape(){
return new CircleShape();
}
}
和简单工厂对比一下,最根本的区别在于,简单工厂只有一个统一的工厂类,而工厂方法是针对每个要创建的对象都会提供一个工厂类,这些工厂类都实现了一个工厂基类。
下面总结一下工厂方法的适用场景:
- 客户端不需要知道它所创建的对象的类。
- 客户端可以通过子类来指定创建对应的对象。
抽象工厂
这个模式最不好理解,而且在实际应用中局限性也蛮大的,因为这个模式并不符合开闭原则。实际开发还需要做好权衡。抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一个对象,而是可以创建一组对象。这是和工厂方法最大的不同点。
抽象工厂的实现思路是,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂和工厂方法一样可以划分为4大部分。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
示例:
现在需要做一款跨平台的游戏,需要兼容Android,Ios,Wp三个移动操作系统,该游戏针对每个系统都设计了一套操作控制器(OperationController)和界面控制器(UIController),下面通过抽象工厂方式完成这款游戏的架构设计。
由题可知,游戏里边的各个平台的UIController和OperationController应该是我们最终生产的具体产品。所以新建两个抽象产品接口。
抽象操作控制器:
interface OperationController {
void control();
}
抽象界面控制器:
interface UIController {
void display();
}
然后完成各个系统平台的具体操作控制器和界面控制器。
Android:
class AndroidOperationController implements OperationController {
@Override
public void control() {
System.out.println("AndroidOperationController");
}
}
class AndroidUIController implements UIController {
@Override
public void display() {
System.out.println("AndroidInterfaceController");
}
}
IOS:
class IosOperationController implements OperationController {
@Override
public void control() {
System.out.println("IosOperationController");
}
}
class IosUIController implements UIController {
@Override
public void display() {
System.out.println("IosInterfaceController");
}
}
WP:
class WpOperationController implements OperationController {
@Override
public void control() {
System.out.println("WpOperationController");
}
}
class WpUIController implements UIController {
@Override
public void display() {
System.out.println("WpInterfaceController");
}
}
下面定义一个抽象工厂,该工厂需要可以创建OperationController和UIController。
public interface SystemFactory {
public OperationController createOperationController();
public UIController createInterfaceController();
}
在各平台具体的工厂类中完成操作控制器和界面控制器的创建过程。
Android:
public class AndroidFactory implements SystemFactory {
@Override
public OperationController createOperationController() {
return new AndroidOperationController();
}
@Override
public UIController createInterfaceController() {
return new AndroidUIController();
}
}
IOS:
public class IosFactory implements SystemFactory {
@Override
public OperationController createOperationController() {
return new IosOperationController();
}
@Override
public UIController createInterfaceController() {
return new IosUIController();
}
}
WP:
public class WpFactory implements SystemFactory {
@Override
public OperationController createOperationController() {
return new WpOperationController();
}
@Override
public UIController createInterfaceController() {
return new WpUIController();
}
}