常用设计模式
前提概念:
设计模式之六大设计原则
Idea Uml 类图使用:
IDEA UML类图
设计模式的分类
总体来说设计模式分为三大类:
工厂模式
简单工厂模式
定义:定义了一个创建对象的类,由这个类来封装实例化对象的行为。
举例:
工厂类的代码:
java
public abstract class Video {
public abstract void produce();
}
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("录制Python课程视频");
}
}
class VideoFactory{
public Video getVideo(String type){
if("java".equalsIgnoreCase(type)){
return new JavaVideo();
}else if("python".equalsIgnoreCase(type)){
return new PythonVideo();
}
return null;
}
}
使用场景:
JDK Calendar :
java
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
优点:
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
缺点: - 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
简单工厂存在的问题与解决方法: 简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?我们可以定义一个创建对象的抽象方法并创建多个不同的工厂类实现该抽象方法,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。这种方法也就是我们接下来要说的工厂方法模式。
工厂方法模式
**定义:**定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
举例:(我们依然举上面的例子。添加了一个新课程,如果用简单工厂模式的的话,我们要去修改工厂代码,并且会增加一堆的if else语句。而工厂方法模式克服了简单工厂要修改代码的缺点,它会直接增加PHP工厂。类图如下:
VideoFactory中有个抽象的方法:
java
public abstract class VideoFactory {
public abstract Video getVideo();
}
PHPVideoFactory工厂类继承VideoFactory并实现抽象方法:
public class PHPVideoFactory extends VideoFactory{
@Override
public Video getVideo() {
return new PHPVideo();
}
}
通过不同的工厂会得到不同的实例化的对象
工厂方法存在的问题与解决方法:简单工厂就是所有产品都由一个工厂类一个方法来创建,而工厂方法将工厂的职责也进行细化了,每种产品都由自己特定的工厂来生产,这也是单一职责原则的体现。
应用场景:
ArrayList的 public Iterator iterator()方法
ArrayList对应上面的PHPVideoFactory 类,Iterator 对应 public abstract class Video
Itr内部实现类对应PythonVideo类
使用场景:
工厂方法模式主要适用于以下场景:
- 创建对象需要大量重复的代码。
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
- 一个类通过其子类来指定创建哪个对象。
工厂方法模式的缺点
工厂方法模式的缺点也是很明显的,每新增一个产品就需要新增两个类,一旦产品数量上来了,类的个数也会过多,就会增加了系统的复杂度,也使得系统更加抽象难以理解。
抽象工厂模式
定义:是指提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
抽象工厂模式强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象时需要大量重复的代码。此时我们就需要提供一个产品类的库,使得所有的产品以同样的接口出现,这样客户端就可以不依赖于具体实现。
举例:
工厂的接口:
java
public interface CourseFactory {
Video getVideo();
Article getArticle();
}
工厂的实现:
java
public class JavaCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
@Override
public Article getArticle() {
return new JavaArticle();
}
}
抽象工厂模式的适用场景
抽象工厂模式适用于我们有一系列类似的产品(比如华为手机和小米手机),然后这些产品的实现又有细节上的不同,那么这时候就可以利用抽象工厂模式来将产品进行抽象化。
抽象工厂模式的缺点
根据上面的例子再结合设计模式七大原则,其实我们可以发现抽象工厂有一个很大的缺点,那就是扩展产品相当困难,比如示例中想在CourseFactory增加新的方法,那么我们需要修改工厂的源码,这样的话抽象工厂、具体工厂都需要修改,显然违背了开闭原则。所以抽象工厂模式使用的前提必须是产品比较稳定,不会轻易作出修改,否则后期的维护将会非常困难。
三种工厂模式的使用选择
简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)
抽象工厂 :用来生产不同产品族的全部产品。(支持拓展增加产品;支持增加产品族)
为了解释得更清楚,先介绍两个概念:
产品等级 :产品等级结构即产品的继承结构,如一个抽象类是电脑,其子类有小米电电脑、华为电脑、戴尔电脑等等,那么抽象电脑
与具体品牌的电脑之间构成了一个产品等级结构,抽象电脑是父类,而具体品牌的电脑是其子类。
产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如小米公司生产的电脑、电冰箱,电视等,电视位于电视产品等级结构中,冰箱位于冰箱产品等级结构中。
因此工厂方法模式、抽象工厂模式最大的区别在于:
工厂方法模式:针对的是 一个产品等级结构。
抽象工厂模式:针对 多个产品等级结构。
单例模式
定义:确保一个类最多只有一个实例,并提供一个全局访问点
java
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazySingleton = null;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance(){
if(null == lazySingleton){
synchronized (LazyDoubleCheckSingleton.class){
if(null == lazySingleton){
lazySingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySingleton;
}
}
java
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){
}
public static final LazyInnerClassSingleton getInstance(){
return InnerLazy.LAZY;
}
private static class InnerLazy{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
java
public class TestLazyInnerClassSingleton {
public static void main(String[] args) throws Exception {
Class<?> clazz = LazyInnerClassSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object o1 = constructor.newInstance();
Object o2 = LazyInnerClassSingleton.getInstance();
System.out.println(o1 == o2); //false
}
}
这种写法巧妙的利用了内部类会等到外部调用时才会被初始化的特性,用饿汉式单例的思想实现了懒汉式单例。
这种写法看起来是不是效率又高又完美。
应用场景:
JDK 下的Runtime 类封装了 Java 运行时的环境。每一个 java 程序实际上都是启动了一个 JVM 进程,那么每个 JVM 进程都是对应这一个 Runtime 实例,此实例是由 JVM 为其实例化的。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。
由于 Java 是单进程的,所以,在一个 JVM 中,Runtime 的实例应该只有一个。所以应该使用单例来实现。
以上代码为 JDK 中 Runtime 类的部分实现,可以看到,这其实是饿汉式单例模式。在该类第一次被 classloader 加载的时候,这个实例就被创建出来了。